Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Building a Chrome Extension in 2026

By Kokil Thapa | Last reviewed: September 2026

Building a Chrome Extension in 2026 is a practical way to ship a small product without a full mobile app or SaaS rebuild. You get direct access to the browser tab, bookmarks, and selected text. You also inherit Google's security model, which changed sharply when Manifest V2 support ended. This guide walks through Manifest V3 architecture, file layout, permissions, messaging, testing, and Chrome Web Store submission using patterns that hold up on real client projects.

What does building a Chrome Extension in 2026 require?

Every extension ships as a folder of static files loaded by Chrome. There is no build step unless you add one. The browser reads manifest.json first. That file declares version, permissions, scripts, and UI entry points. Google enforces Manifest V3 for all new listings. Older MV2 extensions were removed from the store in 2024.

If you already ship web applications for clients, an extension often complements the main product. Think form autofill for internal CRMs, price checkers for eCommerce staff, or a sidebar that talks to your Laravel REST API. The stack is HTML, CSS, and JavaScript. Node.js 26 LTS is useful locally for bundling with Vite 8.x, but the runtime in Chrome is plain JS.

Manifest V3 Extension Architecturemanifest.jsonpermissions, MV3Service Workerbackground.jsContent Scriptspage DOM accessPopup UIpopup.htmlWeb Page + Isolated JS Worldschrome.runtime messaging connects all parts
Building a Chrome Extension in 2026: Manifest V3 splits logic across a service worker, content scripts, and optional popup UI.

Start with the smallest useful surface. One content script on one domain beats a broad <all_urls> permission that triggers review delays and user distrust. On production systems I maintain, extensions stay scoped to known hostnames—the same discipline you apply when locking down CORS on an API.

Minimum file structure

Create a project folder with these files. Names are conventional, not mandatory.

  • manifest.json — extension metadata and registration
  • background.js — event-driven service worker
  • content.js — runs inside matched web pages
  • popup.html + popup.js — toolbar popup UI
  • icons/ — 16, 48, and 128 px PNG assets

How do you scaffold a Manifest V3 Chrome extension?

Copy this starter manifest.json. It targets a single host pattern so Chrome prompts for limited access during install.

{
  "manifest_version": 3,
  "name": "Tab Notes",
  "version": "1.0.0",
  "description": "Save notes per tab on example.com",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_title": "Tab Notes"
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://example.com/*"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],
  "permissions": ["storage"],
  "host_permissions": ["https://example.com/*"]
}

The service worker in background.js wakes on events and sleeps when idle. It cannot touch the DOM. Persist state with chrome.storage, not window.localStorage.

chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GET_NOTE') {
    chrome.storage.local.get([message.tabKey], (result) => {
      sendResponse({ note: result[message.tabKey] ?? '' });
    });
    return true;
  }
});

Content scripts share the DOM but not the page's JavaScript variables. They communicate through chrome.runtime.sendMessage. Validate payloads before acting on them. Treat untrusted page data like any external input—a lesson that overlaps with security practices for web developers in 2026.

const tabKey = `note:${location.hostname}:${location.pathname}`;

chrome.runtime.sendMessage({ type: 'GET_NOTE', tabKey }, (response) => {
  if (response?.note) {
    document.title = `[Note] ${document.title}`;
  }
});

Load the extension unpacked: open chrome://extensions, enable Developer mode, click Load unpacked, and select your folder. Reload the extension after every code change. Use the service worker link on the card to inspect background logs.

How does messaging work between extension parts?

Extension components run in separate contexts. The popup closes when the user clicks away. The content script dies on navigation unless you inject again. Only the service worker (briefly) and chrome.storage survive across sessions.

Pick a messaging pattern early. One-way fire-and-forget suits analytics. Request-response needs return true in the listener when you call sendResponse asynchronously. Long-lived ports fit streaming or frequent updates between popup and background.

Extension Message PassingContent Scriptreads DOMService Workerroutes + storageRemote APIfetch()Popup UIuser actionschrome.storage.local persists notessurvives popup close and tab reload
Message flow when building a Chrome Extension in 2026: content scripts and popups never call APIs directly unless declared in host_permissions.

