Skip to content

Widget API

Script tag

<script
src="https://cdn.gutternote.com/v1/widget.js"
data-gutternote-key="pk_live_..."
defer
></script>
AttributeRequiredDescription
data-gutternote-keyyesPublic project key. It also starts the widget.
data-gutternote-apinoAPI origin. Default: https://api.gutternote.com.
data-gutternote-opennoStarts with comment mode on. Presence is enough.
data-gutternote-enabled="false"noKeeps the widget hidden from visitors until a reviewer turns it on. See Disabled by default.

If the page has a Content Security Policy, add the sources in CSP.

Programmatic start

Load the script without data-gutternote-key, then call init:

await Gutternote.init({
key: 'pk_live_...',
identityToken: await myBackend.gutternoteToken(),
})
OptionTypeDescription
keystringRequired. Public project key.
apiUrlstringAPI origin.
openbooleanStarts with comment mode on.
enabledbooleanfalse waits for a reviewer to turn it on. See Disabled by default.
identityTokenstringToken your backend signed. See Identity tokens.
authEndpointstringSame-origin URL that returns { "token": "..." }. Called with credentials, and only when the project requires your identity provider.
googleCredentialstringA Google ID token the page already has.
guestNamestringName for a guest session.

Identity order

When several are present, the widget uses the first one available:

  1. A stored Gutternote session
  2. identityToken
  3. authEndpoint
  4. googleCredential
  5. guestName

With none, people can read notes. The widget asks who they are when they comment.

Browser exports

ExportReturnsDescription
init(options?)Promise<Widget | null>Starts the widget. null if setup fails or another copy is mounted for the key.
signInWithGoogle(credential)Promise<Author | null>Exchanges a Google ID token after init().
currentAuthor()Author | nullThe identity used for writes.
signOut()voidClears the session and reloads the widget.
destroy()voidRemoves the widget. init() can start it again.
versionstringWidget version.

Disabled by default

On a live site, add data-gutternote-enabled="false", or set the site to Production under Configure → Site mode. The widget then stays hidden from visitors, and a visitor who never turns it on makes no request at all with the attribute. A browser turns it on by any of these:

HowFor how long
Opening any page with ?gutternote-enabled=remember. This is the reviewer link the dashboard gives you.30 days, in that browser
Pressing Alt+Shift+G (Option+Shift+G on a Mac) on the site, outside a text field.30 days, in that browser
Having signed in on the site to comment.As long as the comment session lasts
Opening any page with ?gutternote-enabled.That tab, until it is closed
Opening a link to a thread.That tab, until it is closed

?gutternote-enabled=false turns it off again and forgets all of them except a comment session.

This hides the widget. It is not access control. Who may comment is set in Access.

To hide it per origin without editing the tag, use Configure → Where the widget shows. That costs a hidden-origin visitor one configuration request.

Themes

Pick the widget’s look under Configure → Theme. Each card shows the widget in that theme. The choice is saved when you pick it. No change to the script tag is needed.

  • Workspace members signed in to the widget see the new theme on pages they already have open, at once.
  • Everyone else sees it on their next page load.

Themes besides Classic come with the Team plan and above. On a lower plan their cards are locked. A workspace that moves to a lower plan keeps its choice saved, but the widget shows in Classic until it moves back.

Every theme sets its own colours, so the widget looks the same whether a visitor’s system is light or dark. Classic is the default. Any other theme loads one small extra file, widget-themes.js, from the same place as widget.js before the widget shows. If a content security policy blocks it, the widget shows in Classic.

Deployment metadata

The widget reads these tags:

<meta name="gutternote:branch" content="fix/nav" />
<meta name="gutternote:commit" content="3ab19f2000000000000000000000000000000000000" />
<meta name="gutternote:pr" content="42" />

They supply build context. Add them on every preview, using the full head commit SHA for GitHub verification. The tags do not establish repository authority; GitHub publication also needs an App-verified repository and PR binding. See Build metadata. The widget also reads Vercel’s vercel:git-* tags. Explicit gutternote:* tags win.

Anchors and pins

Each note saves a CSS selector, a quote of the element’s text, and where the click was, as a fraction of the element’s box. A note in a dialog also saves the dialog. A note also saves the last few buttons clicked on the page before it, so the widget can walk back to it. See Notes on a later step.

Selector order

The widget builds the selector from the first unique value it finds:

  1. data-gutternote-id
  2. data-testid
  3. data-test-id
  4. id
  5. A structural CSS path

A value used twice on the page is skipped. So are IDs that a framework creates for each render, such as React’s useId.

Make pins stable

Give the element people review a unique data-gutternote-id:

<button data-gutternote-id="checkout-submit">Pay now</button>
  • Put it on the smallest stable element people mean: the button, card or total. An ID on a whole grid keeps the note on the grid, not on one price.
  • Keep the value the same when copy, classes or layout change.
  • New notes use the new anchor. Old notes keep what they saved.

Finding the element again

On each visit, the widget tries the selector and checks the text. If the text changed, it looks for one clear text match. If that fails and the selector still matches, the selector wins.

If neither finds the element, the widget does not draw the pin. The note moves to the comment list under Not pinned. Open it there to read it. While it is open, its pin shows where the note was left. If the element comes back, the pin comes back.

A note in a <dialog>, role="dialog", role="alertdialog" or aria-modal="true" element is searched for only in that dialog. Its pin shows only while the dialog is open.

Notes on a later step

A note can be on the third step of a sheet or wizard. No URL leads there, so the widget saves up to eight buttons the reviewer clicked on the page before the note. When somebody opens the note from the list or from a link, and its element is not on screen, the widget clicks those buttons again in order:

  1. It clicks a button only if its selector and its text both still match.
  2. It skips a button that is not on the page, or that is behind an open modal dialog. A reader already on step two goes on from there.
  3. It waits for the page to stop changing after each click.
  4. It stops as soon as the note’s element shows.

The widget never saves or clicks:

  • a submit button in a form
  • a link to another page or site, or a download
  • a button whose text names an action, such as Delete, Remove, Archive, Pay or Log out

A step whose Next needs input gets made-up values. The widget fills only empty, required or invalid fields in that step’s form or dialog, for example reviewer@example.com in an email field. What the reviewer typed is never saved. A field that checks its value only in JavaScript, without required, is not filled, so the walk stops at that step.

The saved buttons are cleared when a single-page app changes route. To keep the walk working after a redesign, give wizard buttons a data-gutternote-id and keep their text.

Pins move when the page scrolls or reflows, when new content renders, and when a single-page app changes route.