Heresay docs

Everything you need to add a Report button to your app, read what people say, and answer them.

Set up Heresay

Each team runs its own Heresay, in its own Google account, so your reports never sit on anyone else's servers. One command sets it up:

npx create-heresay

It signs you in to Google, creates a Firebase project, links a billing account, deploys the dashboard, API and SDK, and makes you the owner. It takes about five minutes, and you can run it again if anything stops partway. Firebase's pay-as-you-go plan is needed for the API; a small app stays inside its free tier.

Later, npx create-heresay update deploys new versions and npx create-heresay status shows what's running.

Add it to an app

  1. Open your dashboard at https://<your-heresay>.web.app/app/ (create-heresay prints the address) and choose Add an app. Pick the platform, give it a name, and list the websites it runs on, for example https://app.example.com and http://localhost:3000.
  2. Copy the line the dashboard shows you (it has steps for Next.js, Nuxt and others too) and paste it before </body> on every page:
<script src="https://<your-heresay>.web.app/sdk/v1.js" data-key="pk_your_key" defer></script>
  1. Reload your app. A Report button appears in the bottom corner. Send yourself a test report and the dashboard shows it within a few seconds.

That's the whole setup. Everything below is optional.

What your users see

A small Report button in the corner. Tapping it opens a panel with two tabs:

  • Report: pick what it is (Broken, Confusing, Could be better, Idea), write a sentence, and send. The current page and app version are attached automatically.
  • Your reports: everything they've sent from this device and what happened to it. When you answer, a dot appears on the button until they've seen it.

Nobody signs up. Each browser gets a random device id, which is how people see their own reports and nobody else's.

The dashboard

Reports arrive sorted by urgency: open reports first, then Broken, Confusing, Could be better and Idea, oldest first within each. For each report you can:

ActionWhat happens
AcceptThe reporter sees "Accepted, being worked on". The report becomes available as a coding-agent prompt.
Decline…You must write a reason. The reporter reads it, so write it for them.
Mark fixed…For accepted reports. The reporter sees "Fixed", and your note if you write one.
Copy prompt for agentFor accepted reports. See Coding agents.

Open an app and choose Install to see its line again. Everyone on the Team page can read and answer reports for every app; owners can add and remove people. People sign in with Google or an email link, using the address an owner added.

Script tag options

Add any of these attributes to the script tag.

AttributeExampleWhat it does
data-keypk_…Required. Your app's key from the dashboard.
data-version1.4.0Your app's version, attached to every report.
data-user-idu_1842Who the user is, in your system.
data-user-labelAsha K.A name to show in your dashboard.
data-user-emailasha@example.comTheir email, so you can reply. Or use identify below.
data-accent#6d28d9Your brand colour for the Send button, selected states and the logo. Hex only. Text on it switches to white or black automatically.
data-positionleftPut the button in the bottom-left corner instead of the right. More in Look and placement.
data-apihttps://…/v1Rarely needed. Where your Heresay API lives, if it is not at the same address as sdk.js.
<script src="https://<your-heresay>.web.app/sdk/v1.js"
        data-key="pk_your_key"
        data-version="1.4.0"
        data-accent="#6d28d9"
        defer></script>

Look and placement

The defaults are what we recommend: change only what your app needs. The easiest way is the dashboard: open an app and choose Design to try each option on the real widget and copy the tag. The first value is the default.

AttributeValues
data-positionright · left · top-right · top-left · center
data-offset20 px from the edges, or x,y like 24,96 to clear a chat bubble
data-buttonalways · none (open it from your own menu with Heresay.open()) · desktop (not on phones) · scroll (after the first scroll)
data-labelReport, or your own words up to 40 characters
data-stylepill · icon · tab (on the side edge)
data-sizeregular · small · large
data-fillneutral · accent (filled with your colour)
data-shadowsoft · none · strong
data-hide-onpaths where the button stays hidden, like /checkout,/login
data-markaccent (the logo follows your colour) · heresay (keep the Heresay teal)
data-themeauto (follows the device) · light · dark
data-fontsystem · inherit (your page's font)
data-langyour page's <html lang> · auto (the browser's) · en · fr · ta (Tamil) · hi (Hindi)
data-placeholderthe question in the text box
data-typesall four, or some of broken,confusing,improvement,idea. Their names don't change: the type sets the priority.
data-thanksa line shown after sending
data-panelcorner · sheet (full height on that side) · center
data-widthregular · narrow · wide
data-backdropdim · clear · blur
data-preferencesshow · hide

