Skip to main content
Getting from a cloned repository to feedback landing in your tracker. These are the steps that need a human: creating credentials, approving an OAuth login, and pasting a script tag onto a site. Everything else is a build or a deploy. This page covers self-hosting on Cloudflare Workers. To run the relay as a Node process instead, see Self-hosting on Node. To skip hosting entirely, see burnsidesteps.com.

You end up owning a fork

Both paths leave you running your own copy. Nothing phones home, and nothing updates itself. Pulling a future release is a git pull from upstream when you decide to, and nothing notifies you that there is one. That is the trade for having no account, no per-seat pricing, and no vendor between you and your data.

Prerequisites

git, Node 24 (or 26 and later) and pnpm 9 or later, plus a Cloudflare account. The free plan is enough. wrangler is deliberately not installed globally: it is a devDependency of relay/ and runs through pnpm. Its own floor is lower, Node 22: its binary exits immediately below it. Platforms. These commands are written for macOS and Linux, and for WSL on Windows. rm and openssl are not available in cmd.exe or a default PowerShell. Where this page uses openssl rand -hex 32, this works anywhere Node does:
Clone the repository and install dependencies:
pnpm install prints a few Failed to create bin at ... burnside-relay warnings. They are expected on a fresh checkout: hosted and www depend on relay, whose bin points at relay/dist/server.js, which nothing has built yet. The install exits 0 and the warnings stop after the first build.

Have an agent do it

The steps on this page are the ones an agent follows. If you would rather hand it over, paste this into Claude Code, Cursor, or any agent with terminal access:
Set up Burnside Steps on my site. The repo is github.com/greenwichsteps/burnside-steps. Deploy the relay to my Cloudflare account, connect it to my Linear workspace, and add the widget to my site. Create the KV namespace and paste its id into relay/wrangler.jsonc before the first deploy, and set ADMIN_SETUP_TOKEN as a secret before it too. Both orderings matter.
Swap “my Linear workspace” for “my GitHub repository” or “my GitLab project”. The last step still needs you either way: claiming the admin panel and pasting a destination credential are deliberately manual. The two extra sentences are not padding. Steps 3 and 4 below both have to happen before the first deploy, and an agent handed only the first paragraph will deploy a relay that cannot store a config or claim a panel.

1. Create an API credential for your destination

Feedback can land in Linear, GitHub, or GitLab. Create a credential for whichever you use, and keep it somewhere safe. It is pasted once into the admin panel and is never stored in the repo. Linear. Settings, then Account, then Security and Access, then New API key. Scope it either to full access or to write and create-issue permissions on the teams that will receive feedback. You also need the team UUID for each project, which you can copy from the Linear UI. GitHub. A personal access token with repo scope, or a fine-grained token with issue write permission on the target repository. You need the repository owner and name. GitLab. A personal or project access token with api scope. You need the numeric project id, and for self-managed GitLab, the host of your instance.

2. Authenticate wrangler with Cloudflare

This opens a browser window for OAuth approval.

3. Create the CONFIG KV namespace

The relay stores project configuration and destination tokens in a Workers KV namespace. This is what the admin panel writes to.
The CONFIG binding in relay/wrangler.jsonc ships without an id, so add the one you just created:
Delete the production config. wrangler.production.jsonc in relay/ is the Greenwich Steps deploy config, kept in the repo so the project deploys from its own source. It is not useful to you, and the repo’s tests expect the template to carry no account-specific values, so remove it now that you have pasted your own id:
The guards that depend on it then report as skipped, and pnpm test stays green.

4. Set the admin setup token, before the first deploy

The first-run endpoint that creates an admin password is unauthenticated by nature: anyone who found the URL before you set a password could otherwise claim the panel. It is gated behind an ADMIN_SETUP_TOKEN secret. Generate a long random value, for example with openssl rand -hex 32, and set it as a Worker secret before deploying:
Keep the value. You enter it once, on the admin setup screen in step 6.

Run it locally first, if you want to

Optional, and not part of the deploy path. pnpm dev starts wrangler’s local server on http://localhost:8787, serving the demo page and the admin panel from your own machine. Local runs do not read the secrets you set with wrangler secret put, which only exist on a deployed Worker. They read relay/.dev.vars, which is gitignored. Copy the template and fill it in:
One thing to know about the admin panel in local dev: its session cookie is named __Host-bs_admin, and a browser takes a __Host- cookie only from an origin it treats as secure. Current Chrome and Firefox count http://localhost as secure, so the panel works in both. Safari does not: it accepts the sign-in and then drops the cookie, so you cannot stay signed in there, and http://127.0.0.1 is no different. To use the panel in Safari, serve local dev over TLS with a certificate Safari trusts, or use a deployed relay. The demo page and the widget work in every browser.

