← Quickbase Buddy

Privacy policy — Quickbase Buddy

Chrome Web Store review requires a privacy policy at a public URL for any extension that handles user data. This is that text, hosted at https://app.quickbasebuddy.com/privacy, which is rendered from this file at build time, so this file stays the single source.

Last changed 7 October 2026 (only you can erase the change journal; AI clients no longer have a tool for it). Last reviewed in full against the code on 8 September 2026.

The short version

Buddy is in two halves and they have different answers.

The free features — form switcher, field export, role manager, form search, table home button — run entirely inside your browser, on Quickbase pages, using the session you are already signed in to. They send nothing anywhere except Quickbase itself.

The paid AI layer needs an account, so there is now a server. It holds your name, your email address, a hash of your password, and what you pay for. Nothing about Quickbase is stored on it — no realm, no app or table names, no schema, no records, no tool arguments. There is no column in its database that could hold any of that.

The sidebar chat sends your conversation — which includes what its tools read from your Quickbase tab — to the AI model you choose to chat with. With the Buddy model it passes through the server to Anthropic and is not kept on the way; with a model of your own it goes straight from your browser to that provider. See "The sidebar chat" below.

Everything Buddy remembers about your Quickbase apps — the change journal, the schema index the side panel builds — stays in your browser's own storage on your machine, and you can erase it.

What it does

Buddy adds buttons and a side panel to Quickbase pages. With a paid plan it also edits Quickbase for you — fields, tables, relationships, records and table reports — driven by an AI client on your own computer or by the chat in its side panel. Either way it acts inside a Quickbase tab you already have open, the same way you would if you clicked the buttons yourself.

What runs automatically, and where

Six content scripts are declared in the manifest and Chrome runs them on matching Quickbase pages without being asked. A seventh is injected by the extension's service worker. All seven are in apps/extension/entrypoints/.

Script Runs on When
apiRelay.content.js every https://*.quickbase.com/* page document_start
formSearchTab.content.js every https://*.quickbase.com/* page document_idle
homePageReturn.content.js 86 table- and app-settings URLs document_idle
fieldRoleManager.content.js 10 modify-field / role-permission URLs document_idle
fieldExport.content.js 4 field-list URLs document_idle
roleUserInterface.content.js 4 role table-UI URLs document_idle
formSwitcher.js 12 record-form URLs injected by the service worker

Only apiRelay and formSearchTab state a time explicitly; the other four take Chrome's default, which is document_idle. The two that match every page of your realm do so because the features they carry — the side panel's data relay and the form-search launcher — have to be ready wherever you happen to be.

formSwitcher.js is not a declared content script. The service worker injects it with chrome.scripting.executeScript when chrome.webNavigation's onCompleted or onHistoryStateUpdated fires on one of those record-form URLs — the second event is why the webNavigation permission is needed at all, because Quickbase's single-page navigations are silent to tabs.onUpdated (apps/extension/lib/legacyPanel.ts).

Three of these send network requests, and every one of them goes to Quickbase:

The other four make no network requests of any kind. fieldExport builds its CSV in the page and hands it to the browser as a data: URI, so the export never travels anywhere; fieldRoleManager and roleUserInterface only set form controls; homePageReturn only adds a link.

One thing that changed from v1. The shipped v1 apiRelay sent a Quickbase app token — the author's own, for one of the author's apps — as plain text in its source, on every temporary-token request. Quickbase only reads that header on apps whose admin requires application tokens, so it did nothing for anyone else's app, and it was a secret in a public bundle. 2.0 does not send it (apiRelay.content.js, 7 September 2026). The request works on your signed-in session alone, the same way the AI tools' requests always have.

How it signs in to Quickbase — it doesn't

Buddy has no Quickbase account, no Quickbase password, no API key and no admin token. It never asks you for Quickbase credentials and has nowhere to put them if you offered.

Instead it borrows the session you already have. When it needs to talk to Quickbase, it runs a request inside one of your open Quickbase tabs, so the request carries that tab's cookies. Its first call asks Quickbase for a short-lived token of its own:

GET https://api.quickbase.com/v1/auth/temporary/<app or table id>

This is the same temporary-authorization endpoint the Quickbase web UI uses for its own requests. The token that comes back is held in memory only — four and a half minutes in the service worker's cache (packages/qb-core/src/tempToken.ts), five minutes in the content script's (apiRelay.content.js) — and is never written to disk. Restarting your browser or reloading the extension discards it.