Always there, whatever you choose: the Heresay mark on the button, the Your reports tab (people read why a report was declined), and Powered by Heresay in the panel and the introduction, so people can tell your app uses an outside tool. The widget lives in a shadow root, so your CSS can't restyle it.

JavaScript API

The script creates window.Heresay (also available as window.Feedback). Call these any time after it loads.

CallWhat it does
Heresay.identify({ id, label, email })Tell Heresay who is signed in. Call it after login; pass nothing to clear it. Then nobody is asked their name to send a report, and with email you can reply to them. Apps with many users and logins need nothing more: it follows whoever is signed in on that device.
Heresay.setVersion("1.4.0")Set or change the app version.
Heresay.setScreen("Checkout")Name the current screen, for apps whose URL doesn't change between screens.
Heresay.open({ type, text })Open the report form from your own button or menu item. type and text are optional and fill it in, for example from an error screen; the person still reviews and sends it.
Heresay.on("sent", fn)Calls fn({ id, type }) after each report is sent, to thank people or count it in your analytics. Returns a function that stops it.
Heresay.introduce({ title, body })Once per device, a small bubble over the button says what it's for, so people know it exists. Call it when your main screen is up (after sign-in or onboarding), or add data-intro="auto" to the tag. Later calls do nothing. title and body are optional, to use your own words.
Heresay.openPreferences()Open the Preferences tab: a note about their setup, sent with every report. People who aren't signed in can also add a name and an email for replies. It's all optional and saved on their device. Where the button sits is yours to choose, with data-position.
Heresay.close()Close the panel.

Each report also carries the page it came from: its title, its address without the query string, and the window size.

// after your user signs in
Heresay.identify({ id: user.id, label: user.name, email: user.email });

// single-page apps: name screens as the user moves
router.afterEach((to) => Heresay.setScreen(to.name));

Report types and statuses

Types, picked by the reporter

TypeMeansPriority
BrokenSomething doesn't work1 · first
ConfusingIt works, but they couldn't tell how2
Could be betterIt works, and could be better3
IdeaSomething that doesn't exist yet4

Reporters never pick a priority. It comes from the type. Confusing ranks above Could be better on purpose: a feature nobody can operate is closer to broken.

Statuses

DashboardWhat the reporter sees
OpenWaiting for the developer
AcceptedAccepted, being worked on
FixedFixed
DeclinedDeclined, with your reason

Coding agents

Heresay can hand accepted reports to a coding agent (Claude Code, Cursor, Codex, anything with MCP or a shell). In your app's repo, run:

npx heresay connect --url https://<your-heresay>.web.app

It opens your dashboard to make a token for that repo, then adds three files you commit: .mcp.json (the Heresay MCP server), .claude/skills/heresay/SKILL.md (a short skill) and a section of AGENTS.md. The token itself stays in ~/.heresay on your computer, never in the repo. Then ask your agent:

  • "Add Heresay to this app": it creates the app if needed, puts the tag in the right place for your framework, and waits for the first report.
  • "Fix the next Heresay report": it picks an accepted brief, claims it, fixes it on a branch, and marks it fixed with a note the reporter reads.

What an agent can and can't do

CanCan't
List, read, claim and add notes to accepted briefs for the apps in its repoSee open reports, or anything from other repos
Hand a brief to another repo connected to the same app, with what it foundAccept or decline. People do that.
Mark a brief fixed, with a note for the reporterKeep working after you revoke its token (Connected repos in the dashboard)

Report text is fenced and labelled as a user's description, never instructions, in everything an agent reads, so a report can't take over your agent.

One repo, or several

Each repo gets its own token and sees only its apps. A monorepo links several apps to one token. If one app's code lives in two repos (say a web front end and an API), connect both: when you accept a report, the dashboard asks which repo the fix goes in. An agent that finds the fix belongs elsewhere hands it off, and a claimed brief shows as "In progress in …" so nobody does it twice. Claims lapse after a day without activity.

