Back to projects
    Projects/personal/ Sheet Send - An Open Source Browser Extension for lead Collection
    Browser ExtensionJavaScriptManifest V3Google Sheets APIOAuth 2.0

    Sheet Send - An Open Source Browser Extension for lead Collection

    A free, open-source browser extension that sends highlighted text to Google Sheets with one right-click. No backend, no cost, cross-browser. Built for fast lead collection and data capture.

    6 min readVisit project
    Sheet Send browser extension popup showing Google account connection and destination settings

    One day, I was scrolling through instagram on my phone and was collecting instagram handles ID for another project of mine. Then I wondered what if I could collect Instagram handles on a sheet instead of sending it to my own DM. So I built an extension that does the switching and pasting for me. That's it, that's the whole origin story. Highlight something on a webpage, right click, and it lands as a new row in a sheet you've already set up. No backend, no server, no cost to me or to anyone using it.

    Everyone signs in with their own Google account. I don't see your data, I don't store it, I don't have access to it. The extension talks straight from your browser to your own Google Sheet. There was never a version of this where I wanted to run a server just to move some text around, so there isn't one.

    What it actually does

    Select text on any page, right click, pick a destination, and it shows up as a new row in the sheet you configured. You can also capture a timestamp, the source URL, and the page title alongside the text if you want that.

    Click the toolbar icon and you get a popup where you handle everything, switching your Google account, adding or editing destinations, picking a primary destination, switching which tab you're sending to, and choosing whether the right click menu shows all your destinations or just the one you use most.

    There's a Verify step when you add a destination, it checks the spreadsheet is actually reachable and pulls the real tab names so you're not typing them from memory and getting it wrong. If the tab you want doesn't exist yet, you can create it right there in the popup.

    The first time you use a destination, it writes a header row for you. And if you select something unusually long, it asks you to confirm before sending, so you don't accidentally dump an entire article into one cell.

    Should you use Chrome, Edge, Brave, or Firefox?

    Short answer, doesn't matter, it works on all of them.

    Longer answer, Chrome and Edge use Google's own built in identity system, so signing in is basically instant and Google handles refreshing your session in the background. Brave and Firefox don't have that system, Brave specifically strips it out on purpose, so those two go through a different sign in flow. You'll notice a slightly different popup when you connect, but the end result is the same, you're signed in and it stays that way until you log out yourself.

    I spent a genuinely stupid amount of time figuring out why Brave kept failing silently with no error at all. Turns out chrome.identity.getAuthToken just doesn't work there, and it doesn't fail cleanly either, it opens a broken popup instead of telling you what's wrong. If you're curious about the whole mess, BUILD_NOTES.md has the full story, every wrong turn included.

    Tech stack, if you care

    Manifest V3, plain JavaScript, no framework. I didn't see the point of pulling in React for a popup this small.

    webextension-polyfill, bundled locally so I'm not depending on a CDN being up. Google Sheets API v4 for reading tabs and appending rows. Two separate Google OAuth clients, one for Chrome's native sign in, one for everyone else, because they genuinely require different setups and I learned that the hard way. Destination settings live in chrome.storage.sync so they follow you across your own devices, and your session lives in chrome.storage.local so you're not signing in every time you close the browser.

    sheet-send/
    ├── manifest.chrome.json
    ├── manifest.firefox.json
    ├── src/
    │   ├── background/       # the service worker, handles the right click menu, auth, sheets calls
    │   ├── content/           # the little in-page confirm dialog and toast
    │   ├── popup/              # the popup, this is where most of the actual settings live
    │   ├── options/            # an older settings page, mostly a fallback now
    │   ├── shared/             # storage helpers, constants, the polyfill loader
    │   └── icons/
    ├── PRIVACY_POLICY.md
    ├── VISION.md
    ├── BUILD_NOTES.md
    ├── LICENSE
    └── README.md
    

    Where this is actually headed

    Right click and send is the starting point, not the finish line. What I actually want this to do is sit on a profile page, say Instagram, LinkedIn, TikTok, and let me click one button to capture the profile straight into a sheet, no highlighting text at all. If you're doing any kind of outbound work, hunting leads, that's the whole point, cut out the manual data entry entirely.

    Along the way I also want custom column names instead of the fixed set there is now, and I want different sites to route to different sheets automatically, so Instagram leads go one place and TikTok leads go somewhere else without me choosing every time.

    There's also a smaller idea sitting alongside all this, a save link feature, basically my own tiny version of Instapaper, for building a reading list without leaving the page.

    All of it is written out properly in VISION.md if you want the longer version.

    Setting it up yourself

    1. Clone this repo.

    2. Spin up a Google Cloud project and turn on the Google Sheets API.

    3. Set up the OAuth consent screen, External type, add the spreadsheets scope, and add yourself as a test user while it's unpublished.

    4. You need two OAuth clients here, not one. A Chrome Extension type client, its Item ID has to match the extension ID Chrome gives you once you load it unpacked. And a Web application type client, with https://<extension-id>.chromiumapp.org/ registered as both a JavaScript origin and a redirect URI, watch the trailing slash on the redirect URI, it matters more than you'd think.

    5. Drop both client IDs in, one into manifest.chrome.json, the other into src/shared/constants.js.

    6. Load it unpacked, chrome://extensions, developer mode on, Load unpacked, pick the folder.

    7. For Firefox, use manifest.firefox.json through about:debugging, and the same Web application client with a Firefox flavored redirect URI.

    If something breaks along the way and the error looks unfamiliar, check BUILD_NOTES.md first. I hit a lot of these already, extension ID mismatches, a redirect URI missing a trailing slash, an OAuth client using the wrong type entirely, it's all documented there in the order I actually ran into it.

    Here's the privacy policy for the Sheet-Send browser extension.

    Questions or feedback

    Just reach out to me via my contact page, just put the word "Sheet Send" in the subject.

    License

    MIT, see LICENSE.

    Contributing

    Open to issues and pull requests. I kept this deliberately small and dependency light, plain JS instead of a framework, no backend, and I'd like to keep it that way, so contributions that work within that shape are the easiest for me to review and merge.

    Enjoyed this project? Share it!