Configuration
Site configuration lives in the content root.
Supported config entry files:
<content-root>/mdorigin.config.ts<content-root>/mdorigin.config.mjs<content-root>/mdorigin.config.js<content-root>/mdorigin.config.json
When multiple files exist, mdorigin prefers .ts, then .mjs, then .js, then .json.
Useful fields:
siteTitlesiteDescriptionsiteUrlfaviconsocialImagelogotopNavfooterNavfooterTextsocialLinkseditLinkshowHomeIndexlistingInitialPostCountlistingLoadMoreStepsearchshowDateshowSummarystylesheetplugins
Code Config
Use mdorigin.config.ts when you want code-based customization instead of pure static settings.
Example:
import { defineConfig } from "mdorigin";
export default defineConfig({
siteTitle: "My Site",
plugins: [
{
name: "custom-layout",
renderPage(page, _context, next) {
if (page.kind !== "listing") {
return next(page);
}
const title = escapeHtml(page.title);
return [
"<!doctype html>",
"<html><body>",
`<main class="custom-listing"><h1>${title}</h1>${page.bodyHtml}</main>`,
"</body></html>",
].join("");
},
},
],
});
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
defineConfig is optional. A plain default export object also works.
Current stable plugin hooks:
transformIndex(entries, context)renderHeader(context)renderFooter(context)renderPage(page, context, next)transformHtml(html, context)
The design boundary is:
mdoriginowns routing and content semantics- plugins may fully replace page rendering
- plugins should not replace the request kernel itself
Site Metadata
- If
siteTitleis configured, it is used directly. - Otherwise
mdoriginfalls back to the root homepage frontmatter:title->siteTitlesummary->siteDescription
- If neither config nor root homepage frontmatter provides a value,
siteTitlefalls back tomdorigin.
Navigation
- If
topNavis configured,mdoriginuses it directly. - If
topNavis omitted or empty,mdoriginderives navigation from the content root's first-level subdirectories. - Auto-derived navigation only includes directories treated as
type: page. - Directories treated as
type: postare excluded from auto-derived top navigation. - When the root homepage already has top navigation, the HTML view hides repeated
pageentries from the managed root index block and keeps only the remaining entries, such as posts. footerNavis always explicit and is not auto-derived from the content tree.
Branding
siteUrlsets the canonical site origin and is used for canonical links in rendered HTML.siteUrlalso enables/sitemap.xml, which emits absolute canonical URLs.siteUrlalso enables/feed.xmlby default unless RSS is explicitly disabled.faviconadds a standard favicon link tag.socialImageemits absoluteog:imageandtwitter:imagemetadata whensiteUrlis set.logorenders a small site logo in the header.
Example:
{
"siteUrl": "https://example.com",
"favicon": "/favicon.svg",
"socialImage": "/og.svg",
"logo": {
"src": "/logo.svg",
"alt": "Example"
}
}
RSS
mdorigin can emit a built-in RSS feed at /feed.xml.
Rules:
- if
siteUrlis set, RSS is enabled by default - set
"rss": falseto disable the feed - the feed emits dated post content, not every page in the tree
- rendered HTML adds an RSS autodiscovery
<link rel="alternate" ...>when the feed is enabled
Optional overrides:
{
"rss": {
"title": "Example Feed",
"description": "Latest updates from Example",
"author": "editor@example.com",
"maxItems": 20
}
}
Supported fields:
rss.titlerss.descriptionrss.authorrss.maxItems
Footer
mdorigin supports a small set of explicit footer settings:
footerNavfooterTextsocialLinkseditLink
Example:
{
"footerNav": [
{ "label": "GitHub", "href": "https://github.com/example/repo" }
],
"footerText": "Built with mdorigin.",
"socialLinks": [
{ "icon": "github", "label": "GitHub", "href": "https://github.com/example/repo" }
],
"editLink": {
"baseUrl": "https://github.com/example/repo/edit/main/docs/"
}
}
Built-in social icons currently include:
githubnpmrssxhome
footerText has no implicit default. If you omit it, mdorigin does not render footer copy on its own.
Default Presentation
mdorigin ships one built-in presentation.
- it uses the default atlas-style baseline
- it renders ordinary pages and managed index listings with the same outer document shell
- if you want a different layout or styling, use
stylesheetor code-based hooks instead of selecting a built-in theme/template variant
When a page contains a managed index block, the default renderer turns article entries into a structured listing and supports incremental Load more batches. You can tune that listing behavior with:
{
"listingInitialPostCount": 10,
"listingLoadMoreStep": 10
}
Rules:
listingInitialPostCountcontrols how many article entries are rendered in the first HTML responselistingLoadMoreStepcontrols how many additional article entries eachLoad morerequest appends- directory entries are still rendered in full; the limit only applies to article entries in the managed listing
- both fields default to
10
Search Profile
When a site exposes a search bundle through mdorigin dev --search ... or a deployed search API, mdorigin can apply a site-level search profile.
Example:
{
"search": {
"topK": 10,
"mode": "hybrid",
"minScore": 0.05,
"reranker": {
"kind": "embedding-v1",
"candidatePoolSize": 25
},
"scoreAdjustment": {
"metadataNumericMultiplier": "directory_weight"
}
}
}
Supported fields:
search.topKsearch.modesearch.minScoresearch.reranker.kindsearch.reranker.candidatePoolSizesearch.scoreAdjustment.metadataNumericMultipliersearch.policy.shortQuerysearch.policy.longQuery
Rules:
search.topKsets the default result count for/api/searchwhen the request does not provide its owntopKsearch.modeacceptshybridorvectorsearch.minScoredrops weak tail matches after final scoringsearch.rerankerconfigures the optional final reranking stagesearch.scoreAdjustment.metadataNumericMultiplierpoints to a numeric metadata field that should multiply the final scoresearch.policyis optional; if omitted, mdorigin uses only the static site-level search profilesearch.policy.shortQuery.maxCharsandsearch.policy.longQuery.minCharsare matched against the trimmed query length in Unicode code points- if both thresholds overlap for a query,
shortQuerywins - policy entries may set
mode,minScore,reranker, orscoreAdjustment - within a policy entry,
nullexplicitly clears an inherited value such as the default reranker or score adjustment /api/searchstill keeps a small public surface:q,topK, andmeta.<field>- retrieval mode, reranker choice, score adjustment, and score cutoff stay in site configuration rather than public query parameters
Example query-aware policy:
{
"search": {
"mode": "hybrid",
"topK": 10,
"minScore": 0.02,
"policy": {
"shortQuery": {
"maxChars": 6,
"minScore": 0.02,
"reranker": null
},
"longQuery": {
"minChars": 12,
"reranker": {
"kind": "heuristic-v1",
"candidatePoolSize": 20
}
}
}
}
}
Directory Type
Directory homepage files may declare a content type in frontmatter:
---
title: Projects
type: page
---
---
title: Why mdorigin exists
type: post
---
Rules:
type: pageis for sections, landing pages, and navigable collectionstype: postis for article containers such aspost/README.mdwith colocated assets- if
typeis omitted,mdoriginuses lightweight inference and does not write the result back to frontmatter
Order
Markdown frontmatter may define order:
---
title: Getting Started
order: 10
---
Rules:
- lower
ordervalues come first orderis used for auto-derived top navigation and for directory index generation- when
orderis absent,mdoriginfalls back to its default sort rules
Rendering Flags
showDatecontrols whether parsed markdown dates are surfaced in rendered HTML where the default presentation uses themshowSummarycontrols whether configured summaries are surfaced in rendered HTML where the default presentation uses them- both flags default to
true
Aliases
Markdown frontmatter may define old URLs that should redirect to the current canonical route:
---
title: Hello
aliases:
- /hello-world
- /old/hello
---
Rules:
aliasesmay be a string or a string array- alias requests return
308redirects - aliases redirect to the canonical HTML route for the current document
- directory homepages redirect to
/dir/ - regular markdown documents redirect to
/dir/name - draft documents do not expose aliases in exclude mode