Widget API
Script tag
<script src="https://cdn.gutternote.com/v1/widget.js" data-gutternote-key="pk_live_..." defer></script>| Attribute | Required | Description |
|---|---|---|
data-gutternote-key | yes | Public project key. It also starts the widget. |
data-gutternote-api | no | API origin. Default: https://api.gutternote.com. |
data-gutternote-open | no | Starts with comment mode on. Presence is enough. |
data-gutternote-enabled="false" | no | Keeps 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(),})| Option | Type | Description |
|---|---|---|
key | string | Required. Public project key. |
apiUrl | string | API origin. |
open | boolean | Starts with comment mode on. |
enabled | boolean | false waits for a reviewer to turn it on. See Disabled by default. |
identityToken | string | Token your backend signed. See Identity tokens. |
authEndpoint | string | Same-origin URL that returns { "token": "..." }. Called with credentials, and only when the project requires your identity provider. |
googleCredential | string | A Google ID token the page already has. |
guestName | string | Name for a guest session. |
Identity order
When several are present, the widget uses the first one available:
- A stored Gutternote session
identityTokenauthEndpointgoogleCredentialguestName
With none, people can read notes. The widget asks who they are when they comment.
Browser exports
| Export | Returns | Description |
|---|---|---|
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 | null | The identity used for writes. |
signOut() | void | Clears the session and reloads the widget. |
destroy() | void | Removes the widget. init() can start it again. |
version | string | Widget 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:
| How | For 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:
data-gutternote-iddata-testiddata-test-idid- 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:
- It clicks a button only if its selector and its text both still match.
- 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.
- It waits for the page to stop changing after each click.
- 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.