Chrome Extension File Structure: A Developer's Complete Guide (2025)

Master the Chrome extension file structure with this comprehensive guide. Learn manifest.json configuration, icon sizes, folder organization, and debugging tips for Manifest V3.

11 min readBy Imtinan
Featured image for Chrome Extension File Structure: A Developer's Complete Guide (2025) - Comparison between manual scraping and AI-powered Chrome extension review analysis

Building a Chrome extension? Before you write a single line of code, you need to understand how Chrome extensions are structured.

I've seen developers waste hours debugging issues that come down to one thing: incorrect file structure. A misplaced manifest.json, wrong icon dimensions, or missing permissions can break your entire extension.

This guide covers everything you need to know about Chrome extension file structure in 2025, including Manifest V3 requirements that are now mandatory.


The Anatomy of a Chrome Extension

Every Chrome extension is essentially a folder containing specific files that Chrome knows how to read. Here's the standard structure:

my-extension/
├── manifest.json          # Required: Extension configuration
├── background.js          # Service worker (Manifest V3)
├── content.js             # Runs on web pages
├── popup/
│   ├── popup.html         # Popup UI
│   ├── popup.js           # Popup logic
│   └── popup.css          # Popup styles
├── options/
│   ├── options.html       # Settings page
│   └── options.js         # Settings logic
├── icons/
│   ├── icon-16.png        # Favicon, toolbar
│   ├── icon-32.png        # Windows computers
│   ├── icon-48.png        # Extensions page
│   └── icon-128.png       # Chrome Web Store
├── assets/
│   └── styles.css         # Shared styles
└── _locales/              # Internationalization
    └── en/
        └── messages.json

Let's break down each component.


The manifest.json File (The Heart of Your Extension)

The manifest.json file is required for every Chrome extension. It tells Chrome everything about your extension: what files to load, what permissions to request, and how to display it.

Minimal Manifest V3 Example

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A helpful Chrome extension",
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png"
    }
  }
}

Complete Manifest V3 Reference

Here's a comprehensive manifest.json with all common fields:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A helpful Chrome extension that does X, Y, and Z",
  
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },
  
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png"
    },
    "default_title": "Click to open"
  },
  
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  
  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["content.js"],
      "css": ["assets/content.css"],
      "run_at": "document_idle"
    }
  ],
  
  "permissions": [
    "storage",
    "activeTab",
    "alarms"
  ],
  
  "host_permissions": [
    "https://*.example.com/*"
  ],
  
  "options_page": "options/options.html",
  
  "web_accessible_resources": [
    {
      "resources": ["assets/*"],
      "matches": ["<all_urls>"]
    }
  ],
  
  "commands": {
    "_execute_action": {
      "suggested_key": {
        "default": "Ctrl+Shift+Y",
        "mac": "Command+Shift+Y"
      },
      "description": "Open the extension popup"
    }
  }
}

Manifest V3 vs V2: Key Differences

| Feature | Manifest V2 (Deprecated) | Manifest V3 (Required) | |---------|--------------------------|------------------------| | Background | background.page or scripts | service_worker | | API | browser_action / page_action | action | | Remote Code | Allowed | Not allowed | | Permissions | Combined | Split into permissions and host_permissions |

Important: As of January 2025, Manifest V2 is fully deprecated. All new extensions must use Manifest V3.


Chrome Extension Icon Sizes (Complete Reference)

One of the most common questions: What icon sizes do I need?

Here's the definitive answer:

Required Icon Sizes

| Size | Purpose | Where It's Used | |------|---------|-----------------| | 16x16 | Toolbar icon, favicon | Browser toolbar, context menus | | 32x32 | Windows computers | Taskbar, high-DPI toolbar | | 48x48 | Extensions page | chrome://extensions | | 128x128 | Chrome Web Store | Installation prompt, store listing |

Icon Best Practices

  1. Use PNG format with transparent backgrounds
  2. Design at 128x128 first, then scale down
  3. Test at small sizes - your icon should be recognizable at 16x16
  4. Avoid thin lines - they disappear at small sizes
  5. Use simple shapes - complex icons look muddy when scaled

Icon Declaration in manifest.json

{
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },
  "action": {
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png"
    }
  }
}

Pro tip: You can also use SVG icons for the toolbar action, which scale perfectly:

{
  "action": {
    "default_icon": "icons/icon.svg"
  }
}

Where Are Chrome Extensions Stored?

Understanding where Chrome stores extensions helps with debugging and development.

Extension Installation Location

Windows:

C:\Users\[Username]\AppData\Local\Google\Chrome\User Data\Default\Extensions\

