defineDynamicResource() is a helper function that creates web_accessible_resources entries for dynamically imported content script resources. It automatically manages resource paths for CSS, images, and other assets imported by content scripts.
Usage
import { defineManifest, defineDynamicResource } from '@crxjs/vite-plugin'
export default defineManifest({
manifest_version: 3,
name: 'My Extension',
version: '1.0.0',
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*', 'https://github.com/*'],
}),
],
})
Function Signature
function defineDynamicResource({
matches,
use_dynamic_url,
}: Omit<WebAccessibleResourceByMatch, 'resources'>): WebAccessibleResourceByMatch
Parameters
matches
string[]
default:"['http://*/*', 'https://*/*']"
Array of match patterns that specify which web pages can access the resources.defineDynamicResource({
matches: [
'https://example.com/*',
'https://*.github.com/*',
'http://localhost/*',
],
})
If omitted, defaults to ['http://*/*', 'https://*/*'] which allows all HTTP and HTTPS origins.
If true, resources can be accessed via chrome.runtime.getURL() with dynamically generated URLs.defineDynamicResource({
matches: ['https://example.com/*'],
use_dynamic_url: true,
})
Return Type
interface WebAccessibleResourceByMatch {
matches: string[]
resources: ['<dynamic_resource>']
use_dynamic_url?: boolean
}
The function returns a web_accessible_resources entry with:
- The provided
matches array
- A special
resources array containing '<dynamic_resource>'
- The optional
use_dynamic_url flag
The <dynamic_resource> sentinel value tells CRXJS to automatically populate this array with all resources imported by your content scripts during the build process.
Why Dynamic Resources?
Chrome Manifest V3 requires that content script resources (CSS, images, fonts, etc.) be declared in web_accessible_resources with specific match patterns. This prevents unauthorized websites from accessing your extension’s resources.
However, managing these resource lists manually is error-prone:
// ❌ Manual approach - easy to forget files
web_accessible_resources: [{
matches: ['https://example.com/*'],
resources: [
'assets/content-style-abc123.css',
'assets/icon-def456.png',
'assets/font-ghi789.woff2',
// Did we list everything?
]
}]
defineDynamicResource() automates this:
// ✓ Automatic approach - CRXJS tracks all imports
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*'],
}),
]
Examples
Basic Usage
import { defineManifest, defineDynamicResource } from '@crxjs/vite-plugin'
export default defineManifest({
manifest_version: 3,
name: 'My Extension',
version: '1.0.0',
content_scripts: [{
matches: ['https://example.com/*'],
js: ['src/content.ts'],
}],
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*'],
}),
],
})
Multiple Match Patterns
web_accessible_resources: [
defineDynamicResource({
matches: [
'https://google.com/*',
'https://*.github.com/*',
'https://stackoverflow.com/*',
],
}),
]
With Dynamic URLs
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*'],
use_dynamic_url: true,
}),
]
Multiple Resource Groups
You can define multiple dynamic resource groups for different match patterns:
web_accessible_resources: [
// Resources for Google sites
defineDynamicResource({
matches: ['https://*.google.com/*'],
}),
// Resources for GitHub
defineDynamicResource({
matches: ['https://github.com/*', 'https://*.github.com/*'],
}),
// Resources for local development
defineDynamicResource({
matches: ['http://localhost/*'],
}),
]
How It Works
During the build process, CRXJS:
- Analyzes your content script imports (CSS, images, fonts, etc.)
- Tracks all assets that need to be web-accessible
- Replaces
<dynamic_resource> with the actual list of generated asset paths
- Applies the match patterns you specified
Example Build Output
Given this source:
// manifest.config.ts
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*'],
}),
]
// src/content.ts
import './content.css'
import iconUrl from './icon.png'
CRXJS generates this manifest:
{
"web_accessible_resources": [{
"matches": ["https://example.com/*"],
"resources": [
"assets/content-abc123.css",
"assets/icon-def456.png"
]
}]
}
Match Pattern Syntax
Match patterns follow the Chrome extension match pattern format:
*://example.com/* - Any protocol
https://*/* - All HTTPS sites
https://*.google.com/* - Google and all subdomains
https://example.com/path/* - Specific path prefix
file:///*.mp3 - Local files
Be specific with match patterns. Avoid using *://*/* unless your extension truly needs to expose resources to all websites.
TypeScript Types
WebAccessibleResourceByMatch
interface WebAccessibleResourceByMatch {
matches: string[]
resources: string[]
use_dynamic_url?: boolean
}
DYNAMIC_RESOURCE Constant
const DYNAMIC_RESOURCE = '<dynamic_resource>' as const
This sentinel value is replaced by CRXJS during the build process.
Best Practices
Match Content Script Patterns
Your web_accessible_resources matches should align with your content_scripts matches:
export default defineManifest({
manifest_version: 3,
name: 'My Extension',
version: '1.0.0',
content_scripts: [{
matches: ['https://example.com/*', 'https://github.com/*'],
js: ['src/content.ts'],
}],
web_accessible_resources: [
// Same match patterns as content_scripts
defineDynamicResource({
matches: ['https://example.com/*', 'https://github.com/*'],
}),
],
})
Separate Dev and Prod Patterns
Use different match patterns for development and production:
import type { ConfigEnv } from 'vite'
export default defineManifest((env: ConfigEnv) => {
const isDev = env.mode === 'development'
return {
manifest_version: 3,
name: 'My Extension',
version: '1.0.0',
web_accessible_resources: [
defineDynamicResource({
matches: isDev
? ['http://localhost/*', 'https://*/*']
: ['https://example.com/*'],
}),
],
}
})
Minimize Exposure
Only expose resources to the origins that need them:
// ❌ Too permissive
web_accessible_resources: [
defineDynamicResource({
matches: ['*://*/*'], // All sites can access resources
}),
]
// ✓ Specific and secure
web_accessible_resources: [
defineDynamicResource({
matches: ['https://example.com/*'], // Only example.com
}),
]
Limitations
Content Scripts Only
defineDynamicResource() is designed for content script resources. For other web-accessible resources, define them manually:
web_accessible_resources: [
// Dynamic content script resources
defineDynamicResource({
matches: ['https://example.com/*'],
}),
// Static resources accessed by web pages
{
matches: ['https://example.com/*'],
resources: ['public/injected-script.js'],
},
]
No Firefox Support
Firefox uses a different format for web_accessible_resources. If you’re targeting Firefox, you may need conditional logic:
import type { ConfigEnv } from 'vite'
export default defineManifest((env: ConfigEnv) => {
const isFirefox = process.env.BROWSER === 'firefox'
return {
manifest_version: 3,
name: 'My Extension',
version: '1.0.0',
web_accessible_resources: isFirefox
? undefined // Handle Firefox separately
: [
defineDynamicResource({
matches: ['https://example.com/*'],
}),
],
}
})