Output manifest
The DOMStack manifest is build metadata for service workers, deployment tools, and other build-time integrations.
It is not a Web App Manifest.
A Web App Manifest such as site.webmanifest can be generated independently with a template.
A generated manifest resembles:
// public/domstack-manifest.json
{
"$schema": "https://unpkg.com/@domstack/static@<version>/lib/domstack-manifest/schema.json",
"version": "a1b2c3...",
"generatedAt": "2026-08-31T12:00:00.000Z",
"entries": [
{
"outputRelname": "index.html",
"kind": "page",
"url": "/",
"revision": "d4e5f6...",
"bytes": 1240,
"contentType": "text/html; charset=utf-8",
"static": true,
"role": "navigation"
}
],
"policy": {
"offlineFallbackUrl": "/offline/"
}
}
When enabled, DOMStack collects its emitted pages, templates, bundles, workers, copied files, and static assets into a normalized list of public outputs.
You can filter that list, expose selected page variables, attach application policy, and consume the finalized result from a hook or programmatic build.
The finalized manifest can be injected statically into your service worker or emitted as a standalone domstack-manifest.json file.
Warning
The DOMStack manifest pipeline is an unstable preview feature.
This includes its schema, settings, hooks, policy and entry variables, and process.env.DOMSTACK_MANIFEST_* defines.
Pin @domstack/static to an exact version when building against this preview API.
The manifest lifecycle is:
DOMStack collects and reconciles emitted outputs.
2.
Excludes and entry filters run, then selected page variables are attached.
3.
DOMStack finalizes the manifest entries, root policy, and deterministic version.
4.
manifestBuilt hooks receive the finalized manifest.
5.
DOMStack bundles the site service worker with any constants defined by the hooks.
6.
DOMStack optionally writes domstack-manifest.json and returns the manifest from programmatic builds.
The site service worker is omitted from manifest entries.
This allows the finalized manifest version to be embedded in /service-worker.js without creating a circular content hash.
Enable the manifest
The manifest pipeline is disabled by default. Enable it with one of these configuration surfaces:
| Configuration | Pipeline enabled | Writes domstack-manifest.json |
|---|---|---|
One domstack-manifest.settings.ts file anywhere in src |
Yes | No |
domstackManifest: true |
Yes | Yes |
domstackManifest: { ... } |
Yes | Only with write: true |
CLI --domstackManifest |
Yes | Yes |
A settings file enables manifest reconciliation, hooks, and results.domstackManifest without requiring a public JSON file.
This is sufficient when a service worker receives its cache policy through an injected build constant.
Note
Wherever domstack-manifest.settings.ts is used, you can also use domstack-manifest.settings.js.
Type checking is supported in both file types.
See Supported file types for all available extensions.
Configure entries and policy
Create one domstack-manifest.settings.ts file anywhere under src.
It can default-export an options object or a synchronous or asynchronous function that returns one.
// src/globals/domstack-manifest.settings.ts
import type { DomstackManifestOptions } from '@domstack/static/types.js'
type PageVars = {
offline?: boolean
precache?: boolean
}
type ManifestVars = Pick<PageVars, 'offline' | 'precache'>
type ManifestPolicy = {
offlineFallbackUrl: string
}
const settings = {
exclude: ['admin/**', '**/*.map'],
includeEntry: entry => entry.kind !== 'metadata',
manifestVars: ['offline', 'precache'],
policy: {
offlineFallbackUrl: '/offline/'
}
} satisfies DomstackManifestOptions<
ManifestPolicy,
ManifestVars,
PageVars
>
export default settings
The main settings are:
| Setting | Purpose |
|---|---|
exclude |
Ignore-style patterns matched against both entry.url and entry.outputRelname |
includeEntry(entry) |
A final synchronous or asynchronous predicate that returns true to retain an entry |
manifestVars |
An allowlist or per-entry transform that exposes selected resolved page variables |
policy |
A manifest-wide object or transform for application-defined policy |
hooks.manifestBuilt |
Hooks that consume the finalized manifest before the service worker is bundled |
Only variables explicitly selected by manifestVars are copied into entries.
Arbitrary page variables are not exposed automatically.
exclude runs before includeEntry(entry).
The resulting manifest contains:
version: A deterministic digest that changes when retained cache-relevant entries or root policy changegeneratedAt: The build timestamp, which does not affectversionentries: Included public outputs sorted by URLpolicy: Optional application-defined manifest-wide policy
Useful entry fields include url, revision, kind, bytes, contentType, integrity, urlRevisioned, static, role, and explicitly selected manifestVars.
Import DomstackManifest and DomstackManifestEntry from @domstack/static/types.js when consuming these objects directly.
Manifest built hooks
hooks.manifestBuilt runs after entries, policy, and version are finalized but before /service-worker.js is bundled.
Each hook receives:
manifest: The finalized manifestdest: The absolute destination directorydefineServiceWorkerConstant(name, value): Injects a JSON-serializable value into only the final service-worker bundlewriteFile(outputRelname, contents): Writes an additional file underdest
Files written by a hook are not added back to the already-finalized manifest. Prefer an injected constant when only the service worker needs the generated data.
Service worker integration
A manifest hook can turn the normalized entries into a small application-specific cache policy:
// src/globals/domstack-manifest.settings.ts
import type {
DomstackManifestBuiltHookContext,
DomstackManifestOptions
} from '@domstack/static/types.js'
export type CachePolicy = {
version: string
precacheEntries: Array<{
url: string
revision: string | null
integrity?: string
}>
}
function injectCachePolicy (
context: DomstackManifestBuiltHookContext
): void {
const policy: CachePolicy = {
version: context.manifest.version,
precacheEntries: context.manifest.entries
.filter(entry => entry.static === true)
.filter(entry => entry.revision)
.map(entry => ({
url: entry.url,
revision: entry.urlRevisioned ? null : entry.revision,
...(entry.integrity ? { integrity: entry.integrity } : {})
}))
}
context.defineServiceWorkerConstant('__APP_CACHE_POLICY__', policy)
}
const settings = {
hooks: {
manifestBuilt: [injectCachePolicy]
}
} satisfies DomstackManifestOptions
export default settings
The service worker can then consume the injected value without fetching a public manifest at runtime:
// src/globals/service-worker.ts
import type { CachePolicy } from './domstack-manifest.settings.ts'
declare const __APP_CACHE_POLICY__: CachePolicy
const cachePolicy = __APP_CACHE_POLICY__
Manifest-enabled builds also define:
| Define | Value |
|---|---|
process.env.DOMSTACK_MANIFEST_ENABLED |
"true" for a manifest-enabled one-shot build and "false" otherwise |
process.env.DOMSTACK_MANIFEST_VERSION |
The finalized version inside /service-worker.js; "" in other bundles |
process.env.DOMSTACK_MANIFEST_URL |
The conventional /domstack-manifest.json URL |
DOMSTACK_MANIFEST_URL does not guarantee that the JSON file was written.
Fetch it only when --domstackManifest, domstackManifest: true, or { write: true } enabled public output.
Important
Watch mode still bundles the service worker, but it does not finalize, return, or write the DOMStack manifest.
Manifest hooks do not inject production cache policy in watch mode.
Use a one-shot build or domstack --serve to test manifest-driven service-worker behavior.
domstack --serve runs a normal one-shot build and serves dest without watch-mode filenames or live-reload injection:
domstack --serve
domstack --serve --port 3001
See the complete examples for production-oriented cache lifecycle behavior:
static-mpa-offlineinjects DOMStack manifest entries into a custom service worker.static-mpa-workbox-offlineconverts the finalized entries into Workbox precaching and routing policy.
Programmatic configuration
Configure the manifest through the DomStack constructor when coordinating it with another build tool or script:
// scripts/build.ts
import { DomStack } from '@domstack/static'
const site = new DomStack('src', 'public', {
domstackManifest: {
write: true,
exclude: ['admin/**', '**/*.map']
}
})
const results = await site.build()
console.log(results.domstackManifest?.version)