Chrome-Erweiterung programmieren: Schritt für Schritt mit Manifest V3
Von der leeren manifest.json bis zur funktionierenden Erweiterung mit Popup, Service Worker und Kontextmenü: ein vollständiges Beispiel zum Nachbauen.
Eine Chrome-Erweiterung programmieren Sie mit einer Datei manifest.json im Format Manifest V3 und ein paar HTML- und JavaScript-Dateien. Den Ordner laden Sie unter chrome://extensions im Entwicklermodus, und die Erweiterung läuft sofort. Diese Anleitung baut ein vollständiges, kleines Beispiel: einen Lesezeit-Schätzer mit Popup, Service Worker und Kontextmenü.
Was Sie brauchen
- Einen aktuellen Chrome oder einen anderen Chromium-Browser wie Edge oder Brave.
- Einen Texteditor, zum Beispiel VS Code.
- Grundkenntnisse in JavaScript, insbesondere
asyncundawait.
Ein Build-Werkzeug ist für den Anfang nicht nötig. Sobald das Projekt wächst, lohnt sich ein Setup mit TypeScript und Bundler, das im Bereich Programmiergrundlagen beschrieben ist.
Das Beispielprojekt
Die Erweiterung soll zwei Dinge können:
- Ein Klick auf das Symbol öffnet ein Popup, das die Wörter der aktuellen Seite zählt und eine geschätzte Lesezeit anzeigt.
- Ein Rechtsklick auf markierten Text bietet den Eintrag „Wörter in Auswahl zählen“ an. Das Ergebnis erscheint als Zahl auf dem Symbol.
Die Ordnerstruktur sieht am Ende so aus:
lesezeit/
├── manifest.json
├── background.js
├── popup.html
├── popup.css
├── popup.js
└── icons/
├── 16.png
├── 48.png
└── 128.png
Schritt 1: die manifest.json
Das Manifest ist die einzige Pflichtdatei. Es nennt die Bestandteile und die Berechtigungen.
{
"manifest_version": 3,
"name": "Lesezeit-Schätzer",
"version": "1.0.0",
"description": "Schätzt die Lesezeit der aktuellen Seite und zählt Wörter in markiertem Text.",
"icons": { "16": "icons/16.png", "48": "icons/48.png", "128": "icons/128.png" },
"action": {
"default_title": "Lesezeit schätzen",
"default_popup": "popup.html"
},
"background": { "service_worker": "background.js" },
"permissions": ["activeTab", "scripting", "contextMenus", "storage"]
}
Zu den Feldern:
manifest_versionmuss3sein. Chrome lädt keine Erweiterungen nach Manifest V2 mehr. Was sich gegenüber der alten Version geändert hat, erklärt der Beitrag Manifest V2 vs. V3.versionbesteht aus bis zu vier durch Punkte getrennten Zahlen und muss bei jedem Store-Update steigen.actionsteht für das Symbol in der Symbolleiste.default_popuplegt fest, welche Seite beim Klick erscheint.permissionsenthält nur, was wirklich gebraucht wird.activeTabgibt Zugriff auf den aktuellen Tab, aber erst, wenn jemand die Erweiterung aktiv aufruft. Deshalb braucht das Beispiel keinen Zugriff auf alle Websites.
Schritt 2: das Popup
Das Popup ist eine normale HTML-Seite. Wichtig: Unter Manifest V3 sind Inline-Skripte in Erweiterungsseiten verboten. JavaScript muss als eigene Datei eingebunden werden.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<h1>Lesezeit</h1>
<p id="ergebnis">Wird berechnet …</p>
<script src="popup.js"></script>
</body>
</html>
Die Logik in popup.js fragt den aktiven Tab ab und führt dort eine kleine Funktion aus, die die Wörter zählt:
const WOERTER_PRO_MINUTE = 200; // grobe Annahme, frei anpassbar
const ausgabe = document.getElementById("ergebnis");
async function berechnen() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const [{ result: woerter }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.body.innerText.split(/\s+/).filter(Boolean).length,
});
const minuten = Math.max(1, Math.round(woerter / WOERTER_PRO_MINUTE));
ausgabe.textContent = `${woerter} Wörter, etwa ${minuten} Min. Lesezeit`;
}
berechnen().catch(() => {
ausgabe.textContent = "Auf dieser Seite nicht verfügbar.";
});
chrome.scripting.executeScript benötigt die Berechtigung scripting. Die übergebene Funktion läuft im Kontext der Webseite, nicht im Popup. Sie kann deshalb keine Variablen aus popup.js sehen, nur ihr Rückgabewert kommt zurück. Der catch-Zweig fängt Seiten ab, auf denen Chrome keine Skripte erlaubt, etwa chrome://-Seiten.
Für popup.css genügt eine Breite, sonst wird das Popup sehr schmal:
body { width: 260px; font-family: system-ui, sans-serif; padding: 0.5rem 1rem; }
Schritt 3: der Service Worker
Der Service Worker in background.js läuft ohne Fenster und reagiert auf Ereignisse. Hier legt er bei der Installation den Kontextmenü-Eintrag an und verarbeitet Klicks darauf.
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "woerter-zaehlen",
title: "Wörter in Auswahl zählen",
contexts: ["selection"],
});
});
chrome.contextMenus.onClicked.addListener(async (info) => {
if (info.menuItemId !== "woerter-zaehlen") return;
const anzahl = info.selectionText.split(/\s+/).filter(Boolean).length;
await chrome.storage.local.set({ letzteAuswahl: anzahl });
await chrome.action.setBadgeText({ text: String(anzahl) });
});
Zwei Regeln sollten Sie von Anfang an einhalten:
- Listener auf oberster Ebene registrieren. Chrome beendet den Service Worker nach kurzer Inaktivität und startet ihn beim nächsten Ereignis neu. Nur Listener, die beim Start synchron registriert werden, empfangen dieses Ereignis zuverlässig.
- Keinen Zustand in Variablen halten. Globale Variablen gehen beim Beenden verloren. Was bleiben soll, gehört in
chrome.storage.
Wie Service Worker, Popup und Content Scripts zusammenspielen und Nachrichten austauschen, vertieft der Artikel Aufbau einer Browser-Erweiterung.
Schritt 4: optional ein Content Script
Das Beispiel injiziert Code nur bei Bedarf. Soll eine Erweiterung dagegen auf bestimmten Seiten immer aktiv sein, tragen Sie ein Content Script fest im Manifest ein:
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
Das Muster in matches bestimmt, auf welchen Adressen das Skript läuft. Jedes Muster erzeugt bei der Installation einen Berechtigungshinweis. Breite Muster wie <all_urls> führen zu der Warnung, die Erweiterung könne alle Daten auf allen Websites lesen und ändern. Das schreckt Nutzer ab und verlängert in der Regel die Prüfung im Store. Bevorzugen Sie deshalb, wie im Beispiel, activeTab mit scripting.
Optional: eine Optionsseite für Einstellungen
Die Lesegeschwindigkeit von 200 Wörtern pro Minute ist nur eine Annahme. Wer schneller oder langsamer liest, soll den Wert selbst festlegen können. Dafür gibt es die Optionsseite, die Sie im Manifest mit "options_page": "options.html" registrieren. Chrome verlinkt sie dann in den Details der Erweiterung und im Kontextmenü des Symbols.
Die Optionsseite ist wie das Popup eine normale HTML-Seite mit eigener Skriptdatei. Sie schreibt den gewählten Wert in den Speicher:
// options.js
const feld = document.getElementById("tempo");
const { tempo = 200 } = await chrome.storage.sync.get("tempo");
feld.value = tempo;
feld.addEventListener("change", () => {
chrome.storage.sync.set({ tempo: Number(feld.value) });
});
Binden Sie das Skript mit <script type="module" src="options.js"></script> ein, damit das await auf oberster Ebene funktioniert. chrome.storage.sync gleicht die Einstellung über das Google-Konto zwischen Geräten ab, sofern die Synchronisierung aktiv ist. Für größere Datenmengen ist chrome.storage.local die bessere Wahl, weil der synchronisierte Speicher eng begrenzt ist. Im Popup lesen Sie den Wert anschließend mit chrome.storage.sync.get("tempo") statt mit der festen Konstante. So bleibt die Logik an einer Stelle, und das Popup reagiert sofort auf geänderte Einstellungen, weil es bei jedem Öffnen neu geladen wird.
Schritt 5: laden, testen, Fehler finden
- Öffnen Sie
chrome://extensionsund aktivieren Sie oben rechts den Entwicklermodus. - Klicken Sie auf „Entpackte Erweiterung laden“ und wählen Sie den Ordner
lesezeit. - Heften Sie das Symbol über das Puzzle-Symbol in der Symbolleiste an und öffnen Sie eine beliebige Webseite.
- Klicken Sie auf das Symbol. Das Popup zeigt Wortzahl und Lesezeit.
- Markieren Sie Text, öffnen Sie das Kontextmenü und wählen Sie „Wörter in Auswahl zählen“.
Nach jeder Code-Änderung klicken Sie auf der Erweiterungskarte auf das Neuladen-Symbol. Geöffnete Tabs müssen Sie bei Content Scripts zusätzlich neu laden. Fehler finden Sie an drei Stellen:
| Bestandteil | Wo Sie Fehler sehen |
|---|---|
| Popup | Rechtsklick ins geöffnete Popup, dann „Untersuchen“ |
| Service Worker | Link „Service Worker“ auf der Erweiterungskarte |
| Content Script | Entwicklerwerkzeuge der Webseite, Konsole |
| Manifest | Fehlermeldung direkt beim Laden auf der Erweiterungskarte |
Schritt 6: Icons, Paket und Store
Für den Store brauchen Sie mindestens ein Symbol in 128 × 128 Pixeln, besser alle drei Größen aus dem Manifest. Hochgeladen wird ein ZIP-Archiv des Ordners, wobei die manifest.json auf oberster Ebene liegen muss, nicht in einem Unterordner.
Vor der Einreichung lohnt ein letzter Blick auf die Berechtigungen: Wird jede davon tatsächlich verwendet? Unbenutzte Berechtigungen gehören zu den häufigsten Ablehnungsgründen. Welche Kosten, Prüfzeiten und Stolpersteine im Chrome Web Store auf Sie zukommen, beschreibt der Beitrag Chrome Web Store: Gebühr, Prüfdauer und Ablehnungsgründe.
Häufige Fehler beim ersten Projekt
| Symptom | Ursache | Lösung |
|---|---|---|
| Popup bleibt leer, Konsole meldet CSP-Fehler | Inline-Skript im HTML | Skript in eigene Datei auslagern |
| Kontextmenü fehlt | Listener für onInstalled nicht ausgelöst |
Erweiterung entfernen und neu laden |
Cannot access a chrome:// URL |
interne Seite | Fehler abfangen, Hinweis anzeigen |
| Daten nach einiger Zeit weg | Service Worker wurde beendet | chrome.storage statt Variablen |
chrome.scripting ist undefined |
Berechtigung fehlt | scripting in permissions ergänzen |
Berechtigungen von Anfang an sparsam halten
Jede Berechtigung im Manifest prüft der Store, und viele davon erscheinen bei der Installation als Warnung. Gewöhnen Sie sich deshalb schon im ersten Projekt an, nur das anzufordern, was der Code tatsächlich nutzt:
activeTabstatt Zugriff auf alle Websites, wenn die Erweiterung erst nach einem Klick auf die aktuelle Seite zugreift.host_permissionseng fassen, also auf die benötigten Domains statt auf<all_urls>.- Selten genutzte Funktionen optional machen: Unter
optional_permissionseingetragene Rechte fordern Sie erst bei Bedarf mitchrome.permissions.request()an. - Nichts auf Vorrat eintragen: Kommt bei einem Update eine Berechtigung mit neuer Warnung hinzu, deaktiviert Chrome die Erweiterung, bis Nutzer zustimmen.
Wie Nutzer solche Hinweise lesen, erklärt der Beitrag Welche Berechtigungen von Browser-Erweiterungen sind gefährlich?.
Wie es weitergeht
Mit diesem Gerüst haben Sie alle wichtigen Bausteine einmal benutzt. Sinnvolle nächste Schritte sind eine Optionsseite für die Lesegeschwindigkeit, Mehrsprachigkeit über den Ordner _locales und die Methode chrome.i18n.getMessage sowie automatisierte Tests. Da Edge, Brave und Opera auf Chromium basieren, läuft dieselbe Erweiterung dort in der Regel ohne Änderung. Für Firefox und Safari sind kleinere Anpassungen nötig, vor allem beim Hintergrundteil im Manifest.
Fragen und Antworten
- Welche Programmiersprache brauche ich für eine Chrome-Erweiterung?
- JavaScript, HTML und CSS. Viele Projekte nutzen TypeScript, das vor dem Laden in JavaScript übersetzt wird. Andere Sprachen kommen nur über WebAssembly in Frage, was für den Einstieg unnötig ist.
- Kostet es Geld, eine Chrome-Erweiterung zu entwickeln?
- Entwickeln und lokal testen ist kostenlos. Erst für die Veröffentlichung im Chrome Web Store fällt eine einmalige Registrierungsgebühr für das Entwicklerkonto an.
- Warum funktioniert meine Erweiterung auf chrome://-Seiten nicht?
- Chrome erlaubt Erweiterungen keinen Skriptzugriff auf interne Seiten wie chrome://settings oder auf den Chrome Web Store selbst. Fangen Sie diesen Fall im Code ab und zeigen Sie einen Hinweis an, statt einen Fehler zu werfen.
- Kann ich die Erweiterung ohne Store an Kollegen weitergeben?
- Zum Testen können andere den entpackten Ordner im Entwicklermodus laden. Für den dauerhaften Einsatz in einer Organisation ist die Verteilung über den Store, auch als nicht gelistete oder private Erweiterung, oder über Unternehmensrichtlinien der saubere Weg.
Weiterlesen
- Aufbau einer Erweiterung
Welche Teile eine Erweiterung hat, in welchem Kontext jeder läuft, was er darf und wie die Teile über Nachrichten und Speicher zusammenarbeiten.
- Manifest V2 vs. V3
Service Worker statt Hintergrundseite, Regeln statt blockierendem webRequest, kein nachgeladener Code: die Unterschiede im Überblick und wie Sie migrieren.
- Chrome Web Store
Was das Entwicklerkonto kostet, wie lange die Prüfung dauert, warum Erweiterungen abgelehnt werden und was Sie vor der Einreichung prüfen sollten.
- Grundlagen
Sprachen, Schnittstellen, Build-Werkzeuge, Versionskontrolle und Tests: das Handwerkszeug, das für Erweiterungen jeder Plattform gebraucht wird.