Privacy & data
A technical map of every byte. What's stored where, what gets sent to whom, and which paths are opt-in. No marketing voice.
This page is the long version of the marketing privacy page — the same facts, but written for someone who wants the field-level detail before installing. If you've read the source, none of this should surprise you; if you haven't, this is what you'd find there.
Two principles drive every choice below:
- Data lives on the device. The default storage for anything butter knows about your dashboard is your browser. Every sync layer that moves it elsewhere is either Chrome's own (which we don't operate) or opt-in (Pro cloud sync).
- One thing reaches off-device by design. The AI Inbox sends a small slice of your PR / issue metadata to OpenAI via our Edge Function. It's the only widget that does. It's a Pro feature; it's clearly labelled; and we don't retain or log the payload.
Where data lives
| What | Where | Notes |
|---|---|---|
| Layout, theme, background On your device + Chrome sync | chrome.storage.sync | Rides Chrome's built-in profile sync. Travels between Chromes signed into the same Google account. We don't host or read it. |
| Todo content On your device — plus Supabase if Pro cloud sync is on | chrome.storage.local | Per-instance keyed ( |
| Scratchpad text On your device — plus Supabase if Pro cloud sync is on | chrome.storage.local | Per-instance keyed ( |
| Pomodoro running state On your device only | chrome.storage.local | Per-instance keyed ( |
| GitHub / Linear / Google Calendar / TickTick OAuth tokens On your device only | chrome.storage.local |
|
| Network response caches On your device only | chrome.storage.local |
|
| AI Inbox cached triage On your device only | chrome.storage.local | Namespaced by your butter user ID. TTL is the widget's "Cache for (minutes)" setting (default 60). |
| AI Meeting Prep cached brief On your device only | chrome.storage.local |
|
| butter account session On your device + Supabase auth | chrome.storage.local | Supabase JWT. Used to authenticate server-side calls (AI Inbox quota check, AI Meeting Prep, cloud sync). Sign out from Settings → Account to clear. |
| Pro cloud-sync payload (opt-in) On your device + Supabase | Supabase row, scoped to your user | Same payload as the sync layer: layout + theme + background. Disabled by default on every device; turn on per-device. |
Every outbound request
A full enumeration of every domain butter calls from your browser. All of these are direct browser → service requests; we don't proxy them.
| Endpoint | When | What's sent |
|---|---|---|
| api.github.com | GitHub widget fetches PRs | Your OAuth token + search query. No payload back to us. |
| api.linear.app | Linear widget GraphQL queries | Your OAuth token + GraphQL query. No payload back to us. |
| www.googleapis.com/calendar/v3 | Calendar widget fetches today's events | Your OAuth token + a today-window query. Read-only scope (<code>calendar.readonly</code>). No payload back to us. |
| hn.algolia.com | Hacker News widget refreshes | A public read against HN's Algolia API. No auth. |
| www.reddit.com | Reddit widget refreshes | Anonymous JSON read. No auth. |
| api.open-meteo.com | Weather widget refreshes | Coordinates + units. No account. |
| geocoding-api.open-meteo.com | You search a city in Weather settings | Your query string. |
| google.com/s2/favicons | Quick Links tile favicon load | The hostname of each link. Hidden if you set an emoji. |
| icons.duckduckgo.com | Fallback when Google has no favicon | Same — hostname only. |
| auth.trybutter.xyz | You connect GitHub, Linear, Google Calendar, or TickTick via the proxy flow; or any of those tokens is refreshed | Brokers OAuth handshake and refresh. No persistent storage. |
| api.are.na | Inspiration widget refreshes | Channel slug. Anonymous public read; no auth. |
| api.ticktick.com | Todo widget reads or mutates a synced project | Your TickTick OAuth token + task payload (title, due date, priority, tags, sort order). Goes direct to TickTick, never through our servers. |
| *.supabase.co/functions/v1/ai-inbox | AI Inbox widget refreshes | Titles, URLs, repo / team / state, timestamps, priority, draft flag. Payload below. |
| *.supabase.co/functions/v1/ai-meeting-prep | Calendar widget generates the AI brief for an upcoming meeting (Pro) | Event title, description, attendee names + emails, time window; plus the titles and identifiers of your recent GitHub PRs and Linear issues. Payload below. |
OAuth: what the auth-worker actually sees
GitHub, Linear, and Google Calendar integrations use OAuth. butter operates a small
Cloudflare Worker (auth.trybutter.xyz) that holds each
OAuth app's client_secret so you don't have to register
your own apps. The worker is briefly a middleman in the handshake:
- You click Connect. The worker redirects you to the provider's authorise page with the appropriate scopes.
- You approve. The provider redirects back to the worker with an authorisation code.
- The worker exchanges the code for a token. Uses
its
client_secretagainst the provider's token endpoint. - The worker hands the token to your browser via a
chromiumapp.orgredirect (URL fragment). It keeps no persistent state — the source is open and the README is explicit about this.
From that point on, your browser holds the token in
chrome.storage.local and talks directly to
api.github.com or api.linear.app. The worker
is not in the data path for any subsequent fetch.
The AI Inbox: exact payload
Only relevant if you've added the AI Inbox widget. The Edge Function receives a JSON body containing, for each PR:
titleurlrepoauthorupdatedAtdraft
And for each Linear issue:
identifiertitleurlteamKeystateNamepriorityupdatedAt
Plus a Bearer header with your Supabase JWT (used for
Pro authentication, never forwarded to OpenAI). The Edge Function
passes the structured payload to OpenAI, returns the model's response,
and discards the input. No persistence on our side, and per OpenAI's
API terms, no model training on it.
Things that are not sent: PR diffs, review comments, commit history, Linear issue descriptions, issue comments, attachments, your OAuth tokens, or your IP address beyond the standard request metadata the Edge Function platform sees.
AI Meeting Prep: exact payload
Only relevant if you have the Calendar widget on your dashboard and a Pro subscription. The Edge Function receives a JSON body containing, for the next upcoming event:
idtitledescription(truncated at 1,000 chars)startMs,endMsattendees:email,displayName,isSelflocationjoinUrl
Plus up to thirty of your most recent GitHub PRs (same fields as the AI Inbox above) and up to thirty Linear issues, so the model can spot which work items relate to the meeting and surface them as references in the brief.
Things that are not sent: any other calendar's events, events outside today's window, PR or issue bodies, your OAuth tokens for any provider, attachments, or IPs beyond what the Edge Function platform sees. The brief is cached on your device for two hours so re-renders of the new tab don't re-bill the model.
Stripe (Pro billing)
Pro is billed through Stripe. We never see or store card details — Stripe handles checkout and stores the card on its own infrastructure. On our side we keep only the minimum needed to wire your subscription to your butter account: your email, your Stripe customer ID, your subscription ID, and whether your plan is active.
What we don't do
- No analytics SDK. No Segment, no Mixpanel, no Heap, no Google Analytics in the extension. The marketing site has a privacy-respecting analytics setup; the extension itself has none.
- No ads, no tracking pixels. butter doesn't have an ad business model and won't grow one. The free tier is paid for by the Pro tier.
- No selling data. The data path is: your browser → your devices → maybe Supabase if you opted into cloud sync, maybe OpenAI if you opted into the AI Inbox or AI Meeting Prep. There is no other consumer of it.
- No required account. Free butter doesn't need an account; the AI Inbox and cloud sync are the only features that require sign-in.
Deleting your data
- Layout / theme / background — uninstall the
extension, or clear
chrome.storage.syncvia chrome://extensions → butter → Storage → Clear. If Chrome sync is on, other devices will reflect the clear on next open. - OAuth tokens — disconnect from Settings → Connections, or revoke the integration from the provider's settings.
- Cloud sync payload — turn cloud sync off in Settings → Account, then delete your butter account via the same panel. Pro subscription cancellation goes through Stripe's customer portal.
- Cached network data — also lives in
chrome.storage.local; the Storage panel above clears it too. Caches re-fetch on demand.