Type-safe Chrome Extension Manifest V3 definition utilities with TypeScript support, file path validation, and dynamic resource management. Provides comprehensive manifest schema with intelligent defaults and validation.
Helper function for defining Chrome extension manifests with full TypeScript type safety and file path validation.
/**
* Define a Chrome extension manifest with TypeScript support
* @param manifest - Manifest configuration object, promise, or function
* @returns ManifestV3Export for use with crx plugin
*/
function defineManifest<T extends string = string>(
manifest: ManifestV3Options<T>
): ManifestV3Export;
type ManifestV3Export = ManifestV3 | Promise<ManifestV3> | ManifestV3Fn;
type ManifestV3Fn = (env: ConfigEnv) => ManifestV3 | Promise<ManifestV3>;
type ManifestV3Options<T extends string> = ManifestOptions<T> | Promise<ManifestOptions<T>> | ManifestV3Define<T>;
type ManifestV3Define<T extends string> = (env: ConfigEnv) => ManifestOptions<T> | Promise<ManifestOptions<T>>;Usage Examples:
import { defineManifest } from "@crxjs/vite-plugin";
// Static manifest
const manifest = defineManifest({
manifest_version: 3,
name: "My Extension",
version: "1.0.0",
background: {
service_worker: "src/background.ts",
},
content_scripts: [
{
matches: ["https://example.com/*"],
js: ["src/content.ts"],
css: ["src/content.css"],
},
],
action: {
default_popup: "src/popup.html",
default_icon: {
16: "icons/icon-16.png",
32: "icons/icon-32.png",
},
},
});
// Dynamic manifest with environment-based configuration
const manifest = defineManifest((env) => ({
manifest_version: 3,
name: env.mode === "development" ? "My Extension (Dev)" : "My Extension",
version: "1.0.0",
background: {
service_worker: "src/background.ts",
},
permissions: env.mode === "development"
? ["storage", "activeTab", "debugger"]
: ["storage", "activeTab"],
}));
// Async manifest loading
const manifest = defineManifest(async () => {
const config = await import("./extension-config.json");
return {
manifest_version: 3,
name: config.name,
version: config.version,
background: {
service_worker: "src/background.ts",
},
};
});Type-safe file path validation ensures paths are relative to the Vite project root and properly formatted.
type ManifestFilePath<T extends string> =
T extends `${Code}${string}`
? never
: T extends `${string}.${infer Ext}`
? Ext extends ''
? never
: T
: never;
type Code = '.' | '/' | '\\';
interface ManifestIcons<T extends string> {
[size: number]: ManifestFilePath<T>;
}File Path Requirements:
vite.config.js is located)./, /, or \\"subdir/file.ext" (correct) vs "./subdir/file.ext" (incorrect)Examples:
// ✅ Correct file paths
const goodManifest = defineManifest({
background: {
service_worker: "src/background.ts", // ✅
},
action: {
default_popup: "src/popup.html", // ✅
default_icon: {
16: "icons/icon-16.png", // ✅
},
},
content_scripts: [{
js: ["src/content.ts"], // ✅
css: ["styles/content.css"], // ✅
}],
});
// ❌ TypeScript will prevent these invalid paths
const badManifest = defineManifest({
background: {
service_worker: "./src/background.ts", // ❌ starts with ./
},
action: {
default_popup: "/src/popup.html", // ❌ starts with /
},
});Helper for defining dynamic web-accessible resources with match patterns for content scripts.
/**
* Define dynamic web-accessible resources for content scripts
* @param options - Match patterns and URL configuration
* @returns WebAccessibleResourceByMatch configuration
*/
function defineDynamicResource(options: {
matches?: string[];
use_dynamic_url?: boolean;
}): WebAccessibleResourceByMatch;
interface WebAccessibleResourceByMatch {
matches: string[];
resources: string[];
use_dynamic_url?: boolean;
}
const DYNAMIC_RESOURCE = '<dynamic_resource>' as const;Usage Examples:
import { defineManifest, defineDynamicResource } from "@crxjs/vite-plugin";
const manifest = defineManifest({
manifest_version: 3,
name: "My Extension",
version: "1.0.0",
web_accessible_resources: [
// Define dynamic resources for content scripts
defineDynamicResource({
matches: ["https://example.com/*", "https://test.com/*"],
use_dynamic_url: true,
}),
// Manual resource definition
{
matches: ["https://api.example.com/*"],
resources: ["assets/api-styles.css"],
},
],
});Dynamic Resource Features:
['http://*/*', 'https://*/*'] if not specifiedComplete Chrome Extension Manifest V3 interface with all standard fields and Chrome-specific extensions.
interface ManifestV3 {
// Required fields
manifest_version: number;
name: string;
version: string;
// Recommended fields
default_locale?: string;
description?: string;
icons?: chrome.runtime.ManifestIcons;
// Extension functionality
action?: chrome.runtime.ManifestAction;
background?: ChromeManifestBackground | FirefoxManifestBackground;
content_scripts?: ContentScriptDefinition[];
web_accessible_resources?: (WebAccessibleResourceByMatch | WebAccessibleResourceById)[];
permissions?: string[];
optional_permissions?: string[];
host_permissions?: string[];
// Additional Chrome extension fields (50+ more available)
options_page?: string;
devtools_page?: string;
content_security_policy?: {
extension_pages?: string;
sandbox?: string;
};
// ... and many more
}
interface ChromeManifestBackground {
service_worker: string;
type?: 'module' | string; // If service worker uses ES modules
}
interface FirefoxManifestBackground {
scripts: string[];
persistent?: false;
}
interface ContentScriptDefinition {
matches?: string[];
exclude_matches?: string[];
js?: string[];
css?: string[];
run_at?: 'document_start' | 'document_end' | 'document_idle';
all_frames?: boolean;
match_about_blank?: boolean;
include_globs?: string[];
exclude_globs?: string[];
}Environment-based Configuration:
import { defineConfig } from "vite";
import { crx, defineManifest } from "@crxjs/vite-plugin";
const manifest = defineManifest(({ mode }) => ({
manifest_version: 3,
name: mode === "development" ? "My Extension (Dev)" : "My Extension",
version: "1.0.0",
permissions: [
"storage",
"activeTab",
...(mode === "development" ? ["debugger"] : []),
],
content_security_policy: {
extension_pages: mode === "development"
? "script-src 'self' 'unsafe-eval'; object-src 'self'"
: "script-src 'self'; object-src 'self'",
},
}));Multi-browser Support:
const manifest = defineManifest({
manifest_version: 3,
name: "Cross-browser Extension",
version: "1.0.0",
background: {
service_worker: "src/background.ts",
// Firefox compatibility
scripts: ["src/background.ts"],
persistent: false,
} as any, // Type assertion for mixed browser support
});
export default defineConfig({
plugins: [
crx({
manifest,
browser: process.env.TARGET_BROWSER === 'firefox' ? 'firefox' : 'chrome',
}),
],
});