annotepage

A detailed installation

How you install it, at length, with every option.

Before you start

They take routes away — each names a case you are already in, or one choice the page then stops repeating.

https://

It fills in the examples.

Your project

The key is in the tag: anyone who can open the page can read these notes and write them.The key is not on the page. Each reviewer pastes it once, in their own browser.

The notes server

api.annotepage.com, free and already running. Nothing to install and nothing to maintain.One PHP file on hosting you already have. The notes never leave it.

The client

The tag names a range: a newer client arrives without you.The tag names one release and its digest. Every upgrade is a visit here.

Running the notes server yourself asks four more, and one address, and where the database is:

How you install it

Upload one file; its page asks the rest.One command, carrying these answers.

Which sites may write here

Your origins in, every other out.A relay: any project that asks is served.

Storage

One file, created for you. Nothing to prepare.A database you already have: its name, user and password to hand.

Who updates it

A line in your crontab. Only you can touch the code.For a host with no shell: its scheduler calls an address.Nothing to schedule. The web server can rewrite the code.

https:///api.php

Your installer prints it.The command takes it, and checks that it leads to the folder you run it in. Left empty, the blocks guess.

Where the MySQL server is

The password is typed on the installer's screen, never on this page.The password is read from a file on the server, never typed on this page nor in the command.

What to install, and where

Two things to install, and the drawing says where each one goes.

Three things to install, and the drawing says where each one goes.

Install the app on the server

  1. Run one command on the server

    It carries your answers above, and installs.

    Every option: --fetch, then --help.

    curl -fsSO https://annotepage.com/annotepage-install.php
    php annotepage-install.php \
        --api-address=https://api.staging.example.com/api.php \
        --answers-for=one-site \
        --origins=https://staging.example.com \
        --storage=sqlite \
        --updated-by=cron \
        --delete-installer
  2. Upload one PHP file

    Download it onto the server, then open it in your browser.

    It ends by printing your API address, for the field at the top.

    Download annotepage-install.php

    32 KB, one file, nothing to configure. Put it anywhere under your web root.

  3. Answer its three questions

    You have answered all three above.

    Under them it shows the address your pages will point at, filled in from the request that opened it. Correct it if the name your visitors use is not the one this server sees — behind a proxy or a CDN, or through a temporary address. Change it and the installer writes a file there and asks for it back before doing anything else.

    It writes the configuration, then offers to delete itself. Let it.

    The server is downloaded. Three questions, and all three have a default that works.

    First, can this server run it?

    Yes. PHP 8.3, the extensions, a directory it can write to.

    Then, your answers

    What this server is, and where it puts the notes.

    Which sites may write here

    The ones I listAny site

    Storage

    SQLiteMySQL

    The sites these notes will be written from

    https://staging.example.com

    The address these pages will call

    https://api.staging.example.com/api.php

    Where the MySQL server is.

    The installer connects and creates the tables before writing anything. Every one of them is needed.

    Host

    127.0.0.1

    Port

    3306

    Database name

    your-database

    User

    your-user

    Password

    ••••••••

    How this server gets its updates.

    Keeping this server up to date

    A shell cronAn address to callIt updates itself

  4. Declare your project

    You chose The ones I list: the server only takes notes from the projects and addresses written in internal/config-local.php. Paste this block there.

    'projects' => array(
        '7Qb1kZ3xNvA9dLpEqKf2Zt' => array(
            'origins' => array('https://staging.example.com'),
            'mode'    => 'encrypted',
        ),
    ),
  5. Keep it up to date

    Once a day, from your crontab: it updates when a release is out, does the maintenance, and tells the project three totals.

    What it changes, and what it never touches

    17 4 * * * php <your install>/internal/update.php >/dev/null
  6. Keep it up to date

    Once a day, from your host’s scheduler: it updates when a release is out, does the maintenance, and tells the project three totals. The installer shows the secret once.

    What it changes, and what it never touches

    17 4 * * * curl -fsS 'https://api.staging.example.com/api.php?action=update&token=<the secret>'
  7. Change anything else, later

    Everything else lives in one file, and every key in it is commented.

    config-local.php

    // an extract: the file only carries what
    // differs from the defaults in config.php
    return array(
        'active'     => true,
        'deployment' => 'self-hosted',
    
        // THE PROJECTS. The installer leaves this empty:
        // the id is drawn in a browser, afterwards.
        'projects' => array(),
    );