The consequence is that Buddy can never do anything in Quickbase you could not do yourself, and it stops working the moment you sign out. If no Quickbase tab is open, or that tab is signed out or expired, Buddy fails with an error and does nothing.

Relevant code: packages/qb-core/src/tempToken.ts, packages/qb-core/src/tabFetch.ts, apps/extension/entrypoints/apiRelay.content.js.

The account, and what the backend stores

The paid layer needs to know who you are and what you pay for, so there is a server: a Cloudflare Worker on a D1 database, which also serves the account site. It is described in full in docs/backend.md.

It was deployed on 7 September 2026 and runs at https://app.quickbasebuddy.com — a Cloudflare Worker on a Cloudflare D1 database, in a Cloudflare account belonging to Jaybird Technologies. The account site is the same Worker on the same origin.

The only data in it so far is the developer's own test account and the sandbox subscription created to prove the payment path works; it has not yet held anybody else's. Everything below describes what it stores for a real account from the moment there is one.

What it stores

Eight tables, from apps/api/migrations/:

What it does not store

Nothing about Quickbase. No realm, no app or table names, no field or report definitions, no records, no tool names or tool arguments, no Quickbase session or token of any kind. The schema has no column that could hold any of it. Tool calls in AI mode travel from your AI client to a local process to the extension to your own Quickbase tab, and at no point touch this server. The Buddy model below is the one route where Quickbase content passes through this server, and it is not written down on the way.

Stripe

Payment is handled by Stripe. Card details are entered on Stripe's own hosted Checkout page and never reach this server or the extension — the Worker creates a Checkout Session and sends your browser to Stripe. What the Worker hands Stripe is your email address and your internal user id (apps/api/src/billing.ts), so that the subscription can be matched back to your account. Stripe's own privacy policy governs what it does with that.

The Worker learns about payments through Stripe's webhook, which it verifies by signature before applying.

Email

Not switched on yet. Until it is, the deployed Worker sends no email at all and the verification table stays empty. What follows is what it does once it is on.

The Worker sends you two kinds of email and no others: a link to confirm your address when you create an account (or sign in before confirming it), and a link to reset your password when you ask for one. There is no newsletter, no marketing mail and no mailing list. The mail goes out through Cloudflare Email Service, from noreply@quickbasebuddy.com, so Cloudflare handles your address and the message to deliver it; Cloudflare's privacy policy governs that. The Email Service dashboard can keep a preview of sent messages for about seven days, which would include the link in them. (apps/api/src/mail.ts, apps/api/src/auth.ts)

Separately, and on whether or not email is: to slow down anyone trying to flood these emails or guess passwords, the sign-in, sign-up and email-sending endpoints count requests per IP address, in the rateLimit table above. Page loads that only check whether you are signed in are not counted.

The sidebar chat, and the Buddy model

The side panel's Chat tab lets you ask about, and change, the Quickbase app you have open in plain language. It needs the Pro plan whichever model answers; without it the tab says so and runs nothing (apps/extension/lib/chat/access.ts). Every change it proposes is shown to you first and written only after you click Approve.

By default the chat answers with the Buddy model, which is included in the Pro plan up to a monthly allowance that renews on your billing date. The side panel sends the conversation to POST /llm/v1/messages on the Worker, carrying your entitlement token instead of a password. The conversation is your messages, the chat's instructions and tool list, and the results of the tools it ran — which can include app, table, field and report names, report settings, and record values a tool read from your Quickbase tab. The Worker forwards it to Anthropic's API and returns the answer. It does not store or log the conversation; it writes only the usage_events row above (apps/api/src/llm.ts). Anthropic receives the conversation under its own commercial terms and privacy policy. To show how much of the allowance is used, the panel also reads GET /llm/usage with the same token; that answers with your spend, the cap and the period's dates, and receives nothing.

The chat can instead use a model of your own: Anthropic or OpenRouter with your own API key, a local model in Ollama or LM Studio, or another OpenAI-compatible service you name (apps/extension/lib/chat/llm.ts). Then the extension talks to that service directly and nothing goes through this server. The provider, model and key you choose are kept in chrome.storage.local under qbb:chatSettings. Conversations are saved on your computer, in the extension's own IndexedDB, so the panel can reopen them — see "Chat history" under "What is stored on your computer". On a development build each line is also written to the connection log described there.

Request logging

