DEV Community

Jay Malli
Jay Malli

Posted on

πŸ› οΈ Build Your First Chrome Extension (with a React Bonus)

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 
Enter fullscreen mode Exit fullscreen mode

Confused


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!

Analogy


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" } } 
Enter fullscreen mode Exit fullscreen mode

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 open popup.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 inject content.js and content.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> 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode

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 }); 
Enter fullscreen mode Exit fullscreen mode

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); 
Enter fullscreen mode Exit fullscreen mode

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); } 
Enter fullscreen mode Exit fullscreen mode

πŸ› οΈ Installation & Debugging

You've written the code, now let's bring it to life!

  1. Open Chrome and navigate to chrome://extensions.
  2. Flick the "Developer mode" switch in the top-right corner.
  3. Click the "Load unpacked" button.
  4. 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.

Get the code: Simple Chrome Extension Starter


πŸŽ‰ 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 
Enter fullscreen mode Exit fullscreen mode

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' }, ], }), ], }; 
Enter fullscreen mode Exit fullscreen mode

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.

Find me on LinkedIn


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)