5. Deploy

This builds the widget and admin panel into relay/public/, then runs wrangler deploy. Note the resulting URL, for example https://burnside-steps.<your-subdomain>.workers.dev.

6. Claim the admin panel

Visit https://<relay-host>/admin.
  1. First visit is setup. Enter the ADMIN_SETUP_TOKEN from step 4, then choose a password of at least 8 characters. There is no separate account system: one password gates the panel. The token must be at least 24 characters, and setup is refused outright if it is shorter, however correctly it is typed.
  2. Delete the setup token. It is needed once, but it stays a credential that claims the panel for as long as it exists: if the credentials are ever cleared, whoever still has that token can claim the panel before you do.
    Self-hosting with Docker or npm: remove the line from your .env and restart.
  3. Sign in with that password.
  4. On Integrations, paste the credential from step 1 and test the connection.
  5. On Projects, add a project. Set its allowed origins, its destination, and any tag to label mapping. Project ids and origins live here, not in the repo, so adding a site later needs no redeploy.
The admin password is stored hashed in KV, never in the repo. The session cookie is signed and scoped to the same origin, so the panel is not reachable cross-origin.

7. Install the script tag on a site

Add this before </body>, or through your platform’s snippet or header-scripts feature:
<project-id> must match a project you added in step 6, and that project’s allowed origins must include the exact origin of the site, for example https://staging.acme.com. For a live site, add data-mode="query". The button then appears only after someone visits any URL with ?feedback=on, which persists for that browser tab. Share that link with the people you want giving feedback. There is no integrity attribute by design: the script is first-party, served from a relay you control, and an SRI hash would break every installed site on each widget deploy.

8. Verify end to end

Open a site with the tag installed, submit one piece of real feedback with an annotation, and confirm the issue appears in your tracker with the screenshot attached. This is the one check no automated test covers, because it exercises your tracker’s handling of a real payload. If your destination is GitHub and the issue arrives with no image, nothing has failed. GitHub has no API for attaching an image to an issue, so the relay stores screenshots itself and needs an R2 bucket, which is not part of the steps above. See Enable screenshots for GitHub under Optional. Linear and GitLab upload the screenshot through their own APIs and need nothing extra.

Optional

Enable screenshots for GitHub. Only GitHub needs this. Linear and GitLab upload the screenshot through their own APIs and work with no extra setup. GitHub has no API for attaching an image to an issue, so the relay stores the screenshot itself and links to it. Without a bucket, GitHub issues arrive text-only. Nothing raises an error and nothing is logged: the issue body simply reads _Screenshot unavailable._ where the image would have been.
Then add the binding to relay/wrangler.jsonc, beside the existing kv_namespaces block:
The binding has to be named SHOTS. Redeploy with pnpm deploy. Screenshot URLs are unguessable but public: anyone holding one can open the image, because the <img> GitHub renders carries no cookie. Signing them means only URLs the relay itself produced are served, so a guessed or altered path is refused:
Set it before you file anything you care about. The signature covers the stored path, so changing or removing the key later breaks every screenshot already linked from an existing issue - the image stops loading, though the issue text is untouched. Leaving it unset is supported and screenshots still work; it means any correctly-guessed path is served. Expire old screenshots. Entirely your choice, and off unless you ask for it. The bucket is yours, so screenshots stay until you remove them, and your licence does not affect this either way. To have R2 expire them for you:
Ninety days is an example, not a recommendation. R2 applies the rule itself, so nothing runs in your Worker and it keeps working whether or not the relay is up. To inspect or remove the rule:
Activate a Pro licence. A one-time Pro licence lifts the project cap and removes the “Powered by Burnside Steps” badge. After purchase you receive a key by email. Open the admin panel, go to the Licence tab, and paste it. Activation is offline: the relay verifies the key’s signature and never calls out to check it. The project cap lifts at once. The badge clears from your sites within a minute rather than instantly, because widget settings sit in each visitor’s browser cache for that long, your own included. If you reload your site straight after activating and the badge is still there, that is the cache and not the licence. Attach a custom domain. Free in the Cloudflare dashboard, under Workers, your worker, then Domains. Worth doing if a network blocks *.workers.dev, which some corporate filters and ad blockers do. Rotate a destination token. Paste a new one into the admin panel’s Integrations tab. No code change and no redeploy. Reset the admin password. There is no self-service reset. Clearing the credentials re-opens first-run setup, so the order matters: set a fresh token first, claim the panel, then remove the token again. Doing it the other way round leaves the panel claimable by anyone holding the old token, for as long as it takes you to notice.
Between steps 2 and 3 the panel is unclaimed and anyone with the token can take it. Keep that window short, and do not skip step 3.

Which steps need a human