When an extension calls your backend, reuse API conventions from REST API design best practices. Send a static API key only if you accept the leak risk—any user can read extension source from the installed package. Prefer OAuth with chrome.identity.launchWebAuthFlow or short-lived tokens tied to the user's account.

Debug message failures systematically. Open DevTools on the page for content-script logs. Open the service worker inspector for background errors. A silent failure usually means the listener returned before sendResponse ran.

What changed from Manifest V2 to V3?

Manifest V3 is not a rename. It changes where code runs and what network calls look like. Extensions that relied on persistent background pages or blocking webRequest needed rewrites.

AreaManifest V2Manifest V3 (2026)
BackgroundPersistent background pageEphemeral service worker
Remote codeOften tolerated in practiceProhibited—all logic bundled locally
Network blockingwebRequestBlockingdeclarativeNetRequest rules only
Host accessOften bundled in permissionsSplit into host_permissions
Store reviewLighter on broad permissionsStricter justification required

Ad blockers and privacy tools were hit hardest. Most business extensions—sidebar panels, scrapers, form helpers—map cleanly to MV3 if you drop blocking hooks. For declarative rules, start from the declarativeNetRequest API reference and keep rule sets small.

MV2 vs MV3 Background ModelManifest V2 (legacy)Always-on background pagewebRequest blockingHigher memory footprintManifest V3 (2026)Event-driven workerdeclarativeNetRequestStore-required todaymigrate
Manifest V3 replaces persistent backgrounds with service workers—plan state persistence before you port an older extension.

How do you connect a Chrome extension to your web app or API?

Most useful extensions are front ends for data elsewhere. A directory assistant might call the same JSON endpoints as your main site. A legal-tech internal tool might pull matter metadata from a Laravel backend—the same integration mindset as client portal projects where document workflows live behind authenticated APIs.

Authentication options

  1. OAuth 2.0 via chrome.identity — best for multi-user SaaS; users sign in through a familiar browser flow.
  2. Session cookie on allowed domain — content scripts on your own origin can sometimes read session state; fragile if cookies are HttpOnly.
  3. API token pasted in options page — acceptable for internal tools with a tiny user base.

Store secrets in chrome.storage.session when you do not need them after browser restart. Never embed production admin keys in the package. Anyone can unzip a CRX file.

Validate JSON responses before rendering HTML into the popup. Use your JSON formatter tool while prototyping payloads. Test edge cases with the regex tester when parsing page text in content scripts.

If the extension transforms user-selected text—dates, currency, romanized Nepali—keep conversion logic in a shared npm module. Build it with Vite 8.x and import the bundle into both your web app and extension. One source of truth beats duplicated functions across codebases.

CORS and host permissions

host_permissions lets the service worker call listed origins without a browser CORS preflight block from the extension context. Your API still controls authorization. Return 401 for bad tokens. Log abuse server-side the same way you would for mobile clients.

For extensions that interact with third-party sites you do not own, you cannot bypass their CSP or CORS. Read the DOM from content scripts and send sanitized text to your backend instead of injecting remote scripts into the page.

How do you test, package, and publish to the Chrome Web Store?

Testing starts local and stays local longer than you expect. Write a manual checklist: install fresh, upgrade from prior version, exercise every permission string, and confirm uninstall removes storage if you promise that in the privacy policy.

Extension Publish PipelineScaffoldUnpackedTestZip CRXChrome Web Storereview + publishPre-submit checklistprivacy policy URLpermission justificationscreenshots 1280x800
From unpacked testing to store review: building a Chrome Extension in 2026 ends with a ZIP upload and clear permission explanations.

Automate packaging with a simple script. Zip the folder excluding .git, node_modules, and source maps you do not want public.

#!/bin/bash
cd extension-src
zip -r ../tab-notes-v1.0.0.zip . \
  -x "*.git*" -x "node_modules/*" -x "*.map"

Register a Chrome Web Store developer account (one-time fee, currently USD 5). Upload the ZIP in the developer dashboard. Fill listing copy, screenshots, and a privacy policy URL. Google reviews most extensions within a few days. Rejections often cite broad permissions or missing disclosure for data collection.

Declare single purpose clearly in the listing. If your extension saves notes, do not also request tabs, history, and clipboard unless each permission supports that one purpose. Tie features to strings in plain language users see at install time.

