Author: Balaji R (senpai) Published: July 2026 Reading time: ~12 min Tags: extractdb, building-in-public, indie-hacker, chrome-extension, founder-story, behind-the-build, technical-deep-dive
Overview
Every maker knows the feeling: you’re deep in a workflow, doing something repetitive, and you think — there has to be a better way.
For me, that moment came while copying tables from ChatGPT into Airtable. For the third time that day. Each time, the formatting broke, columns didn’t align, and I lost data in the shuffle.
So I built ExtractDB — a Chrome extension that exports AI chat tables to Airtable, Google Sheets, and Notion in one click.
This is the full story. Not the sanitized “I built it in a weekend and it was magical” version. The real one — including the decisions I still second-guess, the bugs that made me question my life choices, and the architectural gotchas that Chrome extension developers rarely talk about until you’ve been burned by them.
If you’re an indie hacker thinking about building a Chrome extension, or just someone curious about what happens after “ship fast” — this one’s for you.
The Itch — Deeper Than I Thought
I live in AI chats. As an indie builder, I use ChatGPT, Claude, and Perplexity daily for:
- Content calendars and editorial plans
- Task lists and project milestones
- Lead research and competitor analysis
- Meeting notes and client deliverables
The AI would generate beautiful, structured tables. Then I’d spend 15 minutes trying to get that data into my actual tools. Copy. Paste. Fix. Realign. Repeat.
But here’s the thing — I didn’t jump straight to building. I spent about two weeks trying to solve this without building my own tool. I evaluated:
- Zapier — Felt like bringing a flamethrower to light a candle. I didn’t need a workflow automation platform; I needed a single button. Plus, setting up Zaps for one-off table exports was slower than copy-pasting.
- Thunderbird (before it was Thunderbit) — Close, but it generalized too much. It tried to be a “copilot for everything” and the AI table export was buried inside a broader feature set.
- Manual automation scripts — I wrote a quick Python script using Selenium to scrape ChatGPT and push to Airtable’s API. It worked. Once. Then ChatGPT’s DOM changed and my selectors broke. Maintaining scraper scripts is a nightmare you don’t want.
- Just getting better at copy-pasting — I genuinely tried. I learned keyboard shortcuts, tried different paste methods (plain text paste, paste special, paste with destination formatting). Nothing worked reliably.
The itch wasn’t just annoyance — it was a gap I couldn’t solve with existing tools.
The Setup: Decisions Before Code
Before I wrote a single line of code, I had to make a few foundational decisions. These seem trivial in hindsight, but they shaped everything that followed.
Chrome Extension vs Web App vs Desktop App
I considered three approaches:
Desktop app (Electron/Tauri): Off the table immediately. I didn’t want users to install an entire desktop application just to export a table once a day. The friction would kill adoption.
Web app: Interesting for a moment. Users would paste their chat content into a web interface, and we’d parse and export it. But that introduces a server — suddenly I’m responsible for storing (even temporarily) people’s AI conversations. That’s a privacy liability I didn’t want. Also, the UX sucks. Nobody wants to copy text, open a new tab, paste it, click export, and then go back to their chat.
Chrome extension: Won by a mile. It lives where the user already is — inside the chat interface. One click, done. No context switching. And because everything runs client-side, there’s no server to manage, no data to store, no compliance paperwork. Privacy is baked into the architecture by default.
JavaScript vs TypeScript
I went with vanilla JavaScript. Controversial, I know. Here’s why:
- This was a weekend project. TypeScript adds compile step overhead, configuration complexity, and mental friction when you’re in flow state on a Saturday morning.
- The Chrome extension API surface is relatively small. Most of what I was touching —
chrome.tabs,chrome.runtime,chrome.storage,chrome.identity— has excellent documentation and predictable return types. TypeScript would have been nice-to-have, not need-to-have. - I knew I’d rewrite parts anyway. The first version’s job was to validate the core loop, not to have perfect type coverage.
Would I use TypeScript if I started today? Probably. The extension has grown enough that the lack of types creates real maintenance drag. But for that first weekend? Vanilla JS was the right call.
What I Already Had on the Shelf
I didn’t start from zero. I had:
- An Airtable API key I’d been using for a personal project
- A Chrome extension starter template I’d modified from a previous (failed) side project
- About two years of React experience that I deliberately didn’t use (more on this in the technical section)
The Weekend Build — Technical Deep Dive
Day 1 (Saturday): Core Architecture
I started Saturday morning at 8 AM with coffee and a rough plan. By 10 PM, I had a working prototype. Here’s what that day looked like.
Content Script Injection
The foundation of any Chrome extension that interacts with a web page is the content script. I declared mine in manifest.json to run on https://chatgpt.com/ and https://claude.ai/:
{
"content_scripts": [{
"matches": [
"https://chatgpt.com/*",
"https://claude.ai/*",
"https://www.perplexity.ai/*"
],
"js": ["content.js"],
"run_at": "document_idle"
}]
}
document_idle was important — it ensures the page is fully loaded before my script runs, but doesn’t block page rendering. For a utility script like this, it’s the sweet spot between reliability and performance.
DOM Traversal for Table Detection
The core loop is deceptively simple on paper:
- Walk the DOM and find
elements
- Extract header rows and data rows
- Serialize to a structured format (JSON → Airtable schema)
- Present detected tables to the user for selection
- One-click export
The reality: ChatGPT doesn’t render HTML
elements. It uses a complex nest of
, flexbox, and CSS grid to simulate tables. The DOM structure changes between ChatGPT versions, between Claude and Gemini, and even between different response formats on the same platform.My table detection logic started as a function called
findTables()that recursively walks the DOM and heuristically identifies tabular structures. The heuristic: find elements with three or more child elements that have uniform dimensions and a grid-like layout.function findTables(root) { const candidates = []; const walker = document.createTreeWalker( root, NodeFilter.SHOW_ELEMENT, null, false ); let node; while (node = walker.nextNode()) { // Heuristic: look for containers with regularly spaced children if (isGridContainer(node)) { const rows = extractRows(node); if (rows.length >= 2) { // header + at least one data row candidates.push({ element: node, rows }); } } } return candidates; }This was naïve. It worked for simple cases but failed on AI chats with complex formatting. I iterated on this heuristic about fifteen times in the first day.
The MutationObserver Nightmare
Here’s where things got spicy.
AI chat responses stream in token by token. ChatGPT’s interface builds the response DOM incrementally. By the time
document_idlefires, there’s no table to detect — the assistant hasn’t started responding yet.I needed a
MutationObserverthat watches for structural changes to the DOM and re-runs table detection after each meaningful mutation:const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { debouncedDetect(mutation.target); } } }); observer.observe(document.body, { childList: true, subtree: true });The debounce was critical. Without it, every keystroke in the user’s prompt triggered table detection. With a 500ms debounce, detection only fires after the DOM has settled.
Even then, there were edge cases. If the user scrolled while a table was streaming, the
MutationObservermight fire while the DOM was in an inconsistent state. I added arequestAnimationFramewrapper to ensure detection only runs after the browser has painted the latest DOM state:function debouncedDetect(target) { if (detectTimer) clearTimeout(detectTimer); detectTimer = setTimeout(() => { requestAnimationFrame(() => { const tables = findTables(document.body); updatePopupState(tables); }); }, 500); }CSP Issues with Inline Scripts
Chrome extensions have strict Content Security Policy by default. Inline scripts (
tags in HTML) are blocked unless you explicitly allow them inmanifest.json. This burned me when I tried to do something innocent — injecting a small inline script for testing.The fix was simple: move all scripts to external files. But the lesson stuck: always test your CSP by loading the extension and checking the console for violations. Chrome's DevTools will tell you exactly which policy blocked what.
Dynamic Content Across Platforms
By the end of Saturday, I had detection working for ChatGPT (Web version, not the native app) and Claude. Perplexity needed a separate detection path because their table rendering uses yet another DOM pattern — CSS grid instead of flexbox.
I ended up with a platform detection function that sets selectors per site:
const PLATFORM_CONFIGS = { 'chatgpt.com': { tableContainer: '.markdown', rowSelector: '.table-row', isLoadingIndicator: '.streaming-animation' }, 'claude.ai': { tableContainer: '.table-container', rowSelector: '.table-row', isLoadingIndicator: '.cursor-blink' }, 'perplexity.ai': { tableContainer: '[data-table="true"]', rowSelector: 'div[role="row"]', isLoadingIndicator: '.animate-pulse' } };This config-driven approach made it easy to add new platforms later without rewriting detection logic.
Day 2 (Sunday): Polish, Publishing, and Pain
Sunday was supposed to be "easy day." It was not.
The Popup UI
I built the popup interface — the little HTML page that appears when you click the extension icon. It shows:
- A list of detected tables with previews
- A dropdown to select destination (Airtable, Google Sheets, Notion)
- Column mapping (auto-detected, with manual override)
- A big "Export" button
The popup communicates with the content script via
chrome.tabs.sendMessage. The content script detects tables and stores them inchrome.storage.local, which the popup reads:// content.js chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'getTables') { chrome.storage.local.get(['detectedTables'], (result) => { sendResponse({ tables: result.detectedTables || [] }); }); } return true; // keeps message channel open for async response });// popup.js document.getElementById('refresh-tables').addEventListener('click', async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const response = await chrome.tabs.sendMessage(tab.id, { action: 'getTables' }); renderTableList(response.tables); });This was straightforward. The UI itself is plain HTML/CSS with minimal JS — no framework. For a popup that displays a handful of tables, React would've been absurd overkill.
OAuth in Extension Context
OAuth in a Chrome extension is... special. You can use
chrome.identity.launchWebAuthFlowto handle OAuth without a redirect URI server, but there are gotchas:- The redirect URI must use
https://.chromiumapp.org/ - You can't use this with providers that require a registered redirect URI
- Google OAuth works natively through
chrome.identity.getAuthTokenfor Google services, but for Airtable and Notion, I had to use the generic flow
The Airtable OAuth flow took me four hours to debug. The issue: Airtable's OAuth implementation expects a redirect to a specific URL, and the Chrome extension's redirect URI format works differently than a standard web app. The fix was implementing a custom OAuth flow:
- Open a new tab with the Airtable authorization URL
- Set up a
chrome.tabs.onUpdatedlistener that catches the redirect - Extract the authorization code from the URL
- Exchange it for tokens via a background script API call
It works, but it's fragile. Chrome's extension OAuth is a place where platform constraints genuinely make developer experience worse, and I don't think enough documentation acknowledges this.
Error Handling: The Last 10%
I spent the last three hours of Sunday on error handling. Not glamorous, but essential:
- API timeout handling — Airtable's API sometimes takes 15+ seconds. The popup can't freeze. I added optimistic UI updates with background retry logic.
- Network offline detection —
navigator.onLinecheck, with a clear message when the user is disconnected. - DOM not found — What if the user's chat doesn't have a table? Graceful messaging, not a cryptic error.
- Rate limiting — Airtable's API rate limits (5 requests per second per base). I implemented a queue with backoff.
Chrome Web Store Submission
I submitted Sunday night. Then waited.
The first review came back in 3 days. Rejected.
Reason: "Insufficient permissions justification." My extension requested
storage,identity, and host permissions forchatgpt.com,claude.ai, andperplexity.ai. The reviewer wanted me to explain why each permission was needed in my privacy policy and store description.The second rejection was for "functionality" — the reviewer couldn't find any tables to export. I'd only added ChatGPT and Claude support, and the Chrome Web Store reviewer's test environment apparently used an account that didn't generate tabular responses. I added demo data mode (a fake table that appears in the popup to show what the extension does without requiring a real AI chat).
Third attempt: Approved. ExtractDB went live on a Thursday, 10 days after the weekend build.
Challenges Faced and Solved
Manifest V3 Migration
I started building this in late 2025, which meant Manifest V3 was required (V2 extensions were being phased out). This came with tradeoffs:
- Service workers instead of background pages: My service worker has to be careful about keeping state in memory. If Chrome terminates the service worker (which happens frequently on idle), any in-memory state is lost. I moved all persistent state to
chrome.storage.local. - No
XMLHttpRequestin service workers: I had to migrate to thefetch()API throughout. webRequestAPI limitations: The blocking version ofwebRequestis gone in MV3. I had to restructure how I handle rate limit monitoring for external APIs.
If I'd started on MV2 and migrated, that would've been a painful rewrite. Starting on MV3 from day one saved me months of migration headaches.
Cross-Origin Requests to Airtable API
Chrome extensions can make cross-origin requests if you declare the hosts in
permissions. But the Airtable API returnsAccess-Control-Allow-Origin: *, which should work... except in the service worker context, CORS is handled differently.The fix was adding the Airtable API URL to
host_permissionsin manifest.json, and making all API calls from the service worker rather than the popup:{ "host_permissions": [ "https://api.airtable.com/*", "https://sheets.googleapis.com/*", "https://api.notion.com/*" ] }Chrome Web Store Review Process
I mentioned the rejections above. Here are actionable tips:
- Write your privacy policy before submitting. The reviewer may ask for it during the review, and having it ready saves a week of back-and-forth.
- Include a "How it works" section in your store listing that explicitly states why each permission is needed.
- Test with a clean Chrome profile. The reviewer's test environment is a fresh Chrome install with no extensions, no bookmarks, and no saved data. My extension assumed the user was already logged into Airtable, which failed in the review environment.
- Add a demo/tour mode. Show what the extension does without requiring real data.
What Went Right
1. Small scope, solved one problem well.
I didn't try to build a general-purpose data tool. "Export AI chat tables to your database" is narrow enough to dominate. Every feature I didn't build was a feature I didn't have to support.
2. Architecture made privacy a feature, not a checkbox.
Because everything runs client-side, there's no server to audit, no data to breach, no compliance to manage. When users asked "do you see my conversations?", the answer is trivially provable: look at the architecture. The extension can't send your data anywhere because there's nowhere to send it. This built trust that marketing couldn't have bought.
3. The config-driven platform architecture paid off.
The decision to design per-platform configs from the start meant adding Grok, Gemini, DeepSeek, and Perplexity later was mostly just adding entries to a config file and writing a DOM pattern for each. Adding a new AI platform is now a ~2-hour task, not a week-long integration project.
4. Ship fast, iterate based on real usage.
The first version had no analytics, no onboarding, no payment system. Just detect → map → export. I added billing after the first week when I realized people would actually pay for this. I added onboarding flow in month two when I saw users struggling with OAuth setup.
What I Learned: The Hard Parts
Distribution is the Real Challenge
Building the extension took a weekend. Getting users has taken six months.
Here's the brutal truth about Chrome Web Store discoverability: it's a search engine with terrible SEO. The store ranks extensions mostly by install count and ratings, which creates a winner-take-all dynamic. New extensions can't get visibility without... installs. You can't get installs without... visibility.
First 10 users: Friends, Twitter followers, and a post on Indie Hackers. Those first users were all people who knew me personally.
First 100 users: Chrome Web Store search + backlinks. I submitted ExtractDB to AlternativeTo, G2, and a few Chrome extension directories. Each directory submission drove a trickle of users. The Chrome Web Store listing started ranking for terms like "export ChatGPT tables" and "Airtable Chrome extension" after about 100 installs.
First 500 users (target, approaching): Content marketing (this blog!), building in public on Twitter/X and LinkedIn, and word of mouth from productive users. The blog posts on blog.skndan.com/extractdb/ are now a significant acquisition channel — each post drives steady organic traffic.
What didn't work:
- Paid ads: Cost per install was too high for a $4.99/mo product. Breakeven horizon was 6+ months.
- Cold DMs: I tried reaching out to creators in the AI tool space. Zero conversions. Don't do this.
- Launch days/special events: Product Hunt launch is pending (still preparing), but single-day launches create spikes, not steady growth. I want consistent traffic.
Pricing: A Journey of Discovery
I started at $4.99/month with zero data to guide the decision. Here's the progression:
- Week 1: Free (no payment). Just wanted users.
- Week 2: Introduced $4.99/month after 10 users asked "how do I pay you?"
- Month 2: Added $59 lifetime option after users complained about yet another subscription.
- Month 3: The lifetime plan had outsold monthly by 3:1. I seriously questioned my pricing model.
- Month 4: Raised lifetime to $79. Sales didn't drop. Should have priced higher.
- Month 6 (now): Both plans active. Lifetime at $79, monthly at $4.99. Conversion rate on free trial is about 8%. Monthly churn around 12%.
The $59 vs $4.99 math: A lifetime customer at $59 generates revenue equivalent to about 12 months of subscription. If the average user stays for 8 months (based on current churn), the lifetime plan actually outperforms. But — cash flow matters. Monthly recurring revenue gives predictable runway. Lifetime creates lumpy revenue.
My current strategy: promote monthly for cash flow stability, keep lifetime as a higher-priced option for power users who self-identify as long-term users.
Indie Pricing Philosophy
I've settled on a few principles:
- Don't compete on price. The cost of building a similar extension is high enough that $5/month isn't the decision factor. Compete on quality and features.
- Offer both options, let users self-select. Some people hate subscriptions. Some people hate paying upfront. Let them choose.
- Lifetime should hurt a little. If $59 is "obviously the better deal" to everyone, it's probably too cheap. The fact that some users choose monthly at $4.99 validates that both options serve different segments.
- Payment processor matters. I use Paddle (not Stripe) because they handle VAT globally, which is critical for an international product. Stripe requires you to register for VAT in every EU country once you cross thresholds — Paddle acts as a merchant of record and handles that.
Support Stories That Shaped the Product
The Grok User
One of the first user emails I got was: "Does this work with Grok?"
I hadn't planned Grok support. xAI's Grok was new, and I wasn't sure it had enough users to justify the effort. But this was a paying user who'd bought the lifetime plan within an hour of discovering the extension. So I added Grok support that weekend.
The DOM structure for Grok was completely different from ChatGPT — they use yet another rendering approach. But the config-driven architecture made it doable. Two days later, Grok support shipped.
That user has since referred four other users. The lesson: your most engaged users will ask for things that expand your product in directions you didn't plan. Listen to them, even if the feature seems niche.
The Team That Saved 10 Hours/Week
A marketing agency reached out in month two. They had a weekly workflow: ask AI to analyze competitor content (tables of keywords, domains, strategies), then manually enter the data into their Airtable CRM. One person was spending ~10 hours per week on this copy-paste cycle.
With ExtractDB, they cut it to 30 minutes. The team lead bought lifetime licenses for all five team members.
This story shaped my roadmap in two ways:
- It pushed team/shared accounts higher on my priority list (currently in development)
- It validated that the core value prop — "stop copy-pasting tables" — resonated beyond individual power users
The Enterprise Prospect Who Needed Notion
A founder at a 50-person company reached out. He loved the extension but his team used Notion for everything. "I'll buy 20 licenses if you get Notion export working."
Notion's API is... interesting. It uses a block-based system rather than rows and columns. Mapping a flat table to Notion's database structure required significant work:
- Create a Notion database (if it doesn't exist)
- Auto-detect column types (text, number, date, select)
- Create each row as a page with properties
- Handle rate limits (Notion's API is generously but still throttled)
Notion integration took about a week of dedicated work. The enterprise deal? Still in discussion six months later. But Notion export became one of the most-used features for individual users, so it was worth building regardless.
This taught me: enterprise deals are slow. Build features for individual users first; the enterprise use case follows naturally.
User Acquisition: What Actually Works
Ranked by effectiveness:
- Content marketing (this blog) — A single blog post on extractdb.com drives consistent organic traffic. Each post is an SEO asset that compounds over time.
- Chrome Web Store SEO — Title, description, and category optimization. I iterated on the store listing four times to improve ranking for target keywords.
- Directory listings — AlternativeTo, Slant, Chrome-Stats, Extension.dev, and niche directories. Each drives 5-20 installs per month consistently.
- Building in public — Twitter/X threads about the build process and monthly metrics. These get shared in indie hacker circles and drive targeted traffic.
- Word of mouth — The highest quality traffic by far, but hardest to engineer. I focus on making the product excellent so users naturally tell colleagues.
- Reddit (r/chrome_extensions, r/SideProject) — Inconsistent traffic but good for feedback and validation on early builds.
- Backlink building — I wrote guest posts for AI-focused newsletters and blogs. Each guest post links to the Chrome Web Store listing.
What I'd do earlier: Start the blog on day one. The first blog post was written in month four, and I regret every month of content I didn't create.
Marketing Strategy
Content Marketing (This Blog!)
The blog at blog.skndan.com/extractdb/ is now the primary growth engine. The strategy:
- Comparison posts ("ExtractDB vs Thunderbit") — Capture users searching for alternatives. These rank well because people compare tools before choosing.
- How-to posts ("How to Export ChatGPT Tables to Airtable") — Capture tutorial traffic. These are evergreen and get shared on forums.
- Behind-the-build posts (this one!) — Build brand and trust. These don't convert directly but create the "maker story" that makes the product memorable.
- Problem-focused posts ("Why Copy-Paste Is Killing Your AI Productivity") — Attract users who feel the pain but haven't searched for a solution.
Directory Submissions
Dozens of directories list Chrome extensions and productivity tools. The ones that drove meaningful traffic:
- AlternativeTo — High authority, good for comparison traffic
- G2 — Enterprise buyers search here
- Chrome-Stats — Extension analytics site with decent referral traffic
- Slant — Community-driven recommendations
- ProductHunt — Still pending, preparing a proper launch
Building in Public
I tweet about ExtractDB's numbers, decisions, and lessons learned. The engagement isn't massive, but it's high quality — mostly other builders who understand the journey. These connections have led to partnerships, guest post opportunities, and feature requests from thoughtful users.
What's Next — Detailed Roadmap
Webhook Support (In Development)
The most requested feature after Notion integration. Architecture:
AI Chat Table → Extension → Webhook URL → Custom WorkflowThe user configures a webhook URL in the extension settings. When they click "Export," the extension sends the table data as a JSON payload to that URL. This opens up Zapier/Make/n8n integrations — users can take the table data and pipe it anywhere.
Technically, this is straightforward:
fetch()to the user's webhook URL with a JSON body. The challenge is:- Timeouts: Webhooks can hang. I'll implement a 10-second timeout with clear feedback.
- Retries: At-least-once delivery with exponential backoff.
- Authentication: Users can add headers (API keys) for their webhook endpoint.
Baserow / NocoDB Integration
Open-source database platforms are growing fast. Users building on Baserow or self-hosting NocoDB shouldn't be left out. The integration follows the same pattern as Airtable — REST API with API key auth.
The challenge here is the variety: each platform has slightly different API semantics for table creation, row insertion, and schema management. The config-driven approach should handle this, but it's more work than basing everything on Airtable's model.
Template System
A time-saver for power users:
- Pre-built templates: "Weekly content calendar," "Competitor research," "Meeting notes" — each template pre-configures column mapping and destination.
- Custom templates: Users save their own mappings for repeated exports.
- Template sharing: A community gallery of user-created templates.
This turns ExtractDB from a "tool you use" into a "system you set up." Higher stickiness, higher switching cost.
API Access for Power Users
A REST API that lets users trigger exports programmatically. Use cases:
- Automate exports as part of a larger script
- Build custom dashboards that consume exported data
- Integrate with internal tools
Pricing: likely a higher tier ($19/mo) with rate limits and API keys.
Team / Shared Accounts
Teams need to share configurations, templates, and credit pools. This is complex — it requires:
- User accounts (current model is device-bound via
chrome.storage) - Team management interface
- Shared credential storage (Airtable API keys stored at team level)
- Role-based access (admin vs member)
This is the biggest engineering lift ahead. I'm evaluating Supabase for auth and database.
Monetization Lessons
The Subscription vs Lifetime Debate
The data tells an interesting story:
- Lifetime buyers are more engaged, submit more feedback, and refer more users.
- Monthly subscribers have higher churn but provide predictable revenue.
- The ideal mix: Both. Lifetime builds a community of invested power users; monthly provides baseline revenue for operations.
Payment Processor Choice
I chose Paddle over Stripe for one reason: tax compliance. Paddle is a "merchant of record" — they handle VAT, GST, and sales tax globally. With Stripe, I'd need to register for taxes in every country where I have paying users. Paddle abstracts that completely.
Tradeoff: Paddle takes a higher cut (~5% + $0.50 per transaction vs Stripe's 2.9% + $0.30). But the time savings on tax compliance more than justifies the difference for a solo founder.
Refund Policy
Generous by necessity. I offer full refunds within 30 days, no questions asked. The actual refund rate: ~2%. Most refund requests come from users who didn't understand the product scope (expected it to work with native apps, not just web chat interfaces). I've used refund conversations to improve the store listing and onboarding flow.
Upsell Path
Currently weak — there's one product, one feature set. The upsell path depends on future features (team accounts, API access, templates). The goal is a clear "individual → team → power user" progression with natural upgrade triggers.
Advice for Aspiring Chrome Extension Developers
If you're reading this and thinking about building your own extension, here's my practical checklist:
1. Start With One AI Platform
Don't try to support every chat interface from day one. Pick ChatGPT (largest user base) and nail the integration. Add Claude, Grok, and the others after you have a working product and paying users.
2. Nail the Core Loop Before Adding Features
The core loop of ExtractDB is: detect → select → export. I had 50 users before I added a single feature beyond that loop. No analytics, no onboarding, no account management. Just the loop, done well.
Test your loop relentlessly. Can a new user go from install to first export in under 30 seconds? If not, your loop is too complex.
3. Plan for Manifest V3 From Day One
MV3 isn't coming — it's here. If you start with MV2 and plan to "migrate later," you'll have a painful, multi-week rewrite ahead. The constraints of service workers are real but manageable if you design for them from the start.
Key MV3 patterns:
- All network requests via
fetch()in service worker - State persistence via
chrome.storage.local(not in-memory variables) - Alarms for periodic tasks instead of persistent background pages
userScriptsAPI for dynamic script injection (Permission:"userScripts")
4. Chrome Web Store SEO is Real
Your store listing is the most important SEO asset you control. Optimize:
- Title: Put target keywords at the front. "Export ChatGPT Tables to Airtable — ExtractDB" beats "ExtractDB — AI Table Exporter"
- Description: First 160 characters are the snippet shown in search results. Make them compelling.
- Screenshots: Show the actual workflow, not just the extension icon. Users want to see what happens when they click.
- Categories: Pick the most specific category available. General categories bury you.
5. Brace for the Review Process
- First-time extensions get more scrutiny. Expect at least one rejection.
- Have your privacy policy written before you submit.
- Test with a clean Chrome profile.
- Include a demo mode so reviewers can test without logging into AI platforms.
- Response time for rejections is 2-5 business days (if you're lucky). Factor this into your launch timeline.
6. Design for Privacy From Day One
Even if you don't plan to market privacy, build it in:
- Process everything client-side when possible
- Don't store user data on your servers (avoid having servers at all if you can)
- Be transparent about what data you access and why
- Write the privacy policy before you launch, not after a user asks for it
7. Distribution Before You Launch
Start building your audience before you have an extension:
- Start a blog or Twitter account about Chrome extension development
- Engage in communities (Reddit, Indie Hackers)
- Build a mailing list
- Document your build process publicly
The extension takes a weekend. Getting users takes months. Start the distribution engine early.
8. Price Higher Than You Think
I started at $4.99/month because $4.99 felt "safe." It was too low. If I could redo pricing:
- Entry price: $7/mo or $79/year
- Lifetime: $149
- The perceived value of "saving 10 hours per week" justifies a higher price than utility tools
The Numbers (Month 6)
Metric Value Time to first MVP 1 weekend Users ~450 registered Monthly active users ~280 MRR ~$1,200 Lifetime revenue ~$8,400 Conversion rate (free → paid) ~8% Monthly churn ~12% Support load ~2-3 emails/week Platforms supported 6 (ChatGPT, Claude, Grok, Gemini, DeepSeek, Perplexity) Destinations 5 (Airtable, Google Sheets, Notion, CSV, Excel Online) Review cycle time 10 days from first submit to live The numbers aren't explosive. It's not a hockey-stick growth story. It's a slow, steady, building-in-public story of a weekend project that grew into a real product with real paying users.
And honestly? That's the story I'm proudest of.
Key Takeaways
- Ship fast — A working Saturday prototype beats a perfect product that never launches
- Solve one problem — "Export AI chat tables to my database" is specific enough to dominate
- Listen to paying users — They'll tell you what to build next, sometimes in the first email
- Price higher — $4.99/mo was too low; let price anchor to value saved, not development cost
- Privacy by design — Architecture decisions made on day one become product moats
- Distribution > Development — The extension took 2 days. Getting users takes a career. Start early.
- Write about your work — The blog is the best acquisition channel I have. I should have started it earlier.
Resources
- ExtractDB Homepage → [extractdb.com](https://extractdb.com)
- Chrome Web Store Listing → [ExtractDB on Chrome Web Store](https://chromewebstore.google.com)
- Blog Home → [blog.skndan.com/extractdb/](https://blog.skndan.com/extractdb/)
- Follow the Build → [@balaji_r on X](https://x.com/balaji_r)
- Chrome Extension Manifest V3 Docs → [developer.chrome.com/docs/extensions/mv3](https://developer.chrome.com/docs/extensions/mv3/)
- Chrome Identity API (OAuth) → [developer.chrome.com/docs/extensions/reference/identity](https://developer.chrome.com/docs/extensions/reference/identity/)
Building ExtractDB has been the most educational six months of my career as an indie hacker. If you've made it this far, thanks for reading. If you use AI chats and tables — you know the pain. Maybe give the extension a try? It takes about thirty seconds to find out if it works for you.