DEV Community

Sinan Mp
Sinan Mp

Posted on

Complete Chrome Extension Tutorial (Manifest V3): Build & Publish a Real Extension

We fill forms every day.

Signups.
Checkout pages.
Job applications.
Contact forms.

And every time, we type:

  • Name
  • Email
  • Phone number
  • Address

Over and over again.

So I built a Chrome extension called TypeLess to solve exactly that.

This post isn’t just about the idea—I'll walk you through how it actually works under the hood, what I learned, and the technical challenges I faced.

The Core Idea

The concept is simple:

Save your personal details once → Autofill forms anywhere with one click.

  • No backend.
  • No accounts.
  • No external APIs.

Everything runs locally inside the browser.

But implementing that required understanding how Chrome extensions really work.

Understanding Chrome Extension Architecture (Manifest V3)

manifest.json

A Chrome extension isn’t a normal web app.

It has multiple isolated environments:

  1. Popup – The UI when you click the extension icon
  2. Background Service Worker – Runs in the background and handles events
  3. Content Script – Injected into web pages
  4. Options Page – Full settings page Each environment runs separately and communicates using message passing.

This separation is powerful—but it changes how you structure logic.

How TypeLess Is Structured

Here’s the simplified architecture:

User Action (Shortcut / Click)
        ↓
Background Service Worker
        ↓
Send Message to Active Tab
        ↓
Content Script
        ↓
Detect & Fill Form Fields
Enter fullscreen mode Exit fullscreen mode

1️⃣ Storing User Data

User data (name, email, phone, etc.) is stored using:

chrome.storage.local.set({ userData: formValues });
Enter fullscreen mode Exit fullscreen mode

Why chrome.storage.local?

  • It’s asynchronous
  • It’s extension-scoped
  • Data never leaves the browser

One thing to note: unlike localStorage, Chrome storage is async, so you must handle it properly:

chrome.storage.local.get("userData", (result) => {
  const data = result.userData;
});
Enter fullscreen mode Exit fullscreen mode

If you forget the async nature, things break quickly.

2️⃣ Triggering Autofill

I added keyboard shortcuts using the chrome.commands API:

chrome.commands.onCommand.addListener((command) => {
  if (command === "autofill_form") {
    // Send message to active tab
  }
});
Enter fullscreen mode Exit fullscreen mode

The background service worker listens for this event and sends a message to the active tab:

chrome.tabs.sendMessage(tabId, { action: "FILL_FORM" });
Enter fullscreen mode Exit fullscreen mode

3️⃣ The Hard Part: Detecting Form Fields

This is where things get interesting.

Forms are inconsistent across websites.

You’ll see inputs like:

<input name="email" />
<input id="user_email" />
<input placeholder="Enter your email" />
Enter fullscreen mode Exit fullscreen mode

Some frameworks:

  • Don’t use traditional labels
  • Wrap inputs in custom components
  • Manage state internally (React, Vue)

So I built multiple detection strategies:

  • Match by name
  • Match by id
  • Match by placeholder
  • Check associated
  • Fuzzy matching

Example approach:

const inputs = document.querySelectorAll("input, textarea");

inputs.forEach((input) => {
  const fieldName = input.name || input.id || input.placeholder;

  if (fieldName?.toLowerCase().includes("email")) {
    input.value = userData.email;

    // Trigger proper event for React/Vue forms
    input.dispatchEvent(new Event("input", { bubbles: true }));
  }
});
Enter fullscreen mode Exit fullscreen mode

Important: React Forms

Just setting input.value isn’t enough for React apps.

You must dispatch the input event so React updates its internal state.

This was one of the biggest “aha” moments during debugging.

Content Security Policy (CSP) Gotcha

Chrome extensions have strict CSP rules.

  • No inline scripts
  • No inline event handlers

So things like this won’t work:

<button onclick="fillForm()">Fill</button>
Enter fullscreen mode Exit fullscreen mode

Instead, you must attach event listeners in JS files.

This forced me to structure code more cleanly—which was actually a good thing.

Injection Limitations

Extensions cannot run on:

  • chrome:// pages
  • Chrome Web Store pages

Also, permissions must be clearly defined in manifest.json.

For example:

"permissions": [
  "storage",
  "activeTab",
  "scripting",
  "commands"
]

Enter fullscreen mode Exit fullscreen mode

If permissions don’t match usage, the extension won’t work — or worse, it won’t pass review.

Why I Didn’t Use a Backend

I intentionally avoided:

  • Remote databases
  • Analytics tracking
  • User accounts

Everything is local.

This simplified:

  • Architecture
  • Privacy compliance
  • Chrome Web Store approval

Sometimes less infrastructure = better product.

What I Learned Building TypeLess

Here are some practical takeaways if you're thinking about building a Chrome extension:

1️⃣ Think in Isolated Contexts

Popup ≠ Background ≠ Content Script.

2️⃣ Chrome APIs Are Event-Driven

Everything revolves around listeners and messaging.

3️⃣ Modern Forms Are Tricky

React/Vue require event dispatching.

4️⃣ Async Storage Changes Logic Flow

Don’t assume synchronous reads.

5️⃣ Small Tools Still Require Good Architecture

Even “simple” ideas can get complex quickly.

What’s Next

I’m currently experimenting with:

  • AI-based smart field inference
  • Auto-splitting full name into first/last
  • Generating age from DOB
  • Optional cross-device sync

But the core philosophy stays the same:

  • Keep it lightweight.
  • Keep it private.
  • Keep it useful.

If you’ve been thinking about building a Chrome extension — I highly recommend trying it.

It forces you to understand the browser at a deeper level.

And you might end up solving a problem you personally face every day.

If you have questions about Chrome extension development, feel free to ask. 👇

https://chromewebstore.google.com/detail/lhcafeinbeaaingfhamcdpgnmafnpoic?utm_source=item-share-cb

Top comments (0)