Docs

Build the overlay once and host the file next to your site:

bun install
bun run build            # writes packages/overlay/dist/

Host every file in packages/overlay/dist together: openparley.js is a small loader that fetches openparley-app-<hash>.js from the same folder. Cache the app and font files forever (their names change with every release) and the loader briefly.

<script src="/openparley.js" defer></script>

Parleys are saved in each visitor's browser (localStorage). Copy link on a parley produces a URL that contains the whole conversation. When someone opens it, the parley is added to their browser and opened on the element. If they reply, they copy a new link and send it back. Replies don't sync on their own; that's what the team server is for.

Team server (self-hosted)

The sync server is a single Bun process with a SQLite file. One server is one organization.

bun install
bun run build                                  # builds the overlay the server hands out
PUBLIC_URL=https://parley.example.com bun apps/sync/src/main.ts

Or with Docker, from a checkout of the repository, behind your reverse proxy. The database lives in the openparley volume.

docker build -f apps/sync/Dockerfile -t openparley-sync .
docker run -d -p 127.0.0.1:3001:3001 -v openparley:/data \
  -e PUBLIC_URL=https://parley.example.com -e TRUST_PROXY=1 openparley-sync
docker logs <container>                        # the setup link

On first start the server prints a one-time setup link. Open it to create the owner account. Then, on the admin page:

  1. Create a project and list every site origin where it runs, for example https://staging.example.com.
  2. Copy the project's script tag into those sites.
  3. Create invite links for your team.

Visitors see a Sign in button in the overlay. It opens a small window on your server; after signing in, the server sends a session token back to the page, but only if the page's origin is on the project's list.

VariableDefaultMeaning
PORT3001Port to listen on
PUBLIC_URLhttp://localhost:PORTThe URL people use to reach the server. Must match, or sign-in forms are rejected.
DATABASE_PATHapps/sync/data/openparley.db (Docker: /data/openparley.db)SQLite file. Back this up.
TRUST_PROXYoffSet to 1 behind a reverse proxy so rate limiting sees real client IPs.
SETUP_TOKENrandomFix the first-run setup token instead of generating one.
OVERLAY_DIRthe repo's build outputFolder with the built overlay files.

Only for your team, on production

Add data-launcher="members" to the script tag from your admin page:

<script src="https://parley.example.com/openparley.js" data-project="KEY" data-launcher="members" defer></script>

This controls who loads OpenParley, not who can read parleys: that always requires signing in to your server. If your site can already tell employees apart, rendering the script tag only for them works too and costs customers nothing at all.

If your site sends a Content-Security-Policy, allow your OpenParley server in script-src and connect-src, and in font-src if you want the overlay in its own font (otherwise it uses the system font).

Script options

AttributeMeaning
data-projectProject key from your server. Turns on team mode.
data-serverServer URL. Defaults to wherever the script was loaded from.
data-launcher always (default) loads it for every visitor. members (team mode) loads it only in browsers where someone signed in to your server; see On production. auto (links only) loads it once someone opens a parley link or visits with #openparley, and keeps it on in that browser. #openparley=off switches members and auto off again.
data-shortcut Key that starts a new parley from anywhere on the page. Default c; any single key, or off.
data-shortcut-priority page (default): if the page handles the key itself, OpenParley ignores it. openparley: OpenParley handles the key first and the page never receives it.

Using it from code

import { mount } from '@openparley/overlay';

const overlay = mount({ project: 'abc123', server: 'https://parley.example.com', launcher: 'members' });
// later, e.g. when your app unmounts
overlay.destroy();

mount is safe to call more than once; it returns the existing instance.

Using the overlay

ActionHow
Show parleys on the pageClick the parrot in the bottom-right corner
Start a parleyComment, or press C anywhere on the page (even with the overlay closed), then click an element
PostPost, or ⌘/Ctrl + Enter
Next / previous parley on the pageJ / K, or the arrows on a parley
Back out of anything, also while typingEsc (one step at a time)
Close a parley or an unsent new oneClick anywhere outside it
See every parley, including ones whose element is goneView all in the toolbar

Shortcuts never fire while you're typing in a field. C works anywhere; the others only while the overlay is open. If your site or app already uses C, OpenParley stays out of its way: when the page handles the key itself (it calls preventDefault() or stops the event), OpenParley ignores it. See data-shortcut to move or disable it.

How parleys find their element

When you click an element, OpenParley records a CSS selector, its position in the page structure, its text, and identifying attributes such as id, data-testid and aria-label. On later visits it scores candidate elements against all of these. Small changes, like a renamed class or fixed typo, still match. If nothing matches convincingly, the parley shows as “element not found” in the list instead of being pinned to the wrong thing.

Tip: data-testid attributes and stable ids make anchors almost unbreakable. Parleys belong to a page by its path; the query string doesn't matter.

Links and privacy

The conversation in a link is stored after the #. Browsers never send that part to the server, so it doesn't show up in your server logs or analytics. It is visible to anyone who has the link. Team-server links contain only an ID; the content stays on your server and needs a sign-in.

Anyone can craft a link that shows a parley on your site, as with any link-based tool. Comments are always shown as plain text, never as HTML or clickable links. On public production sites, use data-launcher="members" with a team server, or data-launcher="auto".

Limitations