Mac:

~/Library/Application Support/Google/Chrome/Default/Extensions/

Linux:

~/.config/google-chrome/Default/Extensions/

Extension Folder Structure

Inside the Extensions folder, each extension has its own directory named by its extension ID:

Extensions/
├── abc123def456...    # Extension ID
│   └── 1.0.0_0/       # Version folder
│       ├── manifest.json
│       ├── background.js
│       └── ...
├── xyz789ghi012...    # Another extension
│   └── 2.1.0_0/
│       └── ...

Finding Your Extension ID

  1. Go to chrome://extensions
  2. Enable "Developer mode"
  3. Your extension's ID is shown below the name

The Background Service Worker

In Manifest V3, background scripts are replaced by service workers. Here's what you need to know:

background.js Structure

// background.js - Service Worker

// Runs when extension is installed
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('Extension installed!');
    // Set default settings
    chrome.storage.local.set({ enabled: true });
  }
});

// Listen for messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'getData') {
    // Handle the request
    sendResponse({ data: 'Hello from background!' });
  }
  return true; // Required for async response
});

// Listen for tab updates
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    // Tab finished loading
  }
});

Service Worker Limitations

  1. No DOM access - Service workers can't access document or window
  2. Terminated when idle - Chrome kills inactive service workers
  3. No persistent state - Use chrome.storage instead of variables
  4. No setTimeout for long delays - Use chrome.alarms instead

Keeping State Across Service Worker Restarts

// DON'T DO THIS - Will be lost when service worker restarts
let userData = {};

// DO THIS INSTEAD - Persists across restarts
chrome.storage.local.get(['userData'], (result) => {
  const userData = result.userData || {};
});

Content Scripts

Content scripts run in the context of web pages. They can read and modify the DOM.

content.js Structure

// content.js - Runs on matched pages

// Wait for DOM to be ready
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function init() {
  console.log('Content script loaded!');
  
  // Modify the page
  const element = document.querySelector('#target');
  if (element) {
    element.style.backgroundColor = 'yellow';
  }
  
  // Send message to background script
  chrome.runtime.sendMessage({ type: 'pageLoaded', url: window.location.href });
}

// Listen for messages from background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'highlight') {
    document.body.style.border = '5px solid red';
    sendResponse({ success: true });
  }
});

Content Script Matching Patterns

{
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "exclude_matches": ["https://mail.google.com/*"],
      "js": ["content.js"],
      "css": ["content.css"],
      "run_at": "document_idle"
    }
  ]
}

Match pattern syntax:

  • * = any string
  • <all_urls> = all URLs (use cautiously)
  • https://*.example.com/* = all subdomains of example.com

Popup Files

The popup appears when users click your extension icon.

popup.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>My Extension</h1>
    <button id="actionBtn">Do Something</button>
    <div id="status"></div>
  </div>
  <script src="popup.js"></script>
</body>
</html>

popup.css

/* Set explicit dimensions - popups don't auto-size */
body {
  width: 300px;
  min-height: 200px;
  margin: 0;
  padding: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.container {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

button {
  padding: 10px 16px;
  background: #4285f4;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #3367d6;
}

popup.js

// popup.js

document.addEventListener('DOMContentLoaded', () => {
  const button = document.getElementById('actionBtn');
  const status = document.getElementById('status');
  
  button.addEventListener('click', async () => {
    // Get the active tab
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    
    // Send message to content script
    chrome.tabs.sendMessage(tab.id, { type: 'highlight' }, (response) => {
      if (response?.success) {
        status.textContent = 'Done!';
      }
    });
  });
});

Important: Popups close when they lose focus. Save state to chrome.storage if needed.


Options Page

Options pages let users configure your extension.

Declaring an Options Page

{
  "options_page": "options/options.html",
  // OR for a popup-style options
  "options_ui": {
    "page": "options/options.html",
    "open_in_tab": false
  }
}

options.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Extension Options</title>
  <link rel="stylesheet" href="options.css">
</head>
<body>
  <h1>Settings</h1>
  
  <div class="option">
    <label>
      <input type="checkbox" id="enabled">
      Enable extension
    </label>
  </div>
  
  <div class="option">
    <label for="theme">Theme:</label>
    <select id="theme">
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>
  </div>
  
  <button id="save">Save</button>
  <div id="status"></div>
  
  <script src="options.js"></script>
</body>
</html>

options.js

// Load saved settings
chrome.storage.sync.get(['enabled', 'theme'], (result) => {
  document.getElementById('enabled').checked = result.enabled ?? true;
  document.getElementById('theme').value = result.theme ?? 'light';
});

// Save settings
document.getElementById('save').addEventListener('click', () => {
  const enabled = document.getElementById('enabled').checked;
  const theme = document.getElementById('theme').value;
  
  chrome.storage.sync.set({ enabled, theme }, () => {
    const status = document.getElementById('status');
    status.textContent = 'Settings saved!';
    setTimeout(() => status.textContent = '', 2000);
  });
});

Loading Your Extension for Development

Step 1: Open Extensions Page

Navigate to chrome://extensions in Chrome.

Step 2: Enable Developer Mode

Toggle "Developer mode" in the top-right corner.

Step 3: Load Unpacked

Click "Load unpacked" and select your extension folder.

Step 4: Reload After Changes

After making changes:

  • Click the refresh icon on your extension card
  • Or press Ctrl+R on the extensions page

Loading Extension from ZIP

You can also load extensions from ZIP files during development:

  1. Unzip the extension to a folder
  2. Load unpacked as described above

Note: The Chrome Web Store accepts ZIP files for publishing, but for local development, you need to unzip first.


Debugging Chrome Extensions

Debugging the Popup

  1. Right-click your extension icon
  2. Select "Inspect popup"
  3. DevTools opens for the popup

Debugging the Service Worker

  1. Go to chrome://extensions
  2. Find your extension
  3. Click "Service Worker" link under "Inspect views"

Debugging Content Scripts

  1. Open DevTools on the page (F12)
  2. Go to "Sources" tab
  3. Find your content script under "Content scripts"

Common Debugging Issues

| Issue | Solution | |-------|----------| | Changes not appearing | Reload the extension and refresh the page | | Service worker not running | Check for errors in the service worker console | | Content script not injecting | Verify URL matches in manifest.json | | Permission denied | Add required permissions to manifest.json |


Internationalization (_locales)

Support multiple languages with the _locales folder:

_locales/
├── en/
│   └── messages.json
├── es/
│   └── messages.json
└── fr/
    └── messages.json

messages.json

{
  "extensionName": {
    "message": "My Extension",
    "description": "The name of the extension"
  },
  "extensionDescription": {
    "message": "A helpful Chrome extension",
    "description": "The description of the extension"
  },
  "buttonLabel": {
    "message": "Click me",
    "description": "Label for the main button"
  }
}

Using Translations

In manifest.json:

{
  "name": "__MSG_extensionName__",
  "description": "__MSG_extensionDescription__"
}

In JavaScript:

const label = chrome.i18n.getMessage('buttonLabel');

Frequently Asked Questions

What files are required for a Chrome extension?

Only manifest.json is strictly required. However, most extensions include a popup (popup.html, popup.js), icons, and either a background script or content scripts depending on functionality.

Where does Chrome store installed extensions?

On Windows: C:\Users\[Username]\AppData\Local\Google\Chrome\User Data\Default\Extensions\. On Mac: ~/Library/Application Support/Google/Chrome/Default/Extensions/. On Linux: ~/.config/google-chrome/Default/Extensions/.

What icon sizes do I need for a Chrome extension?

You need 16x16, 32x32, 48x48, and 128x128 pixel icons. The 16x16 is for the toolbar, 48x48 for the extensions page, and 128x128 for the Chrome Web Store listing.

Can I use ES modules in Chrome extensions?

Yes! In Manifest V3, add "type": "module" to your background service worker declaration. For content scripts, you'll need to use web_accessible_resources and dynamic imports.

How do I debug a Chrome extension?

For popups: right-click the extension icon and select "Inspect popup". For service workers: go to chrome://extensions, find your extension, and click the "Service Worker" link. For content scripts: use the regular DevTools on the page.


Conclusion

Understanding Chrome extension file structure is foundational to building reliable, maintainable extensions. Here's the quick checklist:

Essential files:

  • manifest.json (Manifest V3)
  • ✅ Icons in all 4 sizes (16, 32, 48, 128)
  • ✅ Service worker for background tasks
  • ✅ Content scripts for page interaction
  • ✅ Popup files for user interface

Key principles:

  • Keep your folder structure organized
  • Use service workers correctly (they hibernate!)
  • Test all icon sizes before publishing
  • Debug with Chrome DevTools

Ready to build your extension? Check out our guide on how to make a Chrome extension for a complete walkthrough from idea to Chrome Web Store.

Need to validate your extension idea first? Use Extension Radar to analyze competitor reviews and find exactly what users are asking for.

Ready to analyze your Chrome extension?

Get AI-powered insights from your reviews in 60 seconds. No scraping required.

Analyze Your Extension Free