Ever been browsing the web and thought, "I wish I could just add a little button here that does this"? Well, you can! Chrome extensions are your gateway to customizing your browsing experience, and they're surprisingly easy to build.
In this guide, we'll create a simple but powerful Chrome extension from scratch. By the end, you'll have a working extension that can:
- π¨ Change the background color of any website.
- π Show desktop notifications.
- πΎ Save and load data.
- π Inject a floating button onto any page.
Ready to become a browser magician? Let's dive in!
π The File Structure
First, create a new folder. Inside, we'll create the following files. This is the complete anatomy of our simple extension.
my-first-extension/ βββ manifest.json # The most important file; the extension's blueprint βββ popup.html # The UI you see when you click the icon βββ popup.js # The logic for our popup βββ background.js # The extension's brain, runs in the background βββ content.js # Code that runs on the webpages you visit βββ content.css # Styles for our code on webpages βββ icons/ # Folder for our extension's icons βββ icon16.png βββ icon32.png βββ icon48.png βββ icon128.png
The "Restaurant" Analogy for Chrome Extensions
To make sense of how these files work together, let's use an analogy. Think of your Chrome extension as a small, efficient restaurant operating inside the larger city of your browser.
manifest.json
(The Restaurant License & Menu): This is your official business license. It tells the city (Chrome) your restaurant's name, where it is, what it needs to operate (permissions like electricity and water), and what's on the menu (the features you offer). Without it, you can't even open.popup.html
&popup.css
(The Front Counter): This is the customer-facing part of your restaurant. It's where customers walk up, see the daily specials, and place their orders. It needs to be clean, well-designed (popup.css
), and easy to understand (popup.html
).popup.js
(The Cashier): The cashier takes the customer's order. They listen for what the customer wants ("I'll have the 'Change Background' special!"), press the right buttons on the register, and send the order to the kitchen. They are the direct link between the customer and the back-of-house operations.background.js
(The Head Chef & Kitchen): This is the heart of the operation. The kitchen runs constantly, even when there are no customers at the counter. The head chef receives orders from the cashier and knows how to prepare everything on the menu (like making a "Notification"). It also handles background tasks like receiving supplies (alarms) or managing the restaurant's inventory (storage).content.js
(The Waiter Delivering to a Table): Sometimes, an order isn't for takeout; it's for a customer already seated in the main dining hall (a webpage). The content script is the waiter who takes the prepared dish from the kitchen, walks out into the dining hall, and delivers it directly to the customer's table, sometimes even rearranging the salt and pepper shakers on the table (modifying the DOM).
Together, this team creates a seamless experience, from ordering at the counter to enjoying a feature on a webpage!
1. The Blueprint: manifest.json
Every extension must have a manifest.json
file. It's the control center that tells Chrome everything it needs to know about your extension.
{ "manifest_version": 3, "name": "My First Chrome Extension", "version": "1.0", "description": "A simple Chrome extension for beginners", "permissions": [ "activeTab", "notifications", "storage", "scripting", "alarms" ], "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": ["<all_urls>"], "js": ["content.js"], "css": ["content.css"] } ], "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } }
What does this all mean?
-
"manifest_version": 3
: We're using the latest and greatest Chrome extension platform version. -
"permissions"
: This is crucial. We're asking for permission to use certain Chrome APIs. We need"scripting"
to run code on pages,"notifications"
to show alerts, and so on. -
"action"
: Defines what happens when you click the extension icon in the toolbar. We're telling it to openpopup.html
. -
"background"
: Points to our background script, the extension's persistent brain. -
"content_scripts"
: This is where the magic happens. We're telling Chrome to injectcontent.js
andcontent.css
into every webpage ("<all_urls>"
).
2. The Face: popup.html
& popup.js
When you click your extension's icon, a small window appears. That's the popup, and it's just a tiny webpage.
popup.html
is a standard HTML file with some buttons and a status area.
<!DOCTYPE html> <html> <head> <style> body { width: 300px; padding: 15px; /* ... and other styles */ } button { width: 100%; padding: 10px; margin: 5px 0; } </style> </head> <body> <h1>My First Extension</h1> <button id="changeBackground">Change Page Background</button> <button id="showNotification">Show Notification</button> <div class="status" id="status">Ready to use!</div> <script src="popup.js"></script> </body> </html>
popup.js
brings the popup to life. It listens for clicks and tells other parts of the extension what to do.
Here's the most interesting partβchanging the page background:
// In popup.js document.getElementById('changeBackground').addEventListener('click', async () => { const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); // π‘ Pro Tip: You can't run scripts on Chrome's internal pages! // So we add a check to give the user a friendly message. if (tab.url.startsWith('chrome://')) { document.getElementById('status').textContent = 'β Cannot run on Chrome pages.'; return; } chrome.scripting.executeScript({ target: {tabId: tab.id}, function: changePageBackground // This function is executed on the webpage }); }); // This function is NOT in popup.js's scope. // It gets sent to the content script to be executed. function changePageBackground() { const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16); document.body.style.backgroundColor = randomColor; }
This is a key concept: the popup is a separate world. To affect a webpage, it uses the chrome.scripting.executeScript
API to send a function to be executed on that page.
3. The Engine Room: background.js
The background script is a service worker. It's the extension's core, always ready to listen for events, even when the popup is closed.
// In background.js console.log('Background script loaded'); // Listen for messages from other scripts (like the popup) chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'showNotification') { chrome.notifications.create({ type: 'basic', iconUrl: 'icons/icon48.png', title: 'Extension Notification', message: 'Hello from your extension!' }); sendResponse({success: true}); } return true; // Keep the message channel open for async response });
Here, the background script listens for a message with the action showNotification
. Why do this here and not in the popup? Because the background script persists. It can handle tasks like notifications, alarms, or listening for tab updates, which don't depend on a UI being open.
4. The Infiltrator: content.js
& content.css
The content script is your agent inside the webpage. It has access to the page's DOM, allowing it to read and modify content.
// In content.js console.log('Content script loaded on:', window.location.href); // Create a floating button and add it to the page const button = document.createElement('button'); button.id = 'myExtensionButton'; button.textContent = 'π My Extension'; button.className = 'my-extension-floating-btn'; button.addEventListener('click', () => { alert('Hello from the content script!'); }); document.body.appendChild(button);
This script creates a button, gives it some text and a class, and appends it to the <body>
of every page you visit.
Of course, an unstyled button looks ugly. That's where content.css
comes in. These styles are also injected into the page to make our button look fabulous.
/* In content.css */ .my-extension-floating-btn { position: fixed; top: 20px; right: 20px; z-index: 9999; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 25px; padding: 10px 15px; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); }
π οΈ Installation & Debugging
You've written the code, now let's bring it to life!
- Open Chrome and navigate to
chrome://extensions
. - Flick the "Developer mode" switch in the top-right corner.
- Click the "Load unpacked" button.
- Select the folder containing your extension files.
Your extension should now appear in your toolbar!
"Houston, we have a problem." How do you debug?
- Popup: Right-click the extension icon and select "Inspect popup". A DevTools window will open for the popup.
- Background Script: On the
chrome://extensions
page, find your extension and click the "Inspect views: service worker" (or similar) link. - Content Script: Open DevTools (F12) on any webpage where your script is running. Your logs will appear in that page's console, prefixed with your extension's name.
π» Source Code for the Simple Extension
You can find the complete code for the vanilla JavaScript extension covered in this tutorial on GitHub.
π What's Next?
You've done it! You've built a fully functional Chrome extension. You've learned about the manifest, popups, background scripts, and content scriptsβthe fundamental building blocks of all extensions.
This is just the beginning. You can now explore other powerful Chrome APIs:
-
chrome.storage
: For more complex data storage. -
chrome.contextMenus
: Add options to the right-click menu. -
chrome.webRequest
: Intercept and modify network requests.
π Bonus Section: Level Up with React and Webpack!
The vanilla JavaScript approach is perfect for simple extensions, but what if you want to build something more complex? What if you love using frameworks like React or Vue?
You absolutely can! But it requires a build step.
Why Do I Need a Build Step?
Browsers don't understand React's JSX syntax out of the box. We need a tool to convert our modern, framework-based code into the plain HTML, JavaScript, and CSS that Chrome can understand.
What is Webpack?
Webpack is a module bundler. It takes all your project files (React components, CSS-in-JS, different JS modules), processes them, and bundles them into a few static files that are ready for the browser.
Modern Project Structure
Your source code would look a little different. Instead of writing directly into popup.js
, you'd have a src
folder.
my-react-extension/ βββ src/ β βββ popup/ β β βββ Popup.jsx # Your React component β β βββ index.js # Entry point to render the component β βββ background/ β β βββ index.js # Your background script β βββ content/ β βββ index.js # Your content script βββ public/ β βββ manifest.json β βββ popup.html # A simple HTML template β βββ icons/ β βββ icon128.png βββ webpack.config.js # The magic configuration file
The Webpack Configuration (webpack.config.js
)
This file tells Webpack how to bundle everything. It looks intimidating, but it's quite logical.
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { // 1. Entry points: Define which files are the start of each bundle. entry: { popup: './src/popup/index.js', background: './src/background/index.js', content: './src/content/index.js', }, // 2. Output: Where to put the final bundled files. output: { path: path.resolve(__dirname, 'dist'), filename: '[name].bundle.js', }, // 3. Modules & Rules: How to handle different file types. module: { rules: [ { test: /\.(js|jsx)$/, // For any .js or .jsx file... exclude: /node_modules/, use: { loader: 'babel-loader', // ...use Babel to transpile it to older JS. options: { presets: ['@babel/preset-env', '@babel/preset-react'], }, }, }, ], }, // 4. Plugins: Extra tools to help the build process. plugins: [ // Creates popup.html in the 'dist' folder from our template. new HtmlWebpackPlugin({ template: './public/popup.html', filename: 'popup.html', chunks: ['popup'], // Only include the 'popup.bundle.js' in this HTML file. }), // Copies static files (like manifest.json and icons) to the 'dist' folder. new CopyWebpackPlugin({ patterns: [ { from: 'public/manifest.json', to: 'manifest.json' }, { from: 'public/icons', to: 'icons' }, ], }), ], };
The Final Step: The Build
After setting this up, you'd run a command like npm run build
. Webpack would then generate a dist
folder.
This dist
folder contains the vanilla, browser-readable code. This is the folder you would load into Chrome as your "unpacked extension."
This approach lets you use all the power of modern web development to build amazing, complex, and maintainable Chrome extensions.
π» Source Code for the React Extension
Want to skip the setup and dive right into building with React? Here is a complete starter template with Webpack, React, and Chakra UI pre-configured.> [Get the code: React Chrome Extension Starter(https://github.com/JayMalli/My-First-Chrome-Extension/tree/main/ReactJs_extension)
π€ Let's Connect!
If you found this guide helpful, let's keep the conversation going! I regularly post deep dives, security tips, and new projects I'm working on.
Connect with me on LinkedInβI'd love to hear about what you're building.
What will you build next? A tool to save your favorite articles? A theme customizer for your favorite website? The possibilities are endless.
Share your ideas in the comments below! Happy coding!
Top comments (0)