Content Registry
Generate a manifest, RSS feed, and sitemap alongside your Markdown.
Converting posts gives you Markdown files, but a site usually needs an index of them too — to build a listing page, a feed, or a sitemap. The content registry is that index: medmark records every post it writes and emits it in the formats you ask for.
The registry is on by default and produces a registry.json in your output
folder.
Configuring it
The registry is configured in your template's getOptions():
module.exports = {
getOptions() {
return {
registry: {
enabled: true,
filename: 'registry',
formats: ['json', 'ts', 'rss', 'sitemap'],
includeDrafts: false,
site: {
url: 'https://example.com',
title: 'My Blog',
description: 'Posts about things.',
language: 'en',
},
},
};
},
// ...getOptions is the only place registry settings live
render(data) {
/* ... */
},
};Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Set to false to skip registry generation entirely |
filename | string | 'registry' | Base filename (no extension) for the json and ts outputs |
formats | ('json' | 'ts' | 'rss' | 'sitemap')[] | ['json'] | Which files to emit |
includeDrafts | boolean | false | Include drafts in the RSS feed and sitemap |
site | object | — | Site metadata; required for rss and sitemap |
site metadata
| Field | Type | Description |
|---|---|---|
url | string | Absolute base URL of the site. Required by rss and sitemap |
title | string | Channel title, used by rss |
description | string | Channel description, used by rss |
language | string | Channel language (e.g. en), used by rss |
rss and sitemap need site.url
If site.url is missing, medmark logs a warning and skips those two formats.
The rest of the run is unaffected.
Output formats
json → registry.json
A machine-readable manifest. The shape is versioned via the top-level
version field.
{
"version": 1,
"generator": "medmark@0.3.0",
"generatedAt": "2026-01-14T09:21:04.512Z",
"count": 2,
"posts": [
{
"slug": "my-first-post",
"title": "My First Post",
"description": "A short summary.",
"date": "2023-04-02",
"path": "my-first-post/index.md",
"draft": false,
"readingTime": "5 min read",
"authors": ["Brion Mario"],
"categories": [],
"tags": ["javascript", "markdown"],
"bannerImage": "https://cdn-images-1.medium.com/max/1024/...",
"source": {
"provider": "medium",
"url": "https://medium.com/@brionmario/my-first-post-abc123"
}
}
]
}ts → registry.ts
The same entries as a typed ESM module, so TypeScript consumers get completions and type errors for free. The default export is the array of entries; the run metadata is exported alongside it:
import registry, { generatedAt, generator } from './medmark/output/registry';
// registry: MedmarkRegistryEntry[]
registry.map(post => post.slug);The MedmarkRegistryEntry interface is inlined into the generated file, so it
has no import-time dependency on the medmark package.
rss → feed.xml
An RSS 2.0 feed built from site plus every non-draft post. Drafts are
included only when includeDrafts is true.
sitemap → sitemap.xml
A sitemap of every post URL, resolved against site.url. Each post's slug
is appended to the base URL, so a site.url of https://example.com and a
slug of my-first-post yields https://example.com/my-first-post.
Registry entry fields
Posts are sorted newest-first by date. Each entry has this shape:
| Field | Type | Description |
|---|---|---|
slug | string | The post's slug |
title | string | The post title |
path | string | Path to the generated file, relative to the output root |
date | string | Publish date as YYYY-MM-DD |
description | string | Post description |
draft | boolean | Whether the post is a draft |
readingTime | string | Reading time text |
authors | string[] | Author display names |
categories | string[] | Post categories |
tags | string[] | Post tags |
bannerImage | string | First image found in the post |
source | {provider, url} | Where the post came from (provider is medium) |
Customising the registry
Two optional template hooks let you take over either half of the process.
buildRegistryEntry(data, context)
Called once per generated post. Return a modified entry, or null to leave
the post out of the registry.
const { buildDefaultRegistryEntry } = require('medmark');
module.exports = {
buildRegistryEntry(data, context) {
// Skip anything that isn't tagged.
if (!data.tags?.length) return null;
return {
...buildDefaultRegistryEntry(data, context),
featured: data.tags.includes('featured'),
};
},
// ...
};context carries:
| Field | Type | Description |
|---|---|---|
options | MedmarkOptions | The resolved template options for the run |
outputRoot | string | The output root directory |
relativePath | string | Path to the generated file, relative to outputRoot |
slug | string | The post's slug |
serializeRegistry(registry, context)
Called once at the end of the run with the aggregated registry. Return the
list of files to write — this bypasses formats entirely, so you control the
output completely.
module.exports = {
serializeRegistry(registry) {
return [
{
filename: 'posts.js',
content: `export const posts = ${JSON.stringify(registry.posts, null, 2)};\n`,
},
];
},
// ...
};Each returned file is {filename, content}, written relative to the output
root.
Turning it off
getOptions() {
return {
registry: { enabled: false },
};
}