manifest.<environment>.json files are generated from the contents of the /pages directory.
The manifests act as a structured index of page routes and their content assets, so applications can discover and retrieve page content at build time without hardcoding repository paths.
A manifest is intended to be a machine-readable contract between the content repository and consuming applications.
Each manifest contains:
- the manifest schema version
- the content version / generation timestamp
- the environment the manifest was generated for
- a
pagesobject keyed by stable page IDs - one route per page
- an
assetsobject for each page - asset descriptors containing the file
pathand assettype
Example:
{
"schemaVersion": 1,
"version": "2026-05-08T10:47:25Z",
"generatedAt": "2026-05-08T10:47:25Z",
"environment": "development",
"pages": {
"research": {
"route": "/research",
"assets": {
"main": {
"path": "research/main.md",
"type": "markdown"
},
"articles.ai-document-insights.article": {
"path": "research/articles/ai-document-insights/article.md",
"type": "markdown"
},
"articles.ai-document-insights.metadata": {
"path": "research/articles/ai-document-insights/metadata.json",
"type": "json"
}
}
}
}
}A manifest describes:
- which directories are valid page routes
- the stable ID for each page
- which assets belong to each page
- the route associated with each page
- the content path for each asset
- the type of each asset
- which files apply to a given environment
Applications should use the manifest to request content semantically, for example:
const page = source.page('research');
const main = await page.asset('main').text();
const metadata = await page.asset('articles.ai-document-insights.metadata').json();This keeps consuming applications independent from the physical folder layout of the content repository.
A directory is treated as a page route only if it contains a file named .page.
This marker makes the directory the root of a page route.
For example:
pages/
research/
.page
main.md
articles/
ai-document-insights/
article.md
metadata.json
Because pages/research/.page exists, research becomes a page route and is emitted as:
{
"pages": {
"research": {
"route": "/research",
"assets": {
"main": {
"path": "research/main.md",
"type": "markdown"
},
"articles.ai-document-insights.article": {
"path": "research/articles/ai-document-insights/article.md",
"type": "markdown"
},
"articles.ai-document-insights.metadata": {
"path": "research/articles/ai-document-insights/metadata.json",
"type": "json"
}
}
}
}
}Without a .page file, the directory is not treated as a route, even if it contains content.
The .page file may be empty, or it may contain JSON metadata.
Use this when you want to provide a stable page ID explicitly:
{
"id": "uprn-service"
}This is useful when the route path and the page ID should not be tightly coupled.
For example:
pages/
apps/
uprn-service/
.page
introduction.md
settings.json
With this .page file:
{
"id": "uprn-service"
}The manifest page entry is keyed by uprn-service:
{
"pages": {
"uprn-service": {
"route": "/apps/uprn-service",
"assets": {
"introduction": {
"path": "apps/uprn-service/introduction.md",
"type": "markdown"
},
"settings": {
"path": "apps/uprn-service/settings.json",
"type": "json"
}
}
}
}
}The pages object is keyed by page ID.
Example:
{
"pages": {
"home": {},
"research": {},
"uprn-service": {}
}
}If the .page file contains an explicit id, that value is used.
If no ID is provided, the generator derives one from the route:
| Route | Default page ID |
|---|---|
/ |
home |
/research |
research |
/apps/uprn-service |
uprn-service |
If two routes would produce the same page ID, add explicit IDs to their .page files.
Each page contains an assets object.
An asset entry maps a stable asset key to an asset descriptor:
{
"assets": {
"introduction": {
"path": "apps/uprn-service/introduction.md",
"type": "markdown"
}
}
}Each asset descriptor contains:
| Field | Description |
|---|---|
path |
File path relative to the /pages directory |
type |
Inferred content type based on the file extension |
Example:
{
"path": "research/main.md",
"type": "markdown"
}Asset types are inferred from file extensions.
Common examples:
| Extension | Type |
|---|---|
.md |
markdown |
.mdx |
markdown |
.json |
json |
.csv |
csv |
.svg |
svg |
.png |
png |
.jpg, .jpeg |
jpg |
.webp |
webp |
.xlsx |
xlsx |
.txt |
text |
.pdf |
pdf |
Unknown extensions are emitted as their extension name. Files without an extension are emitted as binary.
Asset keys are stable lookup keys used by consuming applications.
Asset keys are derived from the file path beneath the page route directory by:
- removing the environment segment, if present
- removing the file extension
- joining folder names and the filename with
. - preserving path-style names such as kebab-case
Examples:
| File path beneath route directory | Asset key |
|---|---|
introduction.md |
introduction |
settings.production.json |
settings |
svgs/bootstrap/info-circle.svg |
svgs.bootstrap.info-circle |
generated/csv/config/datasets.csv |
generated.csv.config.datasets |
articles/ai-document-insights/metadata.json |
articles.ai-document-insights.metadata |
articles/ai-document-insights/architecture-extract-descriptions.svg |
articles.ai-document-insights.architecture-extract-descriptions |
The manifest does not convert filenames to camelCase. This keeps keys consistent with content paths and avoids mixed styles such as:
ai-document-insights.architectureExtractDescriptions
Prefer:
ai-document-insights.architecture-extract-descriptions
Only directories containing .page become page entries in the manifest.
If a directory inside a route also contains .page, it becomes its own route. Its files are not included in the parent route's assets.
Example:
pages/
research/
.page
main.md
articles/
.page
index.md
This produces separate page entries for:
/research
/research/articles
Older manifests used a nested files object.
New manifests use a flat assets object.
Old shape:
{
"files": {
"articles": {
"ai-document-insights": {
"metadata": "research/articles/ai-document-insights/metadata.json"
}
}
}
}New shape:
{
"assets": {
"articles.ai-document-insights.metadata": {
"path": "research/articles/ai-document-insights/metadata.json",
"type": "json"
}
}
}Older manifests mapped keys directly to file paths.
Old shape:
{
"settings": "apps/uprn-service/settings.json"
}New shape:
{
"settings": {
"path": "apps/uprn-service/settings.json",
"type": "json"
}
}The generator builds one manifest per environment.
Environments are defined in site.json.
For example:
{
"environments": ["testing", "production"]
}This generates:
manifest.testing.jsonmanifest.production.json
If environments is not present, the generator falls back to currentenvironment:
{
"currentenvironment": "development"
}This generates:
manifest.development.json
A file is considered environment-specific when its name matches this pattern:
<name>.<environment>.<extension>
Examples:
settings.testing.json
settings.production.json
introduction.development.md
A file without an environment segment is treated as the default version and is included for all environments.
Example:
settings.json
For the same asset key:
- the default file is used for all environments
- an environment-specific file overrides the default for that environment
Example directory:
pages/
research/
.page
settings.json
settings.testing.json
settings.production.json
Generated behaviour:
manifest.testing.jsonusessettings.testing.jsonmanifest.production.jsonusessettings.production.json- other environments use
settings.json, if generated
The asset key remains the same:
{
"settings": {
"path": "research/settings.production.json",
"type": "json"
}
}Only the selected file path changes by environment.
pages/
research/
.page
main.md
main.testing.md
articles/
ai-document-insights/
article.md
metadata.json
architecture-extract-descriptions.svg
{
"schemaVersion": 1,
"version": "2026-05-08T10:47:25Z",
"generatedAt": "2026-05-08T10:47:25Z",
"environment": "production",
"pages": {
"research": {
"route": "/research",
"assets": {
"main": {
"path": "research/main.md",
"type": "markdown"
},
"articles.ai-document-insights.article": {
"path": "research/articles/ai-document-insights/article.md",
"type": "markdown"
},
"articles.ai-document-insights.metadata": {
"path": "research/articles/ai-document-insights/metadata.json",
"type": "json"
},
"articles.ai-document-insights.architecture-extract-descriptions": {
"path": "research/articles/ai-document-insights/architecture-extract-descriptions.svg",
"type": "svg"
}
}
}
}
}{
"schemaVersion": 1,
"version": "2026-05-08T10:47:25Z",
"generatedAt": "2026-05-08T10:47:25Z",
"environment": "testing",
"pages": {
"research": {
"route": "/research",
"assets": {
"main": {
"path": "research/main.testing.md",
"type": "markdown"
},
"articles.ai-document-insights.article": {
"path": "research/articles/ai-document-insights/article.md",
"type": "markdown"
},
"articles.ai-document-insights.metadata": {
"path": "research/articles/ai-document-insights/metadata.json",
"type": "json"
},
"articles.ai-document-insights.architecture-extract-descriptions": {
"path": "research/articles/ai-document-insights/architecture-extract-descriptions.svg",
"type": "svg"
}
}
}
}
}Note that:
main.testing.mdoverridesmain.mdonly fortesting- the asset key stays as
main - the asset descriptor changes by environment
Example folder layout:
pages/
apps/
uprn-service/
.page
introduction.development.md
settings.development.json
climatejust-renderers.json
generated/
csv/
config/
datasets.csv
domains.csv
folders.csv
variables.csv
manifest.json
Example .page file:
{
"id": "uprn-service"
}Example manifest output:
{
"schemaVersion": 1,
"version": "2026-05-08T10:47:25Z",
"generatedAt": "2026-05-08T10:47:25Z",
"environment": "development",
"pages": {
"uprn-service": {
"route": "/apps/uprn-service",
"assets": {
"climatejust-renderers": {
"path": "apps/uprn-service/climatejust-renderers.json",
"type": "json"
},
"generated.csv.config.datasets": {
"path": "apps/uprn-service/generated/csv/config/datasets.csv",
"type": "csv"
},
"generated.csv.config.domains": {
"path": "apps/uprn-service/generated/csv/config/domains.csv",
"type": "csv"
},
"generated.csv.config.folders": {
"path": "apps/uprn-service/generated/csv/config/folders.csv",
"type": "csv"
},
"generated.csv.config.variables": {
"path": "apps/uprn-service/generated/csv/config/variables.csv",
"type": "csv"
},
"generated.manifest": {
"path": "apps/uprn-service/generated/manifest.json",
"type": "json"
},
"introduction": {
"path": "apps/uprn-service/introduction.development.md",
"type": "markdown"
},
"settings": {
"path": "apps/uprn-service/settings.development.json",
"type": "json"
}
}
}
}
}A consuming application should treat the manifest as the source of truth for content locations.
Instead of hardcoding paths such as:
const url = `${baseUrl}/apps/uprn-service/settings.production.json`;use the manifest:
const page = source.page('uprn-service');
const settings = await page.asset('settings').json();For build-time content retrieval in SvelteKit, use the content source from server-side load code and prerender the route:
import type { PageServerLoad } from './$types';
import { createContentSource } from '$lib/server/content-source';
export const prerender = true;
export const load: PageServerLoad = async ({ fetch }) => {
const source = await createContentSource({ fetch });
const page = source.page('uprn-service');
return {
introduction: await page.asset('introduction').text(),
settings: await page.asset('settings').json(),
};
};This allows the content repository layout to change without requiring application code to be refactored, as long as page IDs and asset keys remain stable.