config-local.example.php sits beside it, in the same folder: the same file with every key in it and what each one costs, written out.

Install the tag on the site

  1. Paste the tag

    At the end of <body>.

    The key was drawn in this browser, just now, and sent nowhere.

    The id is derived from the key, in this browser, just now — the server recognises it, it never issues it.

    <script src="https://cdn.jsdelivr.net/npm/annotepage-client@2/dist/annotepage.js"
            data-server="https://api.annotepage.com/api.php"
            data-key="<43 characters, drawn in your browser>"
            defer></script>

    Where it goes, in what you use:

    Framework Static Builder CMS

    Point at your tool to see where the tag goes. Once, in the layout every page shares — not again in a page or a component.

    Next.js app/layout.tsx, last in <body>, as a plain <script> — pages router: pages/_document.tsx

    Nuxt nuxt.config.ts, under app.head.script, with tagPosition: 'bodyClose'

    SvelteKit src/app.html, before </body>

    Astro the layout in src/layouts/, before </body>, with is:inline

    Rails app/views/layouts/application.html.erb, before </body>

    Laravel resources/views/layouts/app.blade.php with Livewire, resources/views/app.blade.php with React, Vue or Svelte

    Symfony templates/base.html.twig, before </body>

    Django the base template your pages {% extends %}, before </body>

    Drupal your theme’s html.html.twig, before </body>

    React Router 7 (Remix) app/root.tsx, last in <body>, after <Scripts />

    SolidStart src/entry-server.tsx, before </body>

    HTML before </body> in every page you want annotated, not only index.html

    Vite (React, Vue) index.html at the root — never imported into the bundle

    Angular src/index.html — not in angular.json scripts

    Hugo layouts/baseof.html, before </body>

    Jekyll _layouts/default.html, before </body>

    Eleventy your base layout, before </body>

    Webflow Site settings → Custom code → Footer code

    Shopify theme.liquid, before </body>, on an unpublished copy of the theme

    Framer Site settings → General → Custom code, end of <body>

    Squarespace Settings → Code injection → Footer

    Wix Settings → Custom code, placed at the end of the body

    Ghost Settings → Code injection → Site footer

    WordPress the plugin: it works on activation, and only the audience you choose receives the key — by hand, hook it on wp_footer

    A Content-Security-Policy must allow cdn.jsdelivr.net in script-src and your notes server in connect-src — what else stops it appearing.

  2. Hand the key over

    Each reviewer pastes it once.

    <43 characters, drawn in your browser>

Keep this key. Lose it and the notes go with it — no recovery, no rotation. Put it where your team keeps its passwords, now.

Nothing appearing? A module or a build that combines the scripts, a wrong integrity digest and a Content-Security-Policy each stop it — the four cases, answered.

Connect your assistant

  1. Give it the key, once

    One file, on the machine where your assistant runs. Write it yourself, or hand it the key and let it write the file (less secure: the key crosses a provider’s logs).

    The address is on your tag. The key is the one your reviewers paste.

    annotepage.json

    {
      "projects": {
        "staging.example.com": {
          "api":    "https://api.annotepage.com/api.php",
          "key":    "<the key>",
          "origin": "https://staging.example.com"
        }
      }
    }
  2. Then ask it

    One sentence, with the address of your site.

    [figure: playing it again]

    use npx annotepage-mcp to fix what the reviewers flagged on staging.example.com

    Bash(annotepage open) … +41 lines (ctrl+o to expand)

    Three notes are open on staging.example.com, all on /pricing.

    Read(pricing.html) … +214 lines (ctrl+o to expand)

    Reading the page they point at. From here it is the loop: measure, edit, reply in the thread, resolve.

    ❯

Nothing to install. Say npx when you ask, and your assistant fetches the latest version itself.
Want it installed for good? See how.