How it works
You want to hear what's wrong with your app in the tracker you already use, ready to fix, without another company holding your users' notes. quibble does it with three pieces, all yours: a script tag in your pages, a Python package inside your app, and your tracker. Nothing else to quibble about in between.
-
Add it to your app
Install the package, give it a token, mount the endpoint and add the button. About ten minutes.
-
People point and say it
The widget adds the page, the element they clicked and the page's last errors.
-
Your app files the issue
With a token only your app holds, straight into your tracker.
Where a note goes
A user clicks the button and writes a sentence. The widget sends it, with the page context, to an endpoint mounted in your own app. Your app files it as an issue using one service token that only your app holds.
That's the whole path. quibble has no telemetry and makes no call home, and the widget's script is served by your app from your own domain, so it adds nothing to your content security policy.
- Their browserA user spots a problem and clicks quibble
- Your appquibble adds the page, the error and who sent it
- Your to-do list GitHub, Jira, Linear…It arrives as an issue, ready to fix
Nothing passes through quibble. There’s no quibble server to pass through.
Add it to your app in four steps
About ten minutes, and most of that is making the token. Whatever your framework and tracker, the steps are the same:
- Install the package. Add quibble to your app's dependencies. Early access comes with the install line. It's free for individuals and small companies; larger ones, email us.
- Give it a token. In GitHub, GitLab, Gitea, Forgejo, Jira or Linear, make a token that can create issues where notes should go, and add it to your app's settings.
- Mount the endpoint. One line where your app starts, behind the sign-in your app already has.
- Add the button. One line in your base template, and every page has it.
The three pieces
The widget
One script tag. Vanilla JavaScript with no dependencies and no build step. It draws the button and panel inside a shadow root, so your styles can't reach it and it adds none to your page. Single-page apps can mount it and unmount it as routes change.
The adapter
A Python package with no dependencies of its own. Mount it on FastAPI, Django, Flask, Starlette or Litestar in a couple of lines. Others can integrate using a documented schema.
Your tracker
Each note becomes an issue carrying a marker label, so feedback is easy to filter from the rest of your work. There's no database: the tracker is the datastore.
One note, step by step
- The widget loads from your app. One script tag. It renders inside a shadow root, reads its settings from your page, and waits.
- On open, it asks your app what's allowed.
GET /quibble/meta?url=…returns the labels and counts of the repository this page's feedback goes to. Your app decides the target, so the tags offered are the tags that will be accepted. - The person writes a sentence. The widget gathers the page URL, title, referrer, viewport and browser, the element they pointed at, the last errors, and any fields your page declares in
window.QUIBBLE_CONTEXT. Masked regions report (masked). - It posts one JSON request to your app.
POST /quibblewith the text, priority, tags and context. Nothing goes anywhere else. - Your app's adapter does the work. It applies the rate limit, picks the target repository, holds the priority to P1–P3, drops tags that don't exist, renders the issue body with diagnostics folded away, and creates the issue with your service token.
- The widget confirms. The response names the issue number, and the person sees it filed.
The endpoints
Five routes under one mount path you choose (default /quibble). The widget only uses the first two; the rest serve the review page.
| Method | Path | Does |
|---|---|---|
POST | /quibble | Files a note as an issue |
GET | /quibble/meta | Labels and counts for the target repository |
GET | /quibble | Lists quibble-filed issues, newest first |
GET | /quibble/review | The review page |
POST | /quibble/{number}/status | Closes or reopens an issue |
Plain JSON over HTTP. The FastAPI, Django, Flask, Starlette and Litestar adapters implement it; anything else can host quibble by implementing the same documented schema.
On your computer, any language
A companion on your computer. quibble serve runs the
widget and its endpoint beside an app written in Node, Rust, Go or anything else: a
desktop app, a local tool, or the app you're developing. It listens only on that
machine, accepts only the origins you list, and can require a shared key, so nothing
else on the network can file through it.
A hosted app in another language implements quibble's documented
backend schema: the same five endpoints, so the widget doesn't change and the path still
ends at your tracker. quibble serve stays local by design; there is no
networked service to run beside a server.
The steps, in the install recipe · all three places quibble works
- Your computerYou spot a problem in your appquibble, running beside it, adds the details
- Your to-do list GitHub, Jira, Linear…It arrives as an issue
Only the issue leaves your computer, sent straight to your to-do list.
What arrives with every note
Always
What they wrote, the page they were on, its title, where they came from, their screen size, browser, language and time zone.
Who sent it
Their name from the sign-in you already have. Without one, they can type it, and the issue says it's self-declared.
When something broke
The last five JavaScript errors the page threw, folded away under Diagnostics until you want them.
When they choose
The element they pointed at. A screenshot, with any region you mark private blanked out.
Additional context you need
Console output, failed network requests and any fields your app wants to add. Off until you turn them on.
What quibble writes to your repository
Worth knowing before you point it at a repository that matters. This is the complete list:
- Issues, each carrying the marker label.
- Labels: the marker, and
P1,P2,P3, created only if missing. Never anything else: a tag your tracker doesn't have is dropped, not created. - On GitHub only, one branch,
quibble-screenshots, and only if screenshots are used; other trackers, Jira and Linear included, attach the image to the issue. You can also keep screenshots on your own server, so the tracker gets only a link. It never touches your default branch.
No comments, no closing, and no edits to anything quibble didn't create.
Everything else it does
Point at it
“This button, here.” People click the element that's wrong, and its selector and text go into the issue.
Private stays private
Mark a region private and its text is never captured and it's blanked out of screenshots.
Your styles can't break it
The widget lives in a shadow root. Your CSS can't reach it, and it adds none to your page.
One button, many repositories
Route notes by page or let people pick. Discover every repository in an org, and mix GitHub with a self-hosted Gitea.
A review page
Every quibble in one list, filtered by open or closed, with close and reopen one click away.
Tags you choose
Offer all your labels, an allowlist, or everything except a blocklist.
Rate-limited writes
Each signed-in sender gets a budget, so one enthusiastic user can't flood your tracker.
An off switch
Set one variable and your app mounts no quibble routes at all.
quibble doctor
Checks a deployment's token, repositories and labels, and reports what's wrong without printing a secret.
quibble demo
Runs a sample app with every feature reachable, offline, with no token.
A remark when you file
If the team switches it on, the panel answers your note with a one-line remark, like “yeah, we know. tell us anyway.” or “let me guess: it worked yesterday.” It's off unless they turn it on, and they pick the tone.
A test kit
In-memory fakes of each supported tracker ship with quibble, so your own tests run with no network.
Which trackers and languages work
Trackers
| Tracker | Status |
|---|---|
| GitHub | In production use |
| Gitea | Tested on 1.27.3 |
| Forgejo | Tested on 12.0.4 |
| GitLab | Tested on 19.4.1 CE |
| Jira Cloud | Tested on Jira Cloud, 2026-09-30 |
| Linear | Tested on Linear, 2026-10-08 |
| GitHub Enterprise Server | Not yet tested |
| Jira Data Center / Server | Roadmap |
| Azure DevOps | Under consideration |
| Redmine | Under consideration |
| Bitbucket Data Center | Under consideration |
Languages
| Language | Status |
|---|---|
| Python | Supported |
| Any other stack By implementing quibble's documented schema. | Supported |
Apps in any language on your computer, with quibble serve as a companion. A hosted app in another language implements quibble’s documented schema. | Local apps |
Every tested version is one we ran quibble against, not one we read about. And we report quibble's own problems with quibble: in each framework it supports, beside a non-Python app, in a single-page app, in the extension and on this site. GitHub Enterprise Server ships as a licensed appliance we can't stand up, so it stays marked untested until someone runs it. Supported rows are generated from quibble 2.0.0 itself; roadmap rows are plans, not promises. Screenshots work on every supported tracker.
Ready to add it?
Four steps, with the code for your framework and the settings for your tracker. Or see it first: the live demo runs quibble itself in your browser, with nothing to install.