Docs
Links only (no server)
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:
- Create a project and list every site origin where it runs, for example
https://staging.example.com. - Copy the project's script tag into those sites.
- 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.
| Variable | Default | Meaning |
|---|---|---|
PORT | 3001 | Port to listen on |
PUBLIC_URL | http://localhost:PORT | The URL people use to reach the server. Must match, or sign-in forms are rejected. |
DATABASE_PATH | apps/sync/data/openparley.db (Docker: /data/openparley.db) | SQLite file. Back this up. |
TRUST_PROXY | off | Set to 1 behind a reverse proxy so rate limiting sees real client IPs. |
SETUP_TOKEN | random | Fix the first-run setup token instead of generating one. |
OVERLAY_DIR | the repo's build output | Folder 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>
- Visitors download a loader of under 1 KB, which checks a flag in their browser and stops. No overlay, no network requests.
- A teammate switches it on once by visiting any page with
#openparleyat the end of the URL, or by opening a parley link a colleague shared, and signing in. - From then on, OpenParley loads in that browser on every visit. If the session expires, they see a Sign in button.
- Sign out in the toolbar switches it off again, as does visiting with
#openparley=off.
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
| Attribute | Meaning |
|---|---|
data-project | Project key from your server. Turns on team mode. |
data-server | Server 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
| Action | How |
|---|---|
| Show parleys on the page | Click the parrot in the bottom-right corner |
| Start a parley | Comment, or press C anywhere on the page (even with the overlay closed), then click an element |
| Post | Post, or ⌘/Ctrl + Enter |
| Next / previous parley on the page | J / K, or the arrows on a parley |
| Back out of anything, also while typing | Esc (one step at a time) |
| Close a parley or an unsent new one | Click anywhere outside it |
| See every parley, including ones whose element is gone | View 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
- Desktop browsers only for now. Touch devices can view parleys but picking elements is not tuned for them.
- Sites that route with the URL hash (
#/page) are treated as a single page. - Elements inside iframes and closed shadow DOM can't be picked individually.
- The team server checks for new replies every 15 seconds and when you return to the tab, not instantly.