Why Chrome Extensions Still Matter
Every day more than three billion people open Chrome. Hidden inside that blue-green-red-yellow icon is the world's largest app platform that most developers ignore. A browser extension can turn a static webpage into a productivity powerhouse, inject data into any site, or fix interface annoyances in seconds. The barrier to entry is one HTML file and twenty lines of JavaScript. No app store approval circus, no deployment gymnastics. You code, you reload, you ship.
What You Will Build
We are going to create a dead-simple extension called Tab Counter. It shows the exact number of open tabs in the current window, updates live, and resets when you close tabs. The same skeleton works for password managers, color pickers, VPN toggles, or full-blown IDEs. Once you grasp the pattern you can swap the logic and scale to thousands of users.
Prerequisites
You need Chrome (any recent version), a text editor, and basic HTML, CSS and JavaScript knowledge. That is it. No build tools, no npm install marathons, no frameworks. We will stay in vanilla land so you see every moving part.
Project Layout in 30 Seconds
Create a folder named tab-counter
and drop four files inside:
manifest.json
– the extension's ID cardpopup.html
– the tiny window that appears when you click the iconpopup.js
– code that runs inside the popupicon.png
– a 128×128 image (draw a smiley in Paint if you want)
The finished folder weighs less than 10 KB. Keep it open in your editor; we will edit each file in turn.
Writing manifest.json
The manifest tells Chrome what your extension does, what permissions it needs, and which files to load. Paste this JSON and save:
{"manifest_version":3,"name":"Tab Counter","version":"1.0","description":"Live count of open tabs in the current window","action":{"default_popup":"popup.html","default_icon":"icon.png"},"permissions":["tabs"]}
Manifest version 3 is the current standard. The tabs
permission lets us query open tabs without triggering scary warnings.
Creating popup.html
This is ordinary HTML. Keep it tiny; Chrome clips popups that exceed 600 px height or width.
<!doctype html><html><head><meta charset="utf-8"><title>Tab Counter</title><style>body{font-family:system-ui;padding:12px;width:160px}h2{margin:0 0 8px;font-size:16px}#count{font-size:28px;text-align:center}</style></head><body><h2>Open Tabs</h2><div id="count">?</div><script src="popup.js"></script></body></html>
Adding the Brain in popup.js
Chrome injects this script every time the popup opens. One call to the chrome.tabs
API returns every tab in the current window; we simply count the array length and paint it into the DOM.
chrome.tabs.query({currentWindow:true},tabs=>{document.getElementById('count').textContent=tabs.length;});
Save the file. Done. The entire business logic is four lines.
Loading the Extension Locally
- Open Chrome and navigate to
chrome://extensions
- Enable Developer mode (toggle in the top-right corner)
- Click Load unpacked and select your
tab-counter
folder
An icon appears next to the address bar. Click it. The popup displays the correct tab count instantly. Congratulations, you just shipped version 1.0 to yourself.
Debugging Like a Pro
Extensions fail silently by default. To see console logs, right-click the extension icon and choose Inspect popup. A DevTools window opens that behaves exactly like any other web page. Add console.log(tabs)
inside popup.js, reload the extension on the extensions page, and inspect the logged array. That is your printf-driven development environment for browser add-ons.
Making the Badge Update Live
Popups only exist while open. To show the count persistently on the toolbar icon, we need a background script. Create background.js
:
function updateBadge(){chrome.tabs.query({currentWindow:true},tabs=>{chrome.action.setBadgeText({text:String(tabs.length)});});}chrome.tabs.onCreated.addListener(updateBadge);chrome.tabs.onRemoved.addListener(updateBadge);updateBadge();
Register it in manifest.json by adding:
"background":{"service_worker":"background.js"}
Reload the extension. The number now sits on the icon even when the popup is closed. Live counters, status indicators and tiny alerts all follow this exact pattern.
Surviving Reloads and Restarts
Service workers unload after five seconds of inactivity. Chrome restarts them when an event fires, but global variables reset. Persist any state in chrome.storage.local
, an asynchronous key-value store baked into every extension.
chrome.storage.local.set({lastCount:tabs.length});chrome.storage.local.get(['lastCount'],data=>{console.log(data.lastCount);});
Adding a Keyboard Shortcut
Power users love hotkeys. Add this object to manifest.json:
"commands":{"_execute_action":{"suggested_key":{"default":"Ctrl+Shift+T"}}}
Reload, then press Ctrl+Shift+T (macOS: Command+Shift+T). The popup opens without a mouse click. One JSON stanza, zero extra code.
Cross-Origin Magic with Content Scripts
Sometimes you want to reach inside a page the user opened. Content scripts are JavaScript files that Chrome injects into tabs. They run in an isolated world yet can read and modify the DOM. Add a new file content.js
:
console.log('Hello from the extension',document.title);
Declare the injection in manifest.json:
"content_scripts":[{"matches":["<all_urls>"],"js":["content.js"]}]
Now every page logs its title. Replace that line with code that adds a button, rewrites headings, or scrapes prices. The same mechanism drives ad blockers, grammar checkers and dark-mode flippers.
Communicating between Worlds
Popups, background workers and content scripts live in separate contexts. To pass data, use Chrome's message passing API. From a content script:
chrome.runtime.sendMessage({type:'GREETING',payload:'hi'});
In the background worker:
chrome.runtime.onMessage.addListener((msg,sender,sendResponse)=>{if(msg.type==='GREETING'){console.log('received',msg.payload);}});
The pattern is fire-and-forget or request-response. Keep messages JSON-serializable and under 64 MB.
Packaging for the Chrome Web Store
Once your extension works:
- Zip the folder (not the parent directory)
- Create a developer account (https://chrome.google.com/webstore/devconsole) – one-time five dollar signup
- Click New Item, upload the ZIP, fill the description, add screenshots (1280×800, no rounded corners)
- Publish. Review usually finishes within a few hours for simple projects
Updates use the same flow; increment version
in manifest.json and upload again. Users receive new code automatically.
Monetisation Ethics
Extensions can sell premium features, show respectful ads or ask for donations. The Web Store policy forbids deceptive installations, cryptominers and intrusive tracking. Ask only for permissions you truly need; users read the list. A tab counter that requests microphone access will be rejected and publicly shamed in reviews.
Performance Rules of Thumb
- Keep background scripts idle; wake up only for events
- Avoid persistent timers; use the
alarms
API instead - Minify and compress images; every byte ships to every user
- Defer heavy work until the user actually opens the popup
Common Pitfalls and Quick Fixes
Blank popup: check that popup.html is valid and script paths are relative.
Icon missing: supply a 16×16, 48×48 and 128×128 PNG so Chrome picks the best density.
Content script not injected: verify the matches
pattern and reload the extension after any manifest change.
Background worker disappears: expect it; persist state and re-register listeners on every start.
Ideas to Level Up
Swap the counter for a Pomodoro timer that blocks distracting sites, add a notepad that syncs across devices using chrome.storage.sync
, or inject a floating widget that translates selected text. Each feature reuses the scaffold we built: manifest, popup, background, optional content script.
Next Steps
Study the official samples repository (GoogleChrome/chrome-extensions-samples) to see service-worker patterns, OAuth flows and side-panel APIs. Build one toy extension per week; after a month you will have a portfolio that proves you can ship real software to real users.
Disclaimer
This tutorial is generated by an AI language model for educational purposes. Chrome extension policies and APIs evolve; always consult the official documentation before publishing.