- Published on
Building First Chrome Extension
- Authors
- Name
- Saad Bash
The Foundation: Understanding manifest.json
Every Chrome extension needs just one required file: manifest.json
.
This file is the blueprint of the extension. It tells Chrome what extension can do, what permissions it needs, and how it should behave.
Example manifest.json
structure covering the most common extension patterns:
{
"manifest_version": 3,
"name": "First Extension",
"description": "A simple Chrome extension to get started",
"version": "1.0.0",
"permissions": ["storage", "activeTab"],
"host_permissions": ["https://*/*"],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"action": {
"default_popup": "popup.html",
"default_title": "Extension"
},
"options_page": "options.html",
"commands": {
"toggle-feature": {
"suggested_key": {
"default": "Ctrl+Shift+K",
"mac": "Command+Shift+K"
},
"description": "Toggle extension feature"
}
},
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}
Essential Scripts and Their Purposes
1. Content Scripts: Interacting with Web Pages
Content scripts run in the context of web pages and can read and modify the DOM. Used for:
- Modifying page content
- Adding new elements to pages
- Highlighting text or images
- Detecting specific content patterns
content.js example:
// This script runs on web pages matching manifest patterns
console.log('Content script loaded!')
// Example: Add a banner to the top of every page
const banner = document.createElement('div')
banner.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
background: #007bff;
color: white;
text-align: center;
padding: 10px;
z-index: 10000;
`
banner.textContent = 'My Extension is Active!'
document.body.appendChild(banner)
// Listen for messages from other parts of the extension
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_PAGE_INFO') {
sendResponse({
title: document.title,
url: window.location.href,
})
}
})
2. Background Scripts (Service Workers): Extension Logic Hub
Background scripts manage extension's lifecycle and handle events. Used for:
- Responding to browser events
- Managing extension state
- Handling keyboard shortcuts
- Processing data between different parts of extension
background.js example:
// Service worker for Manifest V3
console.log('Background script loaded!')
// Handle extension installation
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed!')
// Set default settings
chrome.storage.sync.set({
enabled: true,
theme: 'dark',
})
})
// Handle keyboard commands
chrome.commands.onCommand.addListener((command) => {
if (command === 'toggle-feature') {
// Send message to active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {
type: 'TOGGLE_FEATURE',
})
})
}
})
// Handle browser action clicks
chrome.action.onClicked.addListener((tab) => {
// Execute action when extension icon is clicked
chrome.tabs.sendMessage(tab.id, {
type: 'ICON_CLICKED',
})
})
3. Popup Scripts: Quick Actions Interface
Popup scripts power the small window that appears when users click on extension icon. Used for:
- Quick settings toggles
- Status displays
- Action buttons
- Mini-interfaces
popup.js example:
// popup.js - Controls the extension popup
document.addEventListener('DOMContentLoaded', () => {
const toggleButton = document.getElementById('toggle')
const statusDiv = document.getElementById('status')
// Load current state
chrome.storage.sync.get(['enabled'], (result) => {
const enabled = result.enabled || false
toggleButton.textContent = enabled ? 'Disable' : 'Enable'
statusDiv.textContent = enabled ? 'Active' : 'Inactive'
})
// Handle toggle button click
toggleButton.addEventListener('click', () => {
chrome.storage.sync.get(['enabled'], (result) => {
const newState = !result.enabled
chrome.storage.sync.set({ enabled: newState }, () => {
toggleButton.textContent = newState ? 'Disable' : 'Enable'
statusDiv.textContent = newState ? 'Active' : 'Inactive'
// Notify content scripts
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {
type: 'STATE_CHANGED',
enabled: newState,
})
})
})
})
})
})
popup.html:
<!doctype html>
<html>
<head>
<style>
body {
width: 200px;
padding: 10px;
}
button {
width: 100%;
padding: 8px;
margin: 5px 0;
}
#status {
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div id="status">Loading...</div>
<button id="toggle">Toggle</button>
<script src="popup.js"></script>
</body>
</html>
4. Options Scripts: Settings and Configuration
Options scripts create a dedicated settings page for extension. Used for:
- User preferences
- Configuration options
- Advanced settings
- Theme customization
options.js example:
// options.js - Manages the extension settings page
document.addEventListener('DOMContentLoaded', () => {
const themeSelect = document.getElementById('theme')
const saveButton = document.getElementById('save')
const status = document.getElementById('status')
// Load saved settings
chrome.storage.sync.get(['theme'], (result) => {
themeSelect.value = result.theme || 'light'
})
// Save settings
saveButton.addEventListener('click', () => {
const theme = themeSelect.value
chrome.storage.sync.set({ theme: theme }, () => {
status.textContent = 'Settings saved!'
setTimeout(() => {
status.textContent = ''
}, 2000)
})
})
})
Understanding Manifest.json Sections
Required Fields
{
"manifest_version": 3, // Always use version 3 (latest)
"name": "Extension Name",
"version": "1.0.0"
}
Permissions and Access
{
"permissions": [
"storage", // Save user preferences
"activeTab", // Access current tab
"contextMenus", // Add right-click menu items
"notifications" // Show browser notifications
],
"host_permissions": [
"https://*/*", // Access HTTPS sites
"*://example.com/*" // Specific site access
]
}
Script Definitions
{
"background": {
"service_worker": "background.js",
"type": "module" // Enables ES6 imports
},
"content_scripts": [
{
"matches": ["https://*/*"], // Which sites to run on
"js": ["content.js"], // Scripts to inject
"css": ["styles.css"], // Styles to inject
"run_at": "document_end" // When to run
}
]
}
User Interface Elements
{
"action": {
"default_popup": "popup.html", // Popup when icon clicked
"default_title": "Extension Name",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png"
}
},
"options_page": "options.html", // Settings page
"commands": {
"shortcut-name": {
"suggested_key": {
"default": "Ctrl+Shift+K"
},
"description": "What this shortcut does"
}
}
}
Match Patterns
Control where content scripts run:
"matches": [
"https://*/*", // All HTTPS sites
"*://example.com/*", // Specific domain
"https://*/api/*", // Specific paths
"<all_urls>" // Every site (use carefully)
]
Permissions Strategy
Follow the principle of least privilege:
activeTab
: Access current tab only when user clicks extensionstorage
: Save user preferences locallycontextMenus
: Add right-click menu optionsnotifications
: Show browser notificationshost_permissions
: Access specific websites
Communication Between Scripts
Scripts communicate using Chrome's messaging API:
// Send message from popup to content script
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { type: 'ACTION_TYPE' })
})
// Listen for messages in content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'ACTION_TYPE') {
// Do something
sendResponse({ success: true })
}
})
Essential Resources
Official Documentation
- Chrome Extension Developer Guide
- Manifest.json Reference
- Chrome APIs Documentation
- Getting Started Tutorial
Learning Resources
Development Tools
- Chrome Extensions CLI
- @types/chrome for TypeScript support
- Extension Reloader for faster development