DEV Community

Cover image for Build a Chrome Extension That Checks Selected Text for AI (Manifest V3 Tutorial)

Build a Chrome Extension That Checks Selected Text for AI (Manifest V3 Tutorial)

Browser extensions are one of the most practical places to put AI detection. You highlight text on any page, right-click, and get a quick read on whether it looks AI-generated, without copying and pasting into another tab.

In this tutorial we'll build a small Manifest V3 Chrome extension that does exactly that. Along the way we'll cover the security mistake most "AI extension" tutorials make: shipping a secret API key inside the extension.

What we're building

  • A right-click menu item: "Check selected text for AI"
  • It reads the full selected text from the page
  • It sends the text to your own backend, which calls the detection API
  • It shows the result as a Chrome notification
ai-text-checker/
├── manifest.json
├── background.js
└── icon128.png
Enter fullscreen mode Exit fullscreen mode

Why not call the detection API directly from the extension?

Anything inside an extension package can be read by anyone who installs it. Unzip the .crx and your sk_live_... key is right there. So the extension talks to a small backend endpoint you control, and the backend holds the key.

If you followed our earlier post, How to Add AI Content Detection to Your App with a REST API, you already have that endpoint: POST /api/check-text. Any server route that accepts { text } and returns a score will work.

Step 1: manifest.json

{
  "manifest_version": 3,
  "name": "AI Text Checker (demo)",
  "version": "1.0.0",
  "description": "Right-click selected text to check if it looks AI-generated.",
  "permissions": ["contextMenus", "notifications", "activeTab", "scripting"],
  "host_permissions": ["https://yourapp.com/*"],
  "background": { "service_worker": "background.js" },
  "icons": { "128": "icon128.png" }
}
Enter fullscreen mode Exit fullscreen mode

What each permission is for:

  • contextMenus: adds the right-click item
  • activeTab + scripting: read the selection from the current tab, only after the user clicks our menu item
  • notifications: show the result
  • host_permissions: lets the service worker call your backend without CORS problems

Ask for as little as possible. Reviewers on the Chrome Web Store check this, and so do users.

Step 2: background.js (the service worker)

const BACKEND_URL = "https://yourapp.com/api/check-text";

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: "check-ai",
    title: "Check selected text for AI",
    contexts: ["selection"],
  });
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId !== "check-ai" || !tab?.id) return;

  // Read the full selection from the page.
  // info.selectionText works too, but reading it from the page is more reliable for long selections.
  let text = info.selectionText ?? "";
  try {
    const [{ result }] = await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: () => window.getSelection()?.toString() ?? "",
    });
    if (result) text = result;
  } catch {
    // Some pages (like chrome:// pages) block scripting. Fall back to selectionText.
  }

  const words = text.trim().split(/\s+/).filter(Boolean).length;
  if (words < 50) {
    return notify("Select more text", `Only ${words} words selected. Select at least 50 words for a reliable check.`);
  }

  try {
    const res = await fetch(BACKEND_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text }),
    });
    const data = await res.json();

    if (data.status === "rate_limited") {
      return notify("Try again later", "The daily check limit was reached.");
    }
    if (data.status !== "ok") {
      return notify("Couldn't check this text", "Please try again in a moment.");
    }

    const labels = {
      high: "High likelihood of AI-generated text",
      medium: "Mixed signals: parts may be AI-generated",
      low: "Low likelihood of AI-generated text",
    };
    notify(labels[data.level], `AI probability: ${data.aiProbability}%. Treat this as one signal, not proof.`);
  } catch (e) {
    console.error(e);
    notify("Network error", "Couldn't reach the checking service.");
  }
});

function notify(title, message) {
  chrome.notifications.create({
    type: "basic",
    iconUrl: "icon128.png",
    title,
    message,
  });
}
Enter fullscreen mode Exit fullscreen mode

A few MV3 details worth knowing:

  • Service workers aren't persistent. Don't keep state in global variables between clicks. Use chrome.storage if you need it.
  • Create context menus in onInstalled. Creating them on every service-worker start throws "duplicate id" errors.
  • chrome.scripting.executeScript returns an array of results, one per frame. We only inject into the main frame here.

Step 3: Load it and test

  1. Open chrome://extensions
  2. Turn on Developer mode
  3. Click Load unpacked and pick the ai-text-checker folder
  4. Select a few paragraphs on any web page, right-click, and choose Check selected text for AI

To debug, click "service worker" on the extension's card to open its DevTools console.

Step 4: Make it production-ready

Before publishing to the Chrome Web Store:

  • Authenticate users on your backend (for example with a token from your web app), so strangers can't use your quota.
  • Rate-limit per user on the backend.
  • Write a clear privacy policy: what text you send, where it goes, and whether it's stored. The Web Store requires disclosures for extensions that handle user data.
  • Add an options page so users can set their own thresholds or turn notifications off.
  • Show results in context. A small popup next to the selection, built with a content script, is a nicer experience than a system notification.

Don't want to build it?

If you'd rather not build your own, try the ProofAI Chrome extension, which brings the same detection engine as proofai.in into your browser. Building your own is still a great way to learn MV3, and the pattern works with any detection API.

What would you add to an extension like this: highlighting, history, Google Docs support? Let us know in the comments. 👇


Disclosure: we're the team behind ProofAI at 4Born Solutions. This article was drafted with AI assistance and reviewed and edited by our team.

Top comments (0)