splitforms.com
← Back to the journal

Add a Contact Form to a Chrome Extension (2026)

Ship a feedback or contact form inside a Chrome extension popup: Manifest V3 CSP rules, fetch from the popup, and a no-server endpoint for submissions.

Start free — 500 submissions/moSee pricing →No credit card. Paid plans from $5/mo.

Why a Chrome extension still needs a contact form

A Chrome extension doesn't have a server of its own. manifest.json describes a popup and maybe a background service worker, but there's nowhere inside the extension for a <form>to submit to — no PHP, no Node process, nothing listening on a port. That's fine until something breaks or a user wants a feature you didn't build.

Without a feedback channel, that user doesn't file a GitHub issue — they just uninstall, one more quiet loss with no explanation. A hosted free form submission API closes that gap with zero backend: the popup collects a message, fetch() POSTs it out, and the submission lands where you can read it — a complete Chrome extension feedback form in three small files.

manifest.json: the Manifest V3 config

The manifest wires the popup up, and one key does the real work here — action.default_popup:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A short description of what this extension does.",
  "action": {
    "default_popup": "popup.html",
    "default_title": "Send feedback"
  }
}

That's a complete, valid manifest for an extension whose only UI is this popup — no permissions array, no background service worker required. Between these three files, that's the entire Manifest V3 form submission setup; the next section covers when you'd add host_permissions back in.

Manifest V3 gotchas: CSP, remote scripts, and CORS

Every extension page — popup.html included — ships under Manifest V3's default content-security-policy: script-src 'self'; object-src 'self';. In practice that blocks three things people trip over:

  • Inline <script> tags written directly in popup.html
  • Inline event-handler attributes, like onclick="..."
  • A <script src> pointing at a remote host — a CDN, a CMS, anything not packaged inside the extension

That's why the handler lives entirely in popup.js, loaded with a plain <script src="popup.js"> tag instead of sitting inline — not a splitforms-specific restriction, just how every Manifest V3 extension works.

That rule blocks loading script code, not fetch() calls — a fetch() to a remote API from popup.js isn't a "remote script." Whether it succeeds is a CORS question, not CSP: splitforms' contact form API accepts posts from any origin, so the plain fetch() POST above needs no extra config. Pointed at a stricter endpoint, or want the host declared explicitly? Add it under host_permissions:

{
  "host_permissions": ["https://splitforms.com/*"]
}

That grants elevated fetch access to the host — Chrome's standard way of letting an extension bypass CORS. Add it if you see a CORS error; it isn't required otherwise.

Manifest V3 specifics move over time — confirm current CSP and host_permissions behavior in Chrome's own docs before publishing.

Useful hidden fields: version, locale, and context

A popup already knows things a normal contact form doesn't, and it's worth passing a few along as ordinary FormData fields:

  • chrome.runtime.getManifest().version — the exact build a bug report came from
  • navigator.language — useful for triaging non-English feedback
  • A fixed context field, like "chrome-extension-popup" — separates this channel from a website form in the same dashboard

Stop there without asking first. It's tempting to also grab the current tab's URL with chrome.tabs.query— resist it unless you have explicit consent. A feedback form isn't the place to quietly collect browsing data.

Success and error states inside a tiny popup

A popup is small and fragile — typically 300-400px wide, and it closes the instant a user clicks outside it. That rules out the redirect pattern some contact forms use: there's no page to navigate to, and no guarantee the popup survives long enough to show one anyway. Everything happens inline, without leaving popup.html.

The fetch/JSON flow in popup.js above already fits that constraint — the same pending-then-inline-update pattern covered in more depth in splitforms' AJAX contact form guide: disable the button, show a sending state, swap in a thank-you message on response.ok, show a plain error on failure. No redirect field, no navigation.

One habit worth avoiding: don't call window.close() right after a successful submission. It erases the confirmation before most people register it — leave the message in place and let the user close the popup themselves.

Where submissions go: dashboard and email

A submission that hits https://splitforms.com/api/submit from a popup lands in the same place one from a website form would: the splitforms dashboard, plus an email notification if you've turned one on. Email notificationsare free on every plan, including free, so a side-project extension doesn't need to pay just to get pinged about a bug.