Common rejection reasons

  • Remote code execution or obfuscated scripts that hide behaviour
  • Permissions wider than the described feature set
  • Missing privacy policy when using storage or identity APIs
  • Deceptive functionality or unrelated keyword stuffing in metadata

After approval, version bumps go through the same dashboard. Increment version in manifest.json. Use semantic versioning so enterprise users can track change logs.

What tooling speeds up extension development in 2026?

Plain folders work for small extensions. Larger projects benefit from TypeScript, ESLint, and a bundler. Vite 8.x with the CRX plugin can emit a loadable dist/ folder on each save. Keep "type": "module" in manifest when using ES modules in the service worker.

Hot reload is imperfect. Expect to click reload on chrome://extensions after background changes. Content scripts often need a full page refresh on the target site.

For UI-heavy popups, plain Bootstrap 5 markup matches what many Laravel and WordPress teams already ship. Skip React unless the popup is genuinely complex. Bundle size affects review perception and install size alike.

If the extension is one channel in a broader product, plan it during discovery—the same phase as planning and research for software projects. A week of scoping permissions and API contracts saves a month of store resubmissions.

Internal extensions for a company can skip the public store. Policy-install through Google Workspace or distribute the CRX with documented manual steps. Public consumer tools should almost always use the store for auto-updates and trust signals.

Key Takeaways

  • Building a Chrome Extension in 2026 means Manifest V3 only: service worker, declared permissions, no remotely hosted logic.
  • Keep host permissions narrow; justify every string in the store listing and in your privacy policy.
  • Use chrome.storage and message passing—popups and content scripts are ephemeral by design.
  • Authenticate through OAuth or server-issued tokens; never ship production secrets inside the package.
  • Test unpacked early, zip without dev artefacts, and expect store review to scrutinize broad access.
  • Share business logic with your web app via a bundled module to avoid drift between extension and backend.

People Also Ask

Do I need to pay to publish a Chrome extension?

Google charges a one-time Chrome Web Store developer registration fee (USD 5 as of 2026). There is no per-download fee. Budget a few hours for listing assets, privacy policy hosting, and possible resubmission if reviewers flag permissions.

Can Chrome extensions use Manifest V2 in 2026?

No for new public listings. Manifest V2 is deprecated and removed from the Chrome Web Store. Enterprise policies may temporarily sideload older builds, but all new development should target Manifest V3 APIs documented on MDN Web Extensions.

What is the difference between a content script and a service worker?

A content script shares the DOM of a matched page but runs in an isolated JavaScript world. A service worker handles events, network calls, and storage without DOM access. They communicate through chrome.runtime messaging APIs.

How long does Chrome Web Store review take?

Many submissions resolve within one to three business days. Complex permissions, identity scopes, or data-handling declarations can extend review. Fix rejection notes precisely instead of resubmitting unchanged builds.

Ship your extension with a clear scope

Building a Chrome Extension in 2026 rewards small scope and strict permissions more than feature sprawl. Start with one hostname, one user flow, and a service worker that does one job well. Wire it to a backend you already trust, test unpacked until messaging and auth are boring, then publish with honest copy.

If you want an extension paired with a Laravel API, eCommerce workflow, or internal automation tool, see custom software development services or browse the directory platform portfolio work for examples of multi-surface products. For AI-assisted workflows inside the browser, AI integration and automation may fit your roadmap. Ready to scope a build? Contact us with your target site, permissions list, and API docs.

Frequently Asked Questions

Every extension ships as a folder of static files loaded by Chrome, with manifest.json read first. Google enforces Manifest V3 for all new listings. You declare permissions narrowly, split logic across a service worker, content scripts, and optional popup UI, then test unpacked before publishing through the Chrome Web Store developer dashboard.

Yes. Google charges a one-time Chrome Web Store developer registration fee of USD 5. There is no per-download fee.

No. Manifest V2 is deprecated and removed from the Chrome Web Store. All new development should target Manifest V3.

A content script shares the DOM of a matched page but runs in an isolated JavaScript world separate from the page's own variables. A service worker handles events, network calls, and storage without any DOM access. They wake on different triggers and communicate through chrome.runtime messaging APIs such as sendMessage and onMessage listeners.

