The Ultimate Chrome Extension Development Guide (2026) — From Zero to Published

17 min read

The Ultimate Chrome Extension Development Guide (2026) — From Zero to Published

Chrome extensions are powerful tools that can transform the browsing experience for millions of users. Whether you want to build a productivity booster, a developer tool, or a business application, this comprehensive guide will take you through the entire journey of Chrome extension development — from your first line of code to publishing on the Chrome Web Store.

This guide covers Manifest V3, the current standard for Chrome extensions, along with all the essential components you’ll need to create professional, production-ready extensions.


Prerequisites

Before diving into Chrome extension development, ensure you have the following tools and knowledge:

Essential Tools

  1. Google Chrome Browser — Your primary testing environment
  2. Code Editor — VS Code is recommended with the Chrome Extension Manager extension
  3. Node.js & npm — For package management and build tooling (optional but recommended)
  4. Git — For version control

Knowledge Requirements

If you’re new to JavaScript, we recommend completing a JavaScript basics course before proceeding.

Development Environment Setup

Setting up a proper development environment accelerates your workflow significantly. We recommend using VS Code with these extensions:

Consider using TypeScript for larger projects to benefit from type safety and better IDE support. Many modern extensions are built with TypeScript to catch errors at compile time rather than runtime.


Project Structure

A well-organized project structure is crucial for maintainable extensions. Here’s the recommended layout:

my-extension/
├── manifest.json          # Extension manifest (required)
├── background/
│   └── service-worker.js  # Background service worker
├── content/
│   └── content.js         # Content scripts
├── popup/
│   ├── popup.html         # Popup UI
│   ├── popup.css          # Popup styles
│   └── popup.js           # Popup logic
├── options/
│   ├── options.html       # Options page
│   └── options.js         # Options logic
├── images/
│   ├── icon-16.png        # Extension icons
│   ├── icon-48.png
│   └── icon-128.png
└── README.md              # Documentation

This structure separates concerns and makes it easy to scale your extension as it grows. For more details on extension architecture patterns, see our Architecture Patterns Guide.


Manifest V3

The manifest.json is the heart of every Chrome extension. It defines permissions, entry points, and extension metadata.

Basic Manifest V3 Template

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A brief description of what your extension does.",
  "permissions": [
    "storage",
    "activeTab"
  ],
  "host_permissions": [
    "https://*.example.com/*"
  ],
  "background": {
    "service_worker": "background/service-worker.js",
    "type": "module"
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "images/icon-16.png",
      "48": "images/icon-48.png",
      "128": "images/icon-128.png"
    }
  },
  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["content/content.js"]
    }
  ],
  "icons": {
    "16": "images/icon-16.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  }
}

Key Manifest V3 Changes from V2

For a complete migration guide from Manifest V2 to V3, check out our MV3 Migration Guide.


Content Scripts

Content scripts are JavaScript files that run in the context of web pages. They can read and modify the DOM, communicate with the extension, and respond to page events.

Injecting a Content Script

Content scripts are declared in the manifest under the content_scripts key:

// content/content.js
// This runs on every matching page

// Access the page's DOM
const heading = document.querySelector('h1');
if (heading) {
  console.log('Page title:', heading.textContent);
}

// Listen for page events
document.addEventListener('DOMContentLoaded', () => {
  console.log('Page loaded!');
});

// Communicate with the extension
chrome.runtime.sendMessage({ type: 'PAGE_LOADED' });

Best Practices for Content Scripts

  1. Use run_at strategically — Choose document_start for early modifications or document_idle for guaranteed DOM availability
  2. Avoid memory leaks — Clean up event listeners and intervals when the page unloads
  3. Scope your matches — Be specific with URL patterns to avoid unnecessary injection
  4. Isolate from page JavaScript — Content scripts run in an isolated world but share the DOM

For advanced content script patterns, see our Content Scripts Deep Dive.


Background Workers

In Manifest V3, background service workers handle events, manage state, and coordinate between different parts of your extension. Unlike the old background pages, service workers are ephemeral and wake up only when events occur.

Setting Up a Service Worker

// background/service-worker.js

// Listen for installation
chrome.runtime.onInstalled.addListener((details) => {
  console.log('Extension installed:', details.reason);
  
  // Initialize storage
  if (details.reason === 'install') {
    chrome.storage.local.set({ settings: {} });
  }
});

// Handle messages from content scripts or popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GET_DATA') {
    // Async operation - return true to indicate response will be async
    chrome.storage.local.get('data', (result) => {
      sendResponse({ data: result.data });
    });
    return true;
  }
});

// Handle alarms (useful for scheduled tasks)
chrome.alarms.create('myAlarm', { delayInMinutes: 5 });
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'myAlarm') {
    console.log('Alarm triggered!');
  }
});

Service Worker Lifecycle

  1. Install — Set up initial state, preload resources
  2. Activate — Clean up old caches, claim tabs
  3. Idle — Unloaded when not handling events
  4. Wake — Reactivated when events fire

For more patterns, see our Background Patterns Guide.


The popup is the small window that appears when users click your extension icon. It’s perfect for quick actions and settings.

Creating a Popup

<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>My Extension</h1>
    <div class="status" id="status">Loading...</div>
    <button id="actionBtn">Do Something</button>
  </div>
  <script src="popup.js"></script>
</body>
</html>
// popup/popup.js

document.addEventListener('DOMContentLoaded', () => {
  const statusEl = document.getElementById('status');
  const btn = document.getElementById('actionBtn');
  
  // Load saved state
  chrome.storage.local.get('status', (result) => {
    statusEl.textContent = result.status || 'Ready';
  });
  
  // Handle button click
  btn.addEventListener('click', () => {
    chrome.storage.local.set({ status: 'Action performed!' });
    statusEl.textContent = 'Action performed!';
  });
});

For design patterns and examples, see our Extension Popup Design Guide.


Storage

Chrome provides robust storage APIs for persisting data across sessions. There are several storage types:

Storage Types

Type Capacity Sync Use Case
local 5MB No User preferences, cache
sync 100KB Yes Cross-device settings
managed 1MB No Admin-controlled settings
session 5MB No Tab-scoped data

Using the Storage API

// Save data
await chrome.storage.local.set({
  user: { name: 'John', theme: 'dark' },
  count: 42
});

// Read data
const { user, count } = await chrome.storage.local.get(['user', 'count']);
console.log(user.name, count);

// Listen for changes
chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'local' && changes.user) {
    console.log('User changed:', changes.user.newValue);
  }
});

For more advanced caching strategies, see our Caching Strategies Guide.


Messaging

Message passing allows communication between different parts of your extension — popup to background, content script to popup, or even between extensions.

Simple Message Passing

// From content script to background
chrome.runtime.sendMessage({ 
  type: 'FETCH_DATA', 
  url: 'https://api.example.com/data' 
}, (response) => {
  console.log('Response:', response.data);
});

// In background service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'FETCH_DATA') {
    fetch(message.url)
      .then(res => res.json())
      .then(data => sendResponse({ data }))
      .catch(err => sendResponse({ error: err.message }));
    return true; // Keep message channel open for async response
  }
});

Long-Lived Connections

For ongoing communication, use MessageChannel:

// Create a persistent connection
const port = chrome.runtime.connect({ name: 'my-channel' });

port.onMessage.addListener((message) => {
  console.log('Received:', message);
});

port.postMessage({ message: 'Hello from popup!' });

Testing

Proper testing is essential for reliable extensions. Here’s how to approach it:

Loading Your Extension

  1. Open chrome://extensions/
  2. Enable “Developer mode” (top right)
  3. Click “Load unpacked” and select your extension folder
  4. Test in both development and production modes

Debugging Tips

Automated Testing

For larger extensions, consider:

See our Testing Extensions Guide for detailed strategies.


Options Page

While popups are great for quick actions, you’ll often need a dedicated settings page for complex configurations. The options page provides a full-featured interface for user preferences.

Creating an Options Page

<!-- options/options.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Extension Options</title>
  <style>
    body { padding: 20px; font-family: system-ui; }
    .setting { margin-bottom: 15px; }
    label { display: block; margin-bottom: 5px; font-weight: bold; }
    input[type="text"], select { width: 100%; padding: 8px; }
    button { padding: 10px 20px; background: #4CAF50; color: white; border: none; cursor: pointer; }
    .status { margin-top: 10px; color: green; }
  </style>
</head>
<body>
  <h1>Settings</h1>
  
  <div class="setting">
    <label for="apiKey">API Key</label>
    <input type="text" id="apiKey" placeholder="Enter your API key">
  </div>
  
  <div class="setting">
    <label for="theme">Theme</label>
    <select id="theme">
      <option value="light">Light</option>
      <option value="dark">Dark</option>
      <option value="system">System</option>
    </select>
  </div>
  
  <button id="saveBtn">Save Settings</button>
  <div class="status" id="status"></div>
  
  <script src="options.js"></script>
</body>
</html>
// options/options.js

document.addEventListener('DOMContentLoaded', () => {
  // Load saved settings
  chrome.storage.sync.get(['apiKey', 'theme'], (settings) => {
    if (settings.apiKey) document.getElementById('apiKey').value = settings.apiKey;
    if (settings.theme) document.getElementById('theme').value = settings.theme;
  });
  
  // Save settings
  document.getElementById('saveBtn').addEventListener('click', () => {
    const settings = {
      apiKey: document.getElementById('apiKey').value,
      theme: document.getElementById('theme').value
    };
    
    chrome.storage.sync.set(settings, () => {
      document.getElementById('status').textContent = 'Settings saved!';
      setTimeout(() => {
        document.getElementById('status').textContent = '';
      }, 2000);
    });
  });
});

Registering the Options Page

Add this to your manifest.json:

{
  "options_page": "options/options.html",
  "action": {
    "default_options_page": "options/options.html"
  }
}

For more design patterns, see our Options Page Design Guide.


Side Panel (New in MV3)

The side panel API is a relatively new addition to Chrome that provides a persistent UI alongside the browser’s main content area. Unlike popups, side panels remain open, making them ideal for productivity tools, note-taking apps, and reading assistants.

Enabling Side Panel

{
  "name": "My Side Panel Extension",
  "side_panel": {
    "default_path": "sidepanel/sidepanel.html"
  },
  "permissions": ["sidePanel"]
}
// Open side panel on button click
chrome.action.onClicked.addListener(async (tab) => {
  await chrome.sidePanel.setOptions({ tabId: tab.id, path: 'sidepanel/sidepanel.html' });
  await chrome.sidePanel.open({ tabId: tab.id });
});

Permissions and Security

Understanding permissions is crucial for both functionality and user trust. Manifest V3 has stricter requirements than V2.

Permission Types

Permission Description Trust Level
activeTab Access current tab when clicked Low (user-initiated)
storage Store local data Low
tabs Access tab information Medium
cookies Read/write cookies High
history Browser history access High
webRequest Network request interception High

Best Practices for Permissions

  1. Request the minimum — Only ask for permissions your extension actually needs
  2. Use activeTab — Prefer this over tabs for most use cases
  3. Declare host permissions explicitly — Required in Manifest V3
  4. Explain permissions — In your store listing, explain why you need each permission

See our Permissions Guide for detailed information.


Publishing

Once your extension is ready, it’s time to share it with the world through the Chrome Web Store.

Publishing Checklist

  1. Complete your manifest — Add a detailed description, screenshots, and icons
  2. Set a clear privacy practice — Answer all privacy questions honestly
  3. Create store assets:
    • 128x128 promotional icon
    • 440x280 tile images
    • Screenshots (1280x800 or 640x400)
  4. Verify your developer account — Requires a $5 one-time fee

Publishing via CLI

You can automate publishing with the Chrome Web Store API:

# Package your extension
zip -r my-extension.zip manifest.json background/ content/ popup/ images/

# Upload via Chrome Web Store Developer Dashboard
# Or use the publish npm package
npx chrome-webstore-upload publish --extension-id YOUR_ID

Maintaining Your Extension

After publishing:


Conclusion

Building Chrome extensions is an exciting journey that combines web technologies with unique browser capabilities. This guide covered the essential components you need to create professional extensions:

Now you’re ready to start building! Explore our detailed guides on each topic to deepen your knowledge and create amazing extensions.


Additional Resources

No previous article
No next article