The free plan covers 500 submissions a month across unlimited forms — plenty for a feedback popup. Pro is $5/month, Pro is $5/month, and a 3-Year plan runs $59 if the extension grows into real volume.

Publishing notes: the Chrome Web Store privacy disclosure

One remaining step has nothing to do with code. If the form collects anything that identifies a person — an email address, here — the Chrome Web Store dashboard requires you to declare that under the listing's privacy practices: what you collect, why, and a link to a privacy policy. That applies no matter where the data ends up.

Skip it and an update can bounce in review for an undisclosed data-collection practice — slower than filling out the disclosure correctly the first time.

FAQ

Does a Chrome extension need its own backend to have a contact form?

No. A Chrome extension can't run server code, but its popup can still submit a form to any endpoint on the web. Point popup.js's fetch call at a hosted backend like splitforms (https://splitforms.com/api/submit) with your access key, and the extension has a working feedback channel with no server to run.

Why can't I put an onclick handler or inline <script> in popup.html?

Manifest V3's default CSP (script-src 'self') blocks inline <script> tags and inline event handlers on every extension page, popup.html included. Put the logic in a separate popup.js file and attach it with addEventListener instead.

Do I need host_permissions in manifest.json to submit the form?

Not necessarily. If the endpoint's CORS policy is open, as splitforms' submit endpoint is, a plain fetch() POST from the popup works with no host_permissions entry. Add it for the endpoint's domain only if you hit a CORS error, or want the host declared explicitly.

Does this work in Firefox, or is it Chrome-only?

The pattern is browser-agnostic. Firefox's WebExtensions support both Manifest V2 and V3 and kept V2 around longer than Chrome did, but popup.html/popup.js/fetch() doesn't depend on which one you target — fetch is standard, and Firefox's CSP blocks inline scripts the same way. Manifest keys differ slightly (action vs. browser_action); the form logic carries over unchanged.

Can the background service worker submit the form instead of the popup?

Yes — useful if a submission needs to survive the popup closing, since popup JavaScript is torn down the instant it loses focus, which can abort an in-flight fetch. Routing through the service worker instead — the popup messages it with chrome.runtime.sendMessage, and it calls fetch — keeps the request alive independent of the popup.

What happens to hidden fields like extension version and locale?

They arrive as ordinary form fields, no different from name or email — visible in the splitforms dashboard and any email notification alongside the rest of the submission. Nothing extension-specific happens to them on the backend; they're just extra context you chose to send.

Do I need a Chrome Web Store privacy disclosure for a feedback form?

If the form collects anything identifying — an email address is the common case — yes. The Chrome Web Store dashboard requires a privacy-practices disclosure describing what you collect and why, plus a link to a privacy policy, before an update clears review.

Will visitors hit a CAPTCHA submitting from the popup?

Not by default. splitforms' spam stack — honeypot field, time-trap, and IP-based rate limiting — runs server-side with no visible challenge, which matters more in a 300px popup than a full page. Add a visible CAPTCHA only if measured spam volume actually warrants it.

Building the popup now? Read the docs to grab a free access key — splitforms is free for 500 submissions a month, with honeypot, time-trap, and rate-limit spam protection on by default.

Related articles

More practical guidance from tutorials.

Browse the journal →
Tutorials

FormData in JavaScript: Submit Forms with fetch (2026 Guide)

The complete FormData guide: reading form fields, appending files, sending multipart and url

9 min readRead →
Tutorials

Custom Auto-Responder Emails for HTML Forms (HTML + CSS, 2026)

Design fully branded auto-responder emails for your HTML forms: paste your own HTML/CSS temp

9 min readRead →
Tutorials

Send Form Emails From Your Own Domain (Custom SMTP, 2026)

Send form notification and auto-responder emails from your own domain with custom SMTP: encr

10 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewWhat Is a Form Backend? (Complete Guide)Start here →GuideSelf-Hosted vs SaaS Form BackendRead →GuideForm Backend vs Form BuilderRead →GuideWhere Do Form Submissions Go?Read →ReferenceForm backend guideOpen →

Building forms with ChatGPT, Claude, Cursor, or v0? Connect the native MCP server and give your agent a production form backend.

Explore the MCP server →

Give your form a production backend.

One endpoint adds delivery, spam filtering, storage, and integrations. Start with 500 submissions a month for free.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe