Google Chrome Extension Development and Deployment Complete Guide

From development to Chrome Web Store publication - Building browser extensions with manifest.json

Google Chrome Extension Development and Deployment Complete Guide



Overview

This article covers the complete process of developing a Google Chrome Extension and deploying it to the Chrome Web Store.

Chrome extensions are tools designed to extend browser functionality or enhance user convenience. They consist of various components centered around the manifest.json file, including popup, background scripts, content scripts, and options pages.

This guide explains the basic structure and core roles of extensions, demonstrates how to test developed extensions in a local environment, and provides detailed step-by-step procedures for registering and deploying to the Chrome Web Store.

Additionally, we’ll share two real-world examples (Dev Toolkit and QRify) I created, along with registration tips and solutions for rejection cases encountered during the review process.



What is a Google Chrome Extension?

A Google Chrome Extension is a software program designed to extend the functionality of the Chrome web browser or improve user experience. These extensions allow users to easily utilize additional features within their browser.


Key Benefits

Enhanced Productivity: Automate repetitive tasks
Customization: Tailor browsing experience to your needs
Integration: Connect with external services and APIs
Accessibility: Easy distribution via Chrome Web Store
Cross-platform: Works on any OS running Chrome



Chrome Extension Architecture


Project Structure

my-extension/
│
├── manifest.json        # Configuration file (Required)
├── background.js        # Background script
├── content.js           # Content script
├── popup.html           # Popup interface
├── popup.js             # Logic for popup
├── options.html         # Options page
├── options.js           # Logic for options
├── icon.png             # Extension icon
└── styles.css           # Styles for popup or options



Core Components


1. manifest.json

The manifest.json file is the heart of every Chrome extension—it defines metadata and behavior.

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "1.0",
  "description": "A sample extension to demonstrate key concepts",
  "permissions": ["storage", "activeTab"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["content.js"],
      "css": ["content.css"]
    }
  ],
  "options_page": "options.html",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}


Key Fields Explained

Field Description Required
manifest_version API version (use 3 for new extensions) ✅ Yes
name Extension name shown in Chrome Web Store ✅ Yes
version Version number (e.g., “1.0.0”) ✅ Yes
description Brief description of functionality ✅ Yes
permissions API permissions needed ❌ No
action Defines toolbar button and popup ❌ No
background Background service worker ❌ No
content_scripts Scripts injected into web pages ❌ No
icons Extension icons at various sizes ✅ Yes


2. Background Scripts

Background scripts (typically background.js) handle long-running operations, event management, and logic that runs independently of any webpage.

Manifest V3 uses Service Workers:

// background.js

// Listen for extension installation
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('Extension installed!');
    // Set default settings
    chrome.storage.sync.set({ 
      theme: 'light',
      notifications: true 
    });
  } else if (details.reason === 'update') {
    console.log('Extension updated to version', chrome.runtime.getManifest().version);
  }
});

// Listen for messages from popup or content scripts
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getData') {
    // Fetch data from storage
    chrome.storage.sync.get(['data'], (result) => {
      sendResponse({ data: result.data });
    });
    return true; // Keep message channel open for async response
  }
});

// Listen for tab updates
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete' && tab.url) {
    console.log('Tab loaded:', tab.url);
    // Perform actions after page load
  }
});


3. Content Scripts

Content scripts (content.js) run in the context of web pages and can directly interact with the page’s DOM.

// content.js

// Change background color of all paragraphs
document.querySelectorAll('p').forEach(paragraph => {
  paragraph.style.backgroundColor = '#ffffe0';
});

// Add custom button to page
const button = document.createElement('button');
button.textContent = 'Click Me!';
button.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
button.addEventListener('click', () => {
  alert('Button clicked from content script!');
});
document.body.appendChild(button);

// Listen for messages from background or popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'highlight') {
    document.querySelectorAll(request.selector).forEach(el => {
      el.style.backgroundColor = 'yellow';
    });
    sendResponse({ status: 'highlighted' });
  }
});


4. Popup HTML

The popup (popup.html + popup.js) provides the UI shown when users click the extension icon.

popup.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My Extension Popup</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>Extension Popup</h1>
    <button id="actionButton">Perform Action</button>
    <div id="result"></div>
  </div>
  <script src="popup.js"></script>
</body>
</html>

popup.js:

// popup.js

document.getElementById('actionButton').addEventListener('click', async () => {
  const resultDiv = document.getElementById('result');
  
  // Get active tab
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  
  // Send message to content script
  chrome.tabs.sendMessage(tab.id, { 
    action: 'highlight',
    selector: 'p'
  }, (response) => {
    if (response) {
      resultDiv.textContent = `Status: ${response.status}`;
    }
  });
});

// Load saved data
chrome.storage.sync.get(['theme'], (result) => {
  if (result.theme === 'dark') {
    document.body.classList.add('dark-theme');
  }
});


5. Options Page

Options pages (options.html + options.js) let users configure extension settings.

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>
  
  <label>
    <input type="checkbox" id="notifications"> Enable Notifications
  </label>
  
  <label>
    Theme:
    <select id="theme">
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>
  </label>
  
  <button id="save">Save Settings</button>
  <div id="status"></div>
  
  <script src="options.js"></script>
</body>
</html>

options.js:

// options.js

// Load saved settings
document.addEventListener('DOMContentLoaded', () => {
  chrome.storage.sync.get(['notifications', 'theme'], (result) => {
    document.getElementById('notifications').checked = result.notifications || false;
    document.getElementById('theme').value = result.theme || 'light';
  });
});

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



Real-World Examples

I developed two Chrome extensions as practical examples:


Example 1: Dev Toolkit

Purpose: Developer utility for formatting and encoding/decoding various data formats

GitHub: dev-toolkit-google-extension

Features:

Key Implementation:

// JSON Formatter
function formatJSON(input) {
  try {
    const parsed = JSON.parse(input);
    return JSON.stringify(parsed, null, 2);
  } catch (error) {
    return `Invalid JSON: ${error.message}`;
  }
}

// Base64 Encoder
function encodeBase64(input) {
  return btoa(unescape(encodeURIComponent(input)));
}

// JWT Decoder
function decodeJWT(token) {
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('Invalid JWT format');
  }
  
  const payload = JSON.parse(atob(parts[1]));
  return JSON.stringify(payload, null, 2);
}


Example 2: QRify

Purpose: Simple QR code generator for current page URL

GitHub: qrify-google-extension

Features:

Key Implementation:

// popup.js
async function generateQRCode() {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  const url = tab.url;
  
  // Use QR Code library (loaded via CDN)
  const qr = qrcode(0, 'M');
  qr.addData(url);
  qr.make();
  
  // Display QR code
  document.getElementById('qrcode').innerHTML = qr.createImgTag(5);
}

document.addEventListener('DOMContentLoaded', generateQRCode);



Local Development and Testing


Step 1: Enable Developer Mode

  1. Navigate to chrome://extensions/
  2. Toggle Developer mode (top-right corner)
  3. New buttons will appear: “Load unpacked”, “Pack extension”, “Update”


Step 2: Load Unpacked Extension

  1. Click “Load unpacked”
  2. Select your extension directory (containing manifest.json)
  3. Extension appears in the list with temporary ID


Step 3: Test Extension

# After loading unpacked:
1. Click extension icon in toolbar
2. Test popup functionality
3. Navigate to matching URLs to test content scripts
4. Check background script console:
   - Click "service worker" link under extension
   - View console logs and errors


Step 4: Iterative Development

# After making code changes:
1. Go to chrome://extensions/
2. Click "Reload" button on your extension
3. Test changes immediately
4. Check console for errors


Debugging Tips

// Add extensive logging
console.log('[Extension] Event triggered:', data);

// Use Chrome DevTools
// - Popup: Right-click popup → Inspect
// - Background: Click "service worker" → Console
// - Content Script: Regular page DevTools → Console

// Check for errors
chrome.runtime.lastError && console.error(chrome.runtime.lastError);



Chrome Web Store Deployment


Prerequisites

Important Note: South Korea is not available as a developer account country because it doesn’t allow merchant account conversion. Use a US address if needed.

Requirements:


Step 1: Register as Developer

  1. Visit Chrome Web Store Developer Dashboard
  2. Sign in with Google Account
  3. Pay $5 registration fee
  4. Accept developer agreement


Step 2: Prepare Extension Package

# Create ZIP file of your extension
# DO NOT include:
# - .git directory
# - node_modules
# - Development files
# - Private keys

zip -r extension.zip . -x "*.git*" -x "*node_modules*" -x "*.env*"


Step 3: Create Store Listing

Required Information:

Store Listing:
  - Short description (132 chars max)
  - Detailed description
  - Category selection
  - Language support
  
Visual Assets:
  - Small icon: 128x128 PNG
  - Screenshots: 1280x800 or 640x400 (at least 1)
  - Promotional images (optional)
  
Additional:
  - Privacy policy URL (required if using permissions)
  - Support email
  - Website URL (optional)


Step 4: Upload and Submit

  1. Click “New Item” in developer dashboard
  2. Upload ZIP file
  3. Fill out Store Listing:
    • Product name
    • Summary (short description)
    • Detailed description
    • Category
    • Language
  4. Add Screenshots (at least 1, maximum 5)
  5. Set Pricing & Distribution:
    • Regions to publish
    • Visibility (Public/Unlisted/Private)
  6. Submit for Review



Review Process


Timeline


Common Rejection Reasons


Issue 1: Excessive Permissions

Rejection Message:

Your extension requests permissions that are not required for its functionality.

Solution: Remove unnecessary permissions from manifest.json:

{
  "permissions": [
    "activeTab"  //  Remove if not actively using
  ]
}

Only request permissions you actively use:

{
  "permissions": [
    "storage"  //  Only if storing data
  ]
}


Issue 2: Missing Privacy Policy

Rejection Message:

Extensions that handle user data must provide a privacy policy.

Solution: Create and link a privacy policy:

# Privacy Policy for [Extension Name]

## Data Collection
This extension does not collect any personal information.

## Data Storage
Settings are stored locally using Chrome's storage API.

## Third-Party Services
This extension does not use any third-party services.

## Contact
Email: your-email@example.com

Host on GitHub Pages or your website, then add URL to store listing.


Issue 3: Poor Description

Rejection Message:

Store listing must clearly describe extension functionality.

Solution: Write clear, detailed description:

❌ Bad: "Useful tool for developers"

✅ Good:
"Dev Toolkit provides essential utilities for web developers:
- Format and validate JSON
- Encode/decode Base64
- Parse JWT tokens
- Convert timestamps
- Encode/decode URLs

Perfect for debugging APIs, analyzing tokens, and working with encoded data."


Issue 4: Misleading Screenshots

Rejection Message:

Screenshots must accurately represent extension functionality.

Solution:



Published Extensions

After approval, extensions are publicly available:


Dev Toolkit

Chrome Web Store: Dev Toolkit

Features:


QRify

Chrome Web Store: QRify

Features:



Advanced Topics


Manifest V3 Best Practices

{
  "manifest_version": 3,
  "permissions": [
    "storage"  // Minimum permissions only
  ],
  "host_permissions": [
    "https://api.example.com/*"  // Specific domains only
  ],
  "background": {
    "service_worker": "background.js",
    "type": "module"  // Use ES modules
  }
}


Storage API Usage

// Save data
chrome.storage.sync.set({ key: 'value' }, () => {
  console.log('Data saved');
});

// Load data
chrome.storage.sync.get(['key'], (result) => {
  console.log('Data loaded:', result.key);
});

// Listen for changes
chrome.storage.onChanged.addListener((changes, area) => {
  console.log('Storage changed:', changes);
});


Messaging Between Components

// From popup to background
chrome.runtime.sendMessage({ action: 'getData' }, (response) => {
  console.log('Response:', response);
});

// From background to content script
chrome.tabs.query({ active: true }, (tabs) => {
  chrome.tabs.sendMessage(tabs[0].id, { action: 'highlight' });
});

// Listen in background
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getData') {
    sendResponse({ data: 'example' });
  }
  return true; // Keep channel open for async
});


OAuth Integration

// Using chrome.identity API
chrome.identity.getAuthToken({ interactive: true }, (token) => {
  if (chrome.runtime.lastError) {
    console.error(chrome.runtime.lastError);
    return;
  }
  
  // Use token to call API
  fetch('https://api.example.com/data', {
    headers: { 'Authorization': `Bearer ${token}` }
  });
});



Troubleshooting


Issue: Extension Not Loading

Symptoms: Error when loading unpacked extension

Solutions:

# Check manifest.json syntax
cat manifest.json | jq .

# Verify required fields exist
- manifest_version
- name
- version

# Check file paths match manifest
ls popup.html background.js content.js


Issue: Content Script Not Running

Symptoms: Content script code doesn’t execute

Solutions:

// Check matches pattern in manifest.json
"content_scripts": [{
  "matches": ["https://*.example.com/*"],  // Verify pattern
  "js": ["content.js"]
}]

// Add logging to verify script loads
console.log('[Content Script] Loaded');

// Check CSP policies on target site
// Some sites block extension scripts


Issue: Popup Not Showing

Symptoms: Clicking icon does nothing

Solutions:

// Verify popup path in manifest
"action": {
  "default_popup": "popup.html"  // Check path is correct
}

// Check popup HTML loads
// Right-click extension icon  Inspect popup

// Verify no JavaScript errors
// Check console in popup DevTools



Monetization Considerations

Important: South Korea does not support merchant accounts on Chrome Web Store.


Alternative Approaches

  1. Free Extensions with Donations:
    <!-- Add donation link in options page -->
    <a href="https://buymeacoffee.com/yourname">Support Development</a>
    
  2. Premium Features via External Service:
    • Free basic features
    • Premium features require external subscription
  3. Enterprise Licensing:
    • Contact businesses directly
    • License outside Chrome Web Store



Conclusion

Chrome Extensions are excellent projects that can be developed with simple HTML, JS, and CSS without complex frameworks—making them accessible to anyone.


Key Benefits

Automate repetitive tasks in daily workflow
Extend website functionality with custom features
Share personal tools via browser integration
Global distribution with one-time $5 fee
AI-assisted development makes it beginner-friendly


Limitations to Note

⚠️ No Korean merchant accounts: Cannot monetize directly
⚠️ Review process: Can take several days
⚠️ Permission scrutiny: Only request what you need
⚠️ Manifest V3: Must follow latest standards


Future Enhancements

Consider exploring these advanced features:


With AI tools, even non-developers can create useful Chrome extensions. If you have an idea, give it a try!



References