apps/api/wrangler.jsonc sets observability.enabled with head_sampling_rate: 1. That switches on Cloudflare Workers Logs at 100%: every request to the account site and API is logged by Cloudflare with its standard request metadata, which includes the URL path, method, status, timing and the client IP address. Retention is whatever Cloudflare's plan gives; it is not configured here.

The Worker's own code writes six log lines, all of them error paths, all of them a JSON object with a stage name and an error message (apps/api/src/index.ts, and apps/api/src/mail.ts for a failed send, which logs the subject line and the failure code but not the recipient). None of them logs a user id, an email, a token or a request body on purpose. The catch-all handler does log the exception's stack trace, which could incidentally carry a value that appeared in an error message.

There is no analytics, no product telemetry, no crash reporting and no tracking of any kind in the extension, the bridge, the account site or the Worker. There is no analytics SDK anywhere in the dependency tree, no tracking pixel, and no sendBeacon call. Cloudflare's request logging above is the one thing in this document that is a log of you rather than of your data, and it is infrastructure, not a decision to measure you.

The entitlement token

The extension needs to know whether you have paid, and it needs to know it offline. So the account site hands it a signed statement rather than a session.

  1. You sign in on the account site and click Connect extension.
  2. The page reads GET /entitlement with its own first-party cookie, and sends the token only to the extension with chrome.runtime.sendMessage(extensionId, …). Your session cookie never crosses; the extension never calls the sign-in API.
  3. The extension verifies the signature against a public key compiled into it, and caches the result.

The token is a JWT whose claims are: an opaque account id (the same 32-character id the database uses — not your email), the plan name, one feature flag (mcp), and issued-at and expiry timestamps. It lives 24 hours. There is no Quickbase data in it and no personal data beyond that id (apps/api/src/jwt.ts, packages/qb-core/src/entitlement.ts).

The manifest's externally_connectable lists exactly one origin, and only a page on that origin can send the extension anything at all. The listener checks the sender's origin a second time itself and answers nothing else (apps/extension/lib/entitlementListener.ts). That origin is https://app.quickbasebuddy.com, the deployed account site.

The development bypass. The code contains a setting that skips the entitlement check entirely. It is honoured only when the extension has no update_url in its manifest, which is Chrome's own marker for "loaded unpacked from a folder" (apps/extension/lib/buildKind.ts). A Web Store install reads the same stored value and discards it, and the options page renders its toggle only on an unpacked build. It exists so that a development build can be tested without a paid subscription. It is in the shipped code and inert there, and that is why it is written down.

Where data goes

Only to your own Quickbase realm, and to the account site if you have an account. The extension is allowed to reach exactly two kinds of address for Quickbase, and Chrome enforces this — they are its declared host_permissions:

Plus one optional one, https://app.quickbasebuddy.com/*, the account site. It is not granted at install; only if you click Allow automatic renewal on the Options page does the extension send it your account-site cookie, for the entitlement refresh above. The chat reaches the same site without that permission, for the Buddy model and its allowance: those requests carry the entitlement token rather than a cookie, and the Worker answers only this extension's own origin.

And one loopback address: the legacy WebSocket transport dials ws://127.0.0.1:<port> (apps/extension/lib/bridgeClient.ts). It reaches only your own machine, and the default transport is native messaging, which uses no socket address at all.

Nothing else, with one exception you choose: if you set the chat to use a model of your own, it sends the conversation to that service — Anthropic's API, OpenRouter, a local Ollama or LM Studio, or an address you type in — with the key you gave it. No other third-party host appears in any request it makes. The account site itself loads no third-party resource — no analytics tag, no font from a CDN, no Stripe.js, no framework from anywhere. Each of its pages loads one stylesheet, at most one script, and the two typefaces that stylesheet names, all from their own origin: the fonts are files the site ships, copied out of their npm packages when it is built (apps/web/scripts/sync-design.mjs) rather than fetched from anyone at run time. The site has no runtime dependencies at all.

One third-party address does appear in the product: the side panel has a Submit feedback link to a Zoho Forms page (FEEDBACK_URL in apps/extension/entrypoints/sidepanel/tools/panelConstants.jsx). Nothing is fetched from it and no request is made until you click it, at which point you are on Zoho's site under Zoho's terms.

What is stored on your computer

Five things, all local, none of them synced or uploaded.

1. The change journal

Every change the AI layer applies is recorded, so you can see afterwards exactly what happened — including anything that moved that you did not ask to move. This is a safety feature: it is how Buddy catches itself making an unintended change.

Retention constants: packages/qb-core/src/journal/policy.ts (MAX_AGE_DAYS = 90, MAX_ENTRIES = 500).

2. The side panel's app index

This is a free-tier feature, and it is the largest store of Quickbase data on your machine after the journal.

The side panel has an Add this app button. Adding an app writes the app's id, your realm subdomain and a timestamp under qbb:apps, then reads the app, its tables, every field definition in every table and every report definition in every table through apiRelay.content.js, and writes the lot under qbb:appIndex (indexApp in apps/extension/entrypoints/sidepanel/App.jsx; the keys and the storage calls are in sidepanel/tools/appStorage.jsx). That is a full local mirror of the app's schema — names, ids, types, comments, report definitions. It is not record data.

3. Settings, the connection log, and the entitlement

In chrome.storage.local, in your browser profile (apps/extension/lib/settings.ts, apps/extension/lib/entitlement.ts):

Two things worth knowing about these.

A tool-call entry in the connection log records the tool's name and its full arguments, serialised as JSON (apps/extension/lib/toolDispatch.ts). When the tool is one that writes records, those arguments contain the record values you are writing. So this log is a second place record data can land, and it has different rules from the journal: it is capped at 1000 entries with no age limit, and the journal's Erase history button does not touch it. It has its own Clear control on the options page, next to the log.

The cached entitlement carries your opaque account id. It is deleted when you sign out on the account site — the page tells the extension before it ends the session (apps/web/src/account.ts, apps/extension/lib/entitlementListener.ts) — or when you click Forget this plan on this browser on the Options page. Otherwise it stays in chrome.storage.local; it stops working as a credential after 24 hours plus the 72-hour grace.

Both of these are more than a privacy policy would ideally have to describe. They are written here because they are what the code does.

4. Which tabs have the panel open

In chrome.storage.session, one boolean per tab id, under panelVisible:<tabId> (apps/extension/lib/legacyPanel.ts). It holds no URL and no content, it is removed when the tab closes, and session storage is memory-backed and cleared when the browser restarts.

5. Chat history

Each chat conversation is kept in the extension's IndexedDB database qbb-chat (apps/extension/lib/chat/history.ts): its title (the first thing you asked), when it was started and last used, which model answered, and every message — yours, the model's, and the results of the tools it ran, which can include app, table, field and report names and record values. The system prompt is not stored. At most the last 30 conversations are kept, and one unused for 90 days is deleted. Nothing in it is sent anywhere by being stored; continuing a conversation sends it to the model you have chosen, as described above.

6. The bridge host log

If you use Buddy with an AI client, a small local helper process runs on your machine and writes a log to /tmp/quickbase-buddy-<your user>/host.log (%LOCALAPPDATA%\quickbase-buddy-host.log on Windows), created readable only by you (mode 0600) and truncated once it passes 1 MB.

This log records connection lifecycle only — hosts starting and stopping, clients connecting and disconnecting, framing errors. It does not contain tool names, tool arguments, Quickbase data, or the contents of any request.

The local helper process

To let an AI client such as Claude Code drive it, Buddy uses a helper called qbb-bridge. It matters for privacy that this is entirely local:

One thing it reads that is worth naming. To find Buddy's extension id without asking you to read it off chrome://extensions, the bridge's registration step opens each installed browser profile's Secure Preferences (or Preferences) file and walks the list of every extension you have installed — across Chrome, Chrome Beta, Dev and Canary, Chromium, Edge, Brave, Vivaldi, Arc and Opera (packages/mcp-bridge/src/extensionDiscovery.ts). It is read-only; it keeps only entries that declare the nativeMessaging permission — in either permissions or optional_permissions, since Buddy declares it in the second — and whose name begins "Quickbase Buddy", and only those ids are ever written into the host manifest. Nothing about your other extensions is stored, transmitted or logged. But the file is read, and that is worth saying.

It passes tool calls through to the extension and answers back. Nothing it handles leaves your machine.

How this is built and why is described in docs/native-host.md.

Browser permissions, and why each one

Everything in this table is required — granted when you install the extension — except nativeMessaging and the account site, which are optional: neither is granted at install, and Buddy asks for each only when you click the button on the Options page that needs it.

Permission Required? What it is for
*://*.quickbase.com/* Required Reading and writing Quickbase, on your realm and on Quickbase's services. and .ui. hosts.
*://api.quickbase.com/* Required Quickbase's REST API, with a temporary token.
tabs Required Finding your open Quickbase tab so requests can run in it, and reading a tab's URL to decide whether the side panel applies to it.
scripting Required Injecting formSwitcher.js into a record form, and running the side panel's search and highlight functions in the page.
webNavigation Required Quickbase's single-page navigations do not fire tabs.onUpdated, so the form switcher would not appear without onCompleted and onHistoryStateUpdated.
sidePanel Required Opening and configuring the side panel.
storage Required The settings, log and entitlement above, and the per-tab panel flag.
nativeMessaging Optional Talking to the local helper process. Asked for by a click on the Options page, at the moment you enable the bridge; until you grant it, the extension does not try to start the helper at all.
https://app.quickbasebuddy.com/* Optional Renewing the entitlement from the account site with its cookie, so you need not click Connect extension each day. Asked for by Allow automatic renewal on the Options page.
externally_connectable Required One origin — the account site — may send the extension its entitlement token. Nothing else may message it.

v1's activeTab is not carried over. The host permissions already cover every URL either half touches, so it granted nothing and only widened the install prompt.

Why nativeMessaging is optional. Chrome shows a warning for it — "Communicate with cooperating native applications" — and an extension that adds a warning is disabled until every existing user accepts it. Most people who use Buddy never turn the bridge on, so making it optional is what lets 2.0 arrive as an ordinary silent update instead of stopping everyone at a prompt for a feature they do not use. The practical effect for you: if you never enable the bridge, you are never asked for this permission and the extension never holds it. If you do enable it and then decline, nothing breaks — the bridge simply stands down, and the Options page keeps an Allow native messaging button for whenever you change your mind. You can withdraw the permission afterwards from Chrome's own extension settings, and the extension closes the connection when you do.

Two exceptions worth stating plainly, because both involve pages that are not Quickbase. The tabs permission lets Chrome tell Buddy the URL of every open tab, and two things use it beyond Quickbase.

The service worker reads the URL of every page you load. It listens on chrome.tabs.onUpdated with no URL filter, and on every completed navigation in any tab it looks at that tab's address to decide whether to enable the extension's icon and side panel (enableSidePanelForTab in apps/extension/lib/legacyPanel.ts). If the page is not on a Quickbase subdomain it disables the icon and does nothing further: the URL is not stored, not sent anywhere, and not written to any of the logs described above. It is written to the extension's own service-worker console — console.info("[QuickbaseBuddy] Disabling action for non-Quickbase URL", …) — which is visible only to someone who opens developer tools on the extension, and is not persisted to disk. This is v1 behaviour, and it is the price of an icon that lights up only on Quickbase.

get_current_view reads the active tab's URL, whatever site it is on. The tool answers "which app and table am I looking at?", and to decide whether you are on Quickbase it has to look at the address first. If you are not on Quickbase the URL is discarded — the tool reports only "the active tab is not on the Quickbase realm", and no non-Quickbase address appears in its output, in the journal, or anywhere else. Buddy still cannot read the contents of such a page.

And one about pages that are Quickbase. The side panel's form search runs its search function (apps/extension/entrypoints/sidepanel/tools/pageSearch.jsx) in the page's main JavaScript world (world: "MAIN", in runSearch in apps/extension/entrypoints/sidepanel/App.jsx), not in the isolated world content scripts normally get. It does that because on the new form builder the field labels are only in the page's own Apollo GraphQL cache, which an isolated-world script cannot see. It reads form and section names, field labels, and on legacy form pages the decoded default values of the form's hidden inputs. The results are shown in the panel and are not stored or transmitted. It is a wider reach into the page than a content script has, and it is only ever pointed at a Quickbase form.

What Buddy never does

If you have an account, then the things it does do with personal information are: store your name, email and password hash; record the IP address and user-agent of each sign-in; give your email and account id to Stripe so billing works; send confirmation and password-reset emails to your address through Cloudflare Email Service; pass a conversation you send to the Buddy model through to Anthropic, keeping only its cost; and put your opaque account id inside the entitlement token cached in your browser. That is the whole list.

Erasing your data

On your computer:

On the server:

Children

Buddy is a tool for working with a Quickbase business application. It is not directed at children and collects nothing from anyone under 13.

Changes to this policy

If Buddy ever gains a capability that changes any of the above, this document changes with it in the same commit, and the date at the top moves.

Contact

Questions about this policy, or a request to delete your account: email jtorrence@jaybirdtechnologies.com.