# How defineDynamicResource Simplifies Web Accessible Resources in CRXJS Chrome Extensions

> Learn how defineDynamicResource simplifies web accessible resources in CRXJS Chrome extensions. This function creates type-safe placeholders for dynamic assets, resolving them to actual file names during the build.

- Repository: [crxjs/chrome-extension-tools](https://github.com/crxjs/chrome-extension-tools)
- Tags: how-to-guide
- Published: 2026-02-28

---

**The `defineDynamicResource` function exported from [`packages/vite-plugin/src/node/defineManifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/defineManifest.ts) creates a type-safe placeholder entry for dynamic web accessible resources that the CRXJS Vite plugin automatically resolves to actual bundled file names during the build process.**

The `defineDynamicResource` helper in the `crxjs/chrome-extension-tools` repository eliminates manual enumeration of script hashes in Chrome Extension Manifest V3 files. This function abstracts the boilerplate required to declare resources that will be injected at runtime, while the build system handles the concrete file name resolution and cross-browser compatibility.

## How defineDynamicResource Works in the Build Pipeline

### Creating the Placeholder Entry

When invoked, `defineDynamicResource` returns a manifest entry object containing three key properties. The `matches` field specifies URL patterns that may request the resource. The `resources` array contains a single sentinel value `DYNAMIC_RESOURCE` (the literal string `<dynamic_resource>` defined at lines 71-72 of [`defineManifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/defineManifest.ts)). Finally, the `use_dynamic_url` boolean flag tells Chrome whether to generate a dynamic `chrome.runtime.getURL` for the resource.

### Default Configuration Values

The function provides sensible defaults when options are omitted. If you call it without arguments, it supplies the most permissive match set—`http://*/*` and `https://*/*`—and sets `use_dynamic_url` to false (lines 60-62). This zero-config approach works out-of-the-box while still allowing explicit overrides for specific domains.

### Build-Time Processing in plugin-webAccessibleResources.ts

During the `renderCrxManifest` hook in [`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts), the plugin scans the user's `web_accessible_resources` array for the `DYNAMIC_RESOURCE` placeholder. When detected, the plugin executes three critical operations (lines 94-104):

1. **Strips the placeholder** from the declaration to ensure the final manifest never contains the literal string `<dynamic_resource>` (lines 98-102).
2. **Collects match patterns** into a `Set` named `dynamicScriptMatches`, aggregating all URLs specified by the developer.
3. **Propagates the flag** by storing the `use_dynamic_url` value in `dynamicScriptDynamicUrl` for later application to generated resources (lines 103-104).

### Resolving Dynamic Scripts to Real Resources

After Vite emits the build file manifest, the plugin walks through all content-script entries. For each script identified as dynamic (`isDynamicScript === true`), the plugin assigns the collected `dynamicScriptMatches` and the `use_dynamic_url` flag to the generated web accessible resource entry. The placeholder transforms into actual file names—including the bundled script hash and any imported assets—ensuring the runtime can access the correct URLs.

### Final Manifest Cleanup

The plugin performs post-processing to ensure valid output. Duplicate web accessible resource entries are merged, and the placeholder is completely removed from the final JSON. For browsers that do not support the `use_dynamic_url` property (such as Firefox), the plugin strips this field (lines 26-29), maintaining cross-browser compatibility without manual intervention.

## Implementing defineDynamicResource in Your Extension

### Declaring Dynamic Resources in the Manifest

Define your dynamic web accessible resources in the manifest configuration file using the helper function:

```typescript
// src/manifest.ts
import { defineManifest, defineDynamicResource } from 'crx/vite-plugin'

export const manifest = defineManifest({
  name: 'My Extension',
  version: '1.0',
  manifest_version: 3,
  web_accessible_resources: [
    // All dynamic scripts can be loaded from any HTTPS origin
    defineDynamicResource({
      matches: ['https://example.com/*'],
      use_dynamic_url: true,
    }),
  ],
  // …other manifest fields…
})

```

This call produces a web accessible resource entry containing the placeholder `<dynamic_resource>` and your specified match pattern.

### Internal Build Transformation

The plugin transforms your declaration during the build. Here is the simplified internal logic from [`plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-webAccessibleResources.ts):

