{"slug": "free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill", "title": "Free AI for my Chrome extension's users, with no API key and no surprise bill", "summary": "A developer behind the Groupify Chrome tab organizer built a Cloudflare Worker that gives extension users AI-powered tab grouping without requiring an API key, running on Workers AI's free daily allocation so requests simply fail rather than bill once the quota is exhausted. The Worker identifies the extension via CORS, caps requests per IP per minute, sends only tab titles and site names (never full URLs), and stores no data, while the extension tries on-device AI first, then the cloud, then date-based naming. The developer also implemented a 15-second \"ownChangesUntil\" window so the extension's automatic sorting never overrides groups users created by hand.", "body_md": "**TL;DR** I wanted my tab extension to name and sort tab groups with AI. Chrome's built-in AI isn't on most computers, and nobody wants to paste an API key into a tab tool. So I put a tiny Worker on Cloudflare in between, running on the free Workers AI allocation: when it runs out it simply stops, and nothing is billed. Users without on-device AI flip one switch in Settings and get AI names.\n\nGroupify is my Chrome tab organizer. When I start a new project I usually have Pinterest, the Next.js docs, Cloudflare and Vercel open at once, and grouping by website just splits them apart. What I wanted was one click to put those tabs in one group, with a sensible name such as “Website deploy”.\n\nThat needs AI. The question was which AI.\n\nI wanted a free allocation that stops when it's used up, like the Azure speech free tier I use in another extension.\n\nWorkers AI gives every account a free daily allocation. On the Workers Free plan, requests past it fail instead of being charged, so the most it can ever cost me is nothing. I only needed a tiny Worker in the middle that does three things: recognise my extension, limit how much each person can use, and turn the tabs into a prompt.\n\nOne endpoint names a set of tabs; the other sorts a pile of tabs into groups by task. The Worker stores nothing and logs nothing, so it never remembers what anyone sent.\n\nThe extension sends each tab's title and site name, never the full address, because addresses often carry tokens or private parameters. At most 40 tabs go in one request, and long titles are cut short.\n\nThe Worker answers only my extension's origins (the store build and my development build) through CORS. That means the extension needs no new permission, so users see no new warning when it updates. An origin can be faked, so there's also a per-IP limit per minute on top of the free allocation's hard cap. The worst case is a day without AI.\n\nMost of my users read Traditional Chinese, so I picked a cheap open model that handles Chinese well and told it to skip its thinking output to save allocation. Naming a group uses very little, and at my current number of users the free allocation goes a long way.\n\nCloud AI is a switch in Settings, off by default, with a line saying tab titles and site names go to my server and nothing is kept. The extension always tries on-device AI first, then the cloud, then names by date, so whichever step fails, users never see an error.\n\nOnce Groupify could sort tabs automatically, I hit a problem: automatic sorting must not pull apart groups I made by hand. If I put my own blog's tabs in a group called “Me”, sorting by purpose shouldn't move them into “Reading”.\n\nBut Chrome doesn't record who changed a tab or a group, only how things are now. So the extension has to tell for itself. Before it changes any group, it writes down “the next few seconds of changes are mine”. A background worker listens to Chrome's tab and group events, and treats anything outside that window as the user's decision, including dragging tabs and renaming groups in Chrome's own tab bar.\n\n```\n// The extension's own changes are wrapped, so they never count as the user's\nasync function asExtension<T>(change: () => Promise<T>): Promise<T> {\n  await storage.session.set({ ownChangesUntil: Date.now() + 15_000 })\n  try {\n    return await change()\n  } finally {\n    // Chrome's events can arrive a little late, so keep the window open for a second\n    await storage.session.set({ ownChangesUntil: Date.now() + 1_000 })\n  }\n}\n\nconst isOwnChange = async () => (await storage.session.get('ownChangesUntil')) > Date.now()\n\n// Background worker: every other change is the user's decision\ntabs.onUpdated.addListener(async (tabId, change, tab) => {\n  if (change.groupId === undefined || change.groupId === NO_GROUP) return\n  if (await isOwnChange()) return\n  const group = await tabGroups.get(change.groupId)\n  if (group.title) rememberSite(siteOf(tab.url), group.title) // e.g. \"amsyn.org\" -> \"Me\"\n})\n\ntabGroups.onUpdated.addListener(async (group) => {\n  const before = lastKnownTitle(group.id) // from a snapshot the worker keeps\n  if (before === group.title || (await isOwnChange())) return\n  renameRulesAndLock(before, group.title) // rules and locks follow the new name\n})\n\n// Reclassify: what the user decided goes first, automatic sorting gets the rest\nfunction reclassify(tabs: Tab[], rules: SiteRule[], mode: Mode) {\n  const movable = tabs.filter((tab) => !tab.pinned && !isLocked(tab.group))\n  const { claimed, rest } = splitByRules(movable, rules) // sites with a rule\n  return [...groupsFromRules(claimed), ...plan(mode, rest)]\n}\n```\n\nA few details:\n\n|  | Before | Now | \n|---|---|---|\n| Users without on-device AI | Groups named after the date | One switch for AI names and sorting | \n| API key | Users would need their own | None at all | \n| Cost risk | A budget alert only notifies | Stops when the free allocation runs out | \n| Privacy | — | Off by default, titles and site names only, nothing kept | \n\nIf usage grows or someone abuses it, the worst that happens is a day without AI, and the extension goes back to naming by date. For a side project with a handful of users, that's the setup I'm most comfortable with.\n\nIf you want to add free AI that can't run up a bill to your own extension or small tool, paste this into your AI coding assistant:\n\n```\nAdd an optional shared AI feature to my browser extension, using this design:\n\n1. Server: a Cloudflare Worker using the Workers AI binding (free daily allocation, so on the\n   Workers Free plan it fails instead of billing). Pick a cheap model that handles my users'\n   languages. Expose only the endpoints the feature needs, keep no logs or state, and validate\n   every request body (cap the number of items and the length of each string).\n2. Access: answer CORS only for my extension's origins (store ID plus my unpacked dev ID, read\n   from a variable), so the extension needs no new permission. Add a per-IP rate limit with the\n   Workers rate-limiting binding.\n3. Data: send only what the prompt needs (for tabs: title and hostname, never full URLs).\n4. Answers: ask for JSON, but parse leniently (find the first JSON object, ignore any thinking\n   block) and validate every field. Retry once if the answer contains U+FFFD.\n5. Extension: an opt-in setting, off by default, that says exactly what is sent and where. Try\n   any on-device AI first, then the Worker, then a non-AI fallback, so no error ever reaches the\n   user. Treat every HTTP error or timeout as \"no AI\".\n6. Tests for request parsing, answer parsing and the fallback order, plus a README for the\n   Worker with the deploy steps.\n\nDon't ask me for any secret. Stop after writing the code so I can review and deploy it.\n```\n\n*Cover: [Mail sorting room, U.S. Post](https://www.rawpixel.com/image/7006634/photo-image-vintage-public-domain-tables) by libraryofcongress, [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/).*", "url": "https://wpnews.pro/news/free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill", "canonical_source": "https://dev.to/amsyeung/free-ai-for-my-chrome-extensions-users-with-no-api-key-and-no-surprise-bill-a32", "published_at": "2026-10-19 07:07:55+00:00", "updated_at": "2026-10-19 07:14:33.909115+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "developer-tools", "ai-infrastructure"], "entities": ["Groupify", "Cloudflare", "Workers AI", "Chrome", "Next.js", "Vercel", "Pinterest", "Azure"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill", "markdown": "https://wpnews.pro/news/free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill.md", "text": "https://wpnews.pro/news/free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill.txt", "jsonld": "https://wpnews.pro/news/free-ai-for-my-chrome-extension-s-users-with-no-api-key-and-no-surprise-bill.jsonld"}}