An extension idea is the cheapest kind of software to start and the easiest to abandon: a weekend of manifest and content script, then a Web Store review queue, then silence. Collecting emails first tells you whether anyone wants it before you spend the weekend.
The catch is that the obvious place for a signup form, the extension popup, does not exist yet, and when it does, it will not run the embed script. Manifest V3 changed what an extension page is allowed to load.
Host the waitlist on a plain landing page with the OperatorStack script tag and a data-os-widget="waitlist" div, and send every link there. Do not put os.js in a popup: Manifest V3 blocks remote scripts in extension pages. If you later call the signup endpoint from the extension itself, expect a 403 unless the project URL check is satisfied.
Why the Popup Is the Wrong Place Before Launch
Two reasons, one practical and one technical.
Nobody can open a popup for an extension they have not installed. Before launch your audience arrives from a tweet, a Reddit post, or a Product Hunt teaser. They land on a web page, not on your toolbar icon.
Manifest V3 extension pages cannot load remote code. The default policy for extension pages is script-src 'self', which means only scripts bundled in the extension package run. This line in a popup fails with a CSP error in the console:
<script src="https://operatorstack.dev/os.js" data-project="pk_abc123"></script>
You cannot loosen it for remote hosts in MV3. The embed is built to be loaded from a URL, so it belongs on a web page.
Step 1: Build the Landing Page
Create an OperatorStack project and set its URL to the domain the landing page will live on. Copy your snippet.
Make a single page (plain HTML, Astro, anything) with a headline that says what the extension does on which site. "Hides sponsored posts on LinkedIn" beats "A better LinkedIn". Add the script tag and the widget div:
<script src="https://operatorstack.dev/os.js" data-project="pk_abc123"></script>
<div data-os-widget="waitlist"></div>
That is the whole install. Every signup gets a referral code, and the page also records views and sources through cookie-free analytics. The generic setup is covered in How to Set Up a Waitlist.
Show the extension working. A 10 second GIF of the before and after on the real site converts better than any paragraph, because the pitch for an extension is always "this specific annoying thing, gone".
Step 2: Track Where Signups Come From
Extension audiences cluster by community: a subreddit, a Chrome-focused newsletter, a niche forum. Tag each link before you post it:
https://yourdomain.com/?utm_source=reddit&utm_campaign=r-productivity
https://yourdomain.com/?utm_source=twitter&utm_campaign=launch-thread
The source is stored with each signup, so after a week you can see which community produced emails, not just visits. See Track Referral Sources for reading the breakdown.
Step 3: Add Referral Sharing for the Waitlist
Extension users are good at word of mouth because they recommend tools to colleagues who share the same browser pain. Use the share links on a custom thank-you state:
const { referral_code } = await OperatorStack.joinWaitlist({ email });
const links = OperatorStack.getShareLinks(referral_code, {
text: "Waiting on this Chrome extension",
});
// links: { twitter, linkedin, email, whatsapp, copy }
joinWaitlist() resolves with { success, referral_code, referral_link }. Run this on the landing page, not inside the extension.
After You Ship: Calling the Waitlist From the Extension
Once the extension exists you may want an in-popup prompt such as "Join the Pro waitlist". There are two ways, and one has a trap.
Recommended: open the landing page. A button that calls chrome.tabs.create({ url: "https://yourdomain.com/?utm_source=extension" }) reuses everything above and tags those signups as coming from inside the extension.
Direct fetch from the service worker. The public signup endpoint is POST /v1/projects/{projectKey}/waitlist/signup with email, and optional name, ref, and visitor_id. Add the API host to host_permissions in your manifest and call it with fetch. The trap:
The endpoint checks the request Origin against the URL saved on your project. A request made from an extension carries a chrome-extension://<id> origin, which does not equal your https:// project URL, so you get 403 Origin not allowed. The check is skipped when the project has no URL set, which is also why it works from localhost while you test and fails after you add the URL. Prefer the landing page route rather than clearing the URL.
Common Mistakes
- Putting the form in the popup before launch. Only installed users can see it, so you collect nothing.
- Loading
os.jsin an extension page. MV3 blocks it. Use a web page. - Testing the fetch only on localhost. Localhost origins pass the check, production extension origins do not.
- A landing page with no sources. Untagged links mean you cannot tell which community to post in again.
- Waiting for Web Store approval to start. Review takes days. Collect signups during it.
Frequently Asked Questions
Can I load os.js inside my Chrome extension popup?
No. Manifest V3 extension pages enforce a content security policy that only allows scripts packaged inside the extension, so a script tag pointing at a remote URL is blocked. Put the waitlist on a normal web page, or call the signup endpoint with fetch from the popup.
Do I need a published extension to run a waitlist?
No, and that is the point. The waitlist lives on a landing page. You can collect signups for weeks before you have a manifest.json, and email everyone when the Chrome Web Store listing goes live.
Why does my extension get a 403 when it posts to the waitlist endpoint?
The public endpoint compares the request Origin header with the URL saved on your OperatorStack project. A request from an extension carries a chrome-extension:// origin, which never matches an https project URL, so it is rejected with Origin not allowed.
Should the waitlist form be in the extension or on a website?
On a website, before launch. People who have not installed anything cannot open your popup. After launch, an in-extension prompt can point installed users at the same page.
How do I tell which signups came from the Chrome Web Store listing?
Put a UTM on the link in your listing, for example ?utm_source=cws, and the landing page records it with the signup. Read it back in the source breakdown on the dashboard.