Without MCP

The same actions are commands: npx heresay briefs, brief <id>, claim <id>, note, handoff, fixed <id> "note", and guide <topic>. Agents can also read /llms.txt and the guides under /guides/ on your Heresay.

Keeping it current

The skill in your repo is small and rarely changes. The detailed steps come from your own Heresay each time, so they match what you have deployed. When a newer skill ships, the agent is told to run npx heresay connect --update, and the change shows up in git.

You can still copy a single prompt from any accepted report in the dashboard with Copy prompt for agent.

Security and limits

  • Your key is public, and that's fine. It's in your page's HTML. It only identifies your project; it can't read or change anything.
  • Your key only works on your site. Reports from any other website are refused. Leave the list empty when adding an app to allow any site (useful for testing). Your Heresay's own address is always allowed, so its test page works.
  • Rate limits. Per device: 5 reports per 10 minutes and 20 per day. Per network: 30 per hour. Per app: 500 per day.
  • Size. Reports are up to 2,000 characters. Context fields are trimmed to 200.

Privacy

Heresay stores what the user typed, the type they chose, the page or screen, your app version, browser and OS, and the user id or label only if you pass one. It sets no cookies and takes no screenshots. A random device id lives in the browser's local storage so people can see their own reports.

Content Security Policy

If your site sends a Content Security Policy, allow Heresay in two places:

script-src  https://<your-heresay>.web.app;
connect-src https://<your-heresay>.web.app;

iOS and macOS

A Swift package for SwiftUI apps (iOS 16, macOS 13 and later). In Xcode, File › Add Package Dependencies… with https://github.com/Sibhimanyu/heresay-swift, then:

import Heresay

@main struct MyApp: App {
    init() { Heresay.configure(key: "pk_your_key", url: URL(string: "https://<your-heresay>.web.app")!) }
    var body: some Scene {
        WindowGroup { ContentView().heresayReportButton() }   // iOS: a Report button in the corner
    }
}

On macOS, use ContentView().heresay() and add .commands { HeresayCommands() } for Help › Report a Problem… (⌥⌘R). The same calls as the web SDK exist: Heresay.identify(id:label:email:), Heresay.presentPreferences(), Heresay.introduce() (once per install, a welcome sheet on iOS or a small window on macOS saying where to find it; call it when the main screen appears), , Heresay.setScreen(_:), Heresay.present(type:text:), Heresay.onSent, and Heresay.send(_:text:) for your own UI. How it looks is one HeresayStyle passed to configure(…, style:), with the same choices as the web (corner, offset, text, pill or icon, size, fill, shadow, screens to hide it on, theme, typeface, English, French, Tamil or Hindi, report types, a thank-you line, sheet size, the Preferences tab). The defaults are recommended; the dashboard's Design page shows each choice and gives the Swift. The app version comes from the bundle. Native apps have no web origin to lock the key to; the rate limits still apply.

Other platforms

Android, React Native and Flutter SDKs are on the way. Until then, any app can talk to the same API. Both calls take a JSON body.

POST https://<your-heresay>.web.app/v1/reports
{ "key": "pk_…", "device_id": "a-random-id-16-to-64-chars",
  "type": "confusing", "text": "I can't find where to cancel my plan.",
  "context": { "route": "Settings", "app_version": "1.4.0", "platform": "ios", "os": "iOS 18" },
  "reporter": { "name": "Asha", "email": "asha@example.org", "note": "I use VoiceOver" } }   // optional

POST https://<your-heresay>.web.app/v1/reports/mine
{ "key": "pk_…", "device_id": "the-same-id" }   // this device's reports and their statuses

Troubleshooting

You seeWhy, and the fix
No Report buttonCheck the script tag has data-key, and look for a CSP error in the browser console.
"This site is not allowed to use this key"The page's address doesn't match the website on your project. Use the exact origin, including https:// and any port.
"Too many reports, try again later"A rate limit was hit. It resets on its own; the response says when.
Reports show no versionAdd data-version or call Heresay.setVersion().