Orientation
Building Chrome extensions by way of structured prompts is a productivity pattern that matters when you need to iterate UI components, scaffold manifest configurations, or prototype content scripts without repeatedly opening a template repository. It is especially useful for solo engineers, side‑project prototyping, and education contexts where the overhead of tooling setup must be collapsed into a single generative step. The approach does not replace understanding the extension lifecycle; rather, it compresses the early “boilerplate → runnable” cycle so you can validate ideas faster. What follows is a concrete, prompt‑driven workflow that uses only real tools and publicly documented Chrome extension mechanics.
1. Scaffold a minimal extension from a prompt
Start with a clean directory. The following commands work unchanged on any machine with Node.js installed:
mkdir my-prompted-ext
cd my-prompted-ext
npm init -y
Install a dev‑server that can serve the extension bundle locally:
npm install --save-dev vite
Add a minimal index.html, popup.html, and content.js by way of a single prompt to an LLM. A prompt that reliably produces V3‑compliant output looks like this:
> “Generate the three files required for a Google Chrome V3 extension: a manifest.json with manifest_version, action, and permissions set to ["activeTab"]; a popup.html with a button that logs “hello” to the console; and a content.js that injects a <style> rule turning all links red. Output each file’s contents inside a fenced code block labeled with the filename.”
The LLM response, when guided to use “use strict” and omit deprecated fields, typically returns:
// manifest.json
{
"manifest_version": 3,
"name": "Prompt‑generated demo",
"version": "0.1.0",
"action": {
"default_popup": "popup.html",
"default_icon": {
"48": "icon.png"
}
},
"permissions": ["activeTab"]
}
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="go">Go</button>
<script src="popup.js"></script>
</body>
</html>
// popup.js
document.getElementById("go").addEventListener("click", () => {
console.log("hello");
});
// content.js
(() => {
"use strict";
const style = document.createElement("style");
style.textContent = "a { color: red !important; }";
document.head.appendChild(style);
})();
Copy each block into its respective file. The manifest.json above respects the V3 schema: background service workers are no longer declared in the manifest; instead they live under service_worker or are omitted if not needed.
2. Load the extension into Chrome
Chrome does not require a running server for basic loading, but you must point it at the unpacked directory. Use the CLI flag that has been stable since Chrome 88:
google-chrome --load-extension=$(pwd)/..
If you prefer the UI path, navigate to chrome://extensions/, enable Developer mode, click Load unpacked, and select the extension root. Either method results in the extension appearing in the toolbar; clicking the action opens popup.html.
Open chrome://inspect/#extensions to verify that the service worker (if present) is running and that no “Refused to evaluate script” errors appear in the console. A common first‑run error is:
Manifest file is invalid: Unexpected property 'background' at 'manifest'
This means the JSON still contains a V2‑style background field. Remove it and retry.
3. Generate and inject content scripts via prompt iteration
When you need a content script that manipulates the DOM on a specific site, restructure the prompt to include the target URL’s CSP constraints. A safe prompt pattern:
> “Write a content script that adds a red border to every <img> element on https://example.com. The script must not use eval and must declare run_at: 'document_idle' in its manifest.json matches entry. Output the script as a single JS block labeled content.js.”
The produced script typically follows the document_idle injection timing, which avoids flash of unstyled content. If your extension targets sites with strict CSP (e.g., script-src 'self'), the prompt must explicitly ask the model to avoid document.write and to use appendChild for node insertion. Failure to do so yields the runtime error:
Refused to execute script from 'data:' because it violates the following Content Security Policy directive: "script-src 'self'".
In that case, rewrite the script to create a <script> element via document.createElement and set its src to a relative URL hosted on your own server, or inline the logic via a <style> node as shown in the earlier example.
4. Hot‑reloading and iterative prompts
For rapid iteration, pair the prompt workflow with Vite’s hot‑module replacement. Add a vite.config.js:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: '.',
publicDir: '.',
build: { rollupOptions: { input: 'popup.html' } }
});
Run:
npx vite
Vite serves the extension on localhost:3000 (or another free port). Chrome’s extension loader can point at the dev server origin, but you must whitelist the URL in manifest.json under content_security_policy:
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; connect-src *"
}
After saving a modified popup.js, Vite pushes the update. Chrome reloads the popup automatically if the window is already open. If the reload does not fire, open chrome://extensions/, find your extension, and click the Reload link. This cycle—prompt → code → npx vite → Chrome reload—lets you iterate UI behavior without manual file edits beyond the initial prompt output.
Sharp edges you will actually hit
| Symptom | Root cause | Fix |
|---|---|---|
| Extension appears disabled, no action popup | default_popup points to a missing file or has a typo | Verify the exact filename in manifest.json matches a file present in the root |
Console spams Cannot read properties of null (reading 'addEventListener') | Popup HTML loaded before popup.js parsed | Ensure <script src="popup.js"> is placed at the end of <body> or use DOMContentLoaded |
| Content script does not run on HTTPS pages | Missing "secure_urls" or "host_permissions" in manifest | Add "host_permissions": ["https://*.example.com/*"] and re‑load |
Refused to load media for images injected by content script | CSP blocks img srcset or cross‑origin requests | Set "img-src *" in the content_security_policy section or prepend https: to relative URLs |
| “Invalid manifest” error after prompt‑generated JSON | Model omitted manifest_version or used a non‑numeric value | Always verify the first key is "manifest_version": 3; the value must be the integer 3 |
A gotcha that catches even experienced developers: Chrome caches the extension ID and service worker across reloads. If you change the name or version field and expect a fresh install, you must either bump the version or remove the extension from chrome://extensions/ and click Remove before Load unpacked again. Prompt‑generated manifests sometimes omit version entirely, defaulting to "0.0.0" in the model’s output; always confirm this field exists and is a semver string before the first google-chrome --load-extension run.
Checklist before you ship a prompt‑generated extension
- [ ]
manifest.jsoncontains exactly:manifest_version: 3,name,version, and no V2‑style fields (background,content_security_policyat the top level). - [ ]
action.default_popupreferences a file that exists at the extension root. - [ ]
host_permissionsandpermissionslists are minimal—grant only what the script actually reads. - [ ]
content_security_policyis set for bothextension_pagesand, if you use remote resources,browser_action/actioncontexts. - [ ] The extension loads without errors in
chrome://extensions/andchrome://inspect/#extensions. - [ ] A manual click on the toolbar icon produces the expected UI response.
Using prompts to generate these files saves the repetitive labor of copying templates, but the above validations remain identical to a hand‑crafted extension. The prompt is the scaffolding accelerator; the Chrome mechanics are non‑negotiable.
This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.