```typescript
// Internally (simplified from lines 94-104)
for (const r of manifest.web_accessible_resources) {
  if (r.resources.includes(DYNAMIC_RESOURCE)) {
    // Remove placeholder, collect matches, remember flag
    const matches = new Set(r.matches)
    const useDynamic = r.use_dynamic_url ?? false
    
    // Later, when Vite emits a dynamic script file:
    web_accessible_resources.push({
      matches: [...matches],
      resources: ['assets/dynamic-script.abc123.js'],
      use_dynamic_url: useDynamic,
    })
  }
}

```

### Accessing Dynamic URLs at Runtime

When `use_dynamic_url` is enabled, Chrome rewrites `chrome.runtime.getURL` to return temporary URLs that remain valid even when script hashes change:

```typescript
// content script (dynamic)
import scriptUrl from './dynamic-script?script'

// Chrome resolves it to the actual bundled file
chrome.runtime.getURL(scriptUrl).then(url => {
  const s = document.createElement('script')
  s.src = url
  document.head.append(s)
})

```

## Summary

- **`defineDynamicResource`** creates a placeholder entry in [`packages/vite-plugin/src/node/defineManifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/defineManifest.ts) with the sentinel value `DYNAMIC_RESOURCE` (`<dynamic_resource>`), match patterns, and the `use_dynamic_url` flag.
- The **default configuration** provides permissive URL matches (`http://*/*`, `https://*/*`) and disables dynamic URLs by default for immediate usability.
- During the **`renderCrxManifest` hook** in [`plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-webAccessibleResources.ts), the plugin strips the placeholder, aggregates matches into `dynamicScriptMatches`, and preserves the `use_dynamic_url` setting.
- The system **automatically resolves** placeholder entries to actual bundled file names (including content hashes) for all scripts marked with `isDynamicScript === true`.
- **Cross-browser compatibility** is handled by stripping `use_dynamic_url` for unsupported browsers like Firefox (lines 26-29).

## Frequently Asked Questions

### What is the DYNAMIC_RESOURCE sentinel value?

The `DYNAMIC_RESOURCE` constant is a literal string value `<dynamic_resource>` defined in [`packages/vite-plugin/src/node/defineManifest.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/defineManifest.ts) at lines 71-72. It serves as a marker that the [`plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-webAccessibleResources.ts) build hook detects and replaces with actual file names during the Vite build process. This sentinel allows developers to declare "any script that will be injected at runtime" without knowing the final hashed file names in advance.

### Does defineDynamicResource work with Firefox?

Yes, but with compatibility adjustments. The `use_dynamic_url` flag is specific to Chrome's Manifest V3 implementation. During final manifest cleanup in [`plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/plugin-webAccessibleResources.ts) (lines 26-29), the plugin detects Firefox and strips the `use_dynamic_url` property from the output. The dynamic resource handling itself works across browsers, but the dynamic URL generation feature is Chrome-specific.

### How does use_dynamic_url affect chrome.runtime.getURL?

When `use_dynamic_url` is set to true, Chrome generates a dynamic URL via `chrome.runtime.getURL` that remains stable even when the underlying file hash changes between builds. This allows content scripts to inject sub-resources using `chrome.runtime.getURL` without hardcoding specific asset hashes. The CRXJS plugin propagates this flag from your `defineDynamicResource` call to the final generated web accessible resource entries.

### Where does the actual file name substitution happen?

The substitution occurs in [`packages/vite-plugin/src/node/plugin-webAccessibleResources.ts`](https://github.com/crxjs/chrome-extension-tools/blob/main/packages/vite-plugin/src/node/plugin-webAccessibleResources.ts) during the `renderCrxManifest` hook. The plugin scans for entries containing `DYNAMIC_RESOURCE` (lines 94-103), removes the placeholder, and later assigns actual file names (like [`assets/dynamic-script.abc123.js`](https://github.com/crxjs/chrome-extension-tools/blob/main/assets/dynamic-script.abc123.js)) when processing content scripts where `isDynamicScript === true`. This happens after Vite has generated the final asset hashes but before the manifest is written to disk.