Many submissions resolve within one to three business days. Complex permissions or identity scopes can extend review.

Start with a project folder containing manifest.json for metadata and registration, background.js as the event-driven service worker, content.js for matched web pages, popup.html plus popup.js for toolbar UI, and an icons folder with 16, 48, and 128 pixel PNG assets. Names are conventional rather than mandatory, but this layout matches what Chrome expects when you load the folder unpacked at chrome://extensions.

Write manifest.json with manifest_version 3, name, version, icons, action popup, background service_worker with type module, content_scripts with matches and run_at document_idle, permissions, and host_permissions scoped to one hostname. Add background.js with onInstalled and onMessage listeners using chrome.storage.local. Add content.js that sends typed messages to the background. Load unpacked in Developer mode and reload after every code change.

MV3 is not a rename. Persistent background pages became ephemeral service workers. Remote code is prohibited and all logic must be bundled locally. Blocking webRequest was replaced by declarativeNetRequest rules only. Host access moved from bundled permissions into separate host_permissions. Store review now demands stricter justification for broad access. Extensions relying on blocking hooks or always-on backgrounds needed rewrites; most business sidebar and form-helper tools map cleanly once those patterns are dropped.

Popup, content script, and service worker run in separate contexts. Popups close when the user clicks away; content scripts die on navigation unless reinjected. Use fire-and-forget sendMessage for one-way updates. Request-response patterns require return true in the listener when sendResponse runs asynchronously. Long-lived ports suit streaming or frequent popup-to-background updates. Debug content scripts in page DevTools and the service worker through its inspector link on chrome://extensions.

Declare target origins in host_permissions so the service worker can call your API without browser CORS blocking from the extension context. Your API still controls authorization and should return 401 for bad tokens. Prefer OAuth 2.0 via chrome.identity.launchWebAuthFlow or short-lived server-issued tokens over pasted API keys. Never embed production admin keys in the package because anyone can unzip the CRX. Validate JSON responses before rendering HTML in the popup, matching REST conventions you already use on the main site.

OAuth 2.0 through chrome.identity is best for multi-user SaaS because users sign in through a familiar browser flow. Session cookies on your own domain can work from content scripts but break easily when cookies are HttpOnly. An API token pasted on an options page is acceptable for tiny internal tools; store secrets in chrome.storage.session when they should not survive browser restart. Treat any static key in source as publicly readable and log abuse server-side the same way you would for mobile clients.

Open chrome://extensions, enable Developer mode, click Load unpacked, and select your project folder. Reload the extension after every code change and use the service worker link on the card for background logs. Write a manual checklist covering fresh install, upgrade from a prior version, every permission string, and uninstall behaviour if your privacy policy promises storage removal. Content scripts often need a full page refresh on the target site after updates because hot reload is imperfect for background changes.

Zip the extension folder excluding .git, node_modules, and source maps you do not want public. Register a Chrome Web Store developer account for the one-time USD 5 fee, upload the ZIP in the developer dashboard, and supply listing copy, screenshots, and a privacy policy URL. Increment version in manifest.json using semantic versioning for each release. Google reviews most extensions within a few days; after approval, version bumps follow the same upload path through the dashboard.

Reviewers frequently reject extensions for remote code execution or obfuscated scripts that hide behaviour, permissions wider than the described feature set, missing privacy policy when using storage or identity APIs, and deceptive functionality or unrelated keyword stuffing in metadata. Tie every permission string to a single declared purpose in plain language users see at install time. If your extension saves notes, do not also request tabs, history, and clipboard unless each permission directly supports that one purpose. Fix rejection notes precisely instead of resubmitting unchanged builds.

Plain folders suffice for small extensions. Larger projects benefit from TypeScript, ESLint, and a bundler. Vite 8.x with the CRX plugin can emit a loadable dist folder on each save; keep type module in manifest when using ES modules in the service worker. Node.js 26 LTS is useful locally for bundling, though Chrome runs plain JavaScript at runtime. For UI-heavy popups, Bootstrap 5 markup matches what many Laravel and WordPress teams already ship. Share business logic with your web app via a bundled npm module imported into both codebases to avoid drift.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: