Configuration file
Every option in blume.config.ts, from site metadata and content sources to the links that lead into each individual feature configuration guide.
Blume reads blume.config.ts from your project root. Wrap your config in defineConfig for autocomplete and type-checking — every field is optional, with a sensible default.
import { defineConfig } from "blume";
export default defineConfig({
title: "My Docs",
description: "Documentation for my project.",
});
A complete example
A broader example touching the most common options (see each feature’s guide for the rest):
import sitemap from "@astrojs/sitemap";
import { defineConfig } from "blume";
export default defineConfig({
// Site
title: "My Docs",
description: "Documentation for my project.",
logo: "/logo.svg",
// Astro integrations — installed and versioned by this site
integrations: [sitemap()],
// Content
content: {
root: "docs",
},
// Theme — see the Theming guide
theme: {
accent: "teal",
radius: "md",
mode: "system",
},
// Search — see the Search guide
search: {
provider: "orama",
},
// Markdown features
markdown: {
imageZoom: true,
code: {
icons: true, // language icon in the code-block header
wrap: false, // wrap long lines instead of scrolling
},
codeBlocks: {
theme: {
light: "github-light", // bundled name or custom Shiki theme object
dark: "github-dark",
},
},
},
// AI — see the AI guide
ai: {
llmsTxt: true,
// MCP server (needs server output)
mcp: {
enabled: false,
route: "/mcp",
},
},
// SEO — OG images, feeds, sitemap, structured data; see the SEO guide
seo: {
og: { enabled: true },
rss: { enabled: true, types: ["blog", "changelog"] },
sitemap: true,
robots: true,
structuredData: true,
},
// Deployment — see the Deployment guide
deployment: {
output: "static",
site: "https://docs.example.com",
},
});
Site
| Option | Default | Description |
|---|---|---|
title |
"Documentation" |
Site name — shown in the header, page titles, OG cards. |
description |
— | Default meta description, used for SEO and OG. |
logo |
— | Brand mark and/or wordmark shown in the header. |
banner |
— | Site-wide announcement bar above the header. |
Logo
Point logo at an SVG and Blume inlines it, so a currentColor logo follows the light and dark theme automatically:
logo: "/logo.svg",
The SVG can live at your project root or in public/. The brand is a mark (image) plus a wordmark (text); the object form lets you set them independently:
logo: {
image: "/logo.svg", // string, or { light, dark, alt } for themed raster art
text: "Acme", // wordmark beside the mark
href: "/", // overrides the brand link (defaults to "/")
},
image takes the same value as the shorthand — a single path, or { light, dark, alt } for separate light/dark artwork (raster images must live in public/).
text controls the wordmark independently of the mark:
- Omit
textand the brand uses your sitetitle(the default). - Set
text: ""to show the mark alone — handy when the logo image already includes the wordmark. - Set
textwith noimagefor a text-only logo.
Favicon
There’s no favicon option — Blume auto-detects one by filename, the way Next.js does. Drop an icon or favicon file (.svg, .png, or .ico) in your project root or public/ directory and it becomes the browser tab icon:
my-docs/
├─ blume.config.ts
├─ icon.png ← picked up automatically
└─ docs/
SVG wins over PNG over ICO when several are present, and a file in public/ is preferred over one at the root. If Blume finds no icon, it falls back to its own mark.
Apple touch icon
The icon iOS uses when someone adds your site to their home screen is detected the same way. Drop an apple-icon file (.png, .jpg, or .jpeg) — or an apple-touch-icon.png, the name most favicon generators emit — in your project root or public/ directory and Blume wires up <link rel="apple-touch-icon"> for you. There’s no default; if no file is found, no tag is emitted.
my-docs/
├─ blume.config.ts
├─ apple-icon.png ← picked up automatically
└─ docs/
Put the file in public/ rather than the project root: iOS ignores the inlined data URI Blume uses for a root-level icon, so only a public/ file (served at /apple-icon.png) reliably reaches the home screen.
Banner
Show a site-wide announcement bar above the header. Pass a string, or an object with a link and a dismiss button:
banner: "Docs are in beta — expect changes.",
banner: {
content: "Blume v1 is here!",
link: { text: "Read more", href: "/blog/v1" },
dismissible: true,
id: "v1",
},
When dismissible is on, the bar shows a close button and stays hidden for that visitor afterward. The dismissal key defaults to the content text, so editing the message brings the banner back; set a stable id to keep it dismissed across edits.
Content
Where your content lives and how Blume discovers it. See Pages for how files become routes.
content: {
root: "docs",
}
| Option | Default | Description |
|---|---|---|
root |
"docs" |
Folder Blume scans for content. |
include |
["**/*.{md,mdx}"] |
Globs that match content files. |
exclude |
["**/_*", "**/.*"] |
Globs to ignore (underscore- and dot-files). |
pages |
"pages" |
Folder for custom .astro pages. |
defaultType |
"doc" |
Page type used when frontmatter omits it. |
Static assets live in public/ — a file at public/logo.png is served at /logo.png, so a reference like  resolves against public/images/create.png. Images referenced by relative path () live next to your content instead, and are optimized at build time.
Images
Local images referenced by relative path are optimized automatically at build time — compressed, converted to WebP, and given intrinsic width/height attributes so the layout doesn’t shift while they load. There’s nothing to configure; see Links and images for authoring guidance.
Remote images are served untouched by default. To have Blume download and optimize them at build time too, authorize their hosts:
image: {
domains: ["cdn.example.com"],
remotePatterns: [{ protocol: "https", hostname: "**.example.com" }],
}
| Option | Default | Description |
|---|---|---|
domains |
[] |
Hostnames whose remote images may be optimized. |
remotePatterns |
[] |
Pattern-based authorization (protocol, hostname, port, pathname); hostnames accept *. (one level) and **. (any depth) wildcards. |
Frontmatter
Page frontmatter is strictly validated — an unknown key fails the build, so typos are caught early. To carry project-specific metadata (an owner, a review date), declare the extra keys under frontmatter.extend, each mapped to a schema you supply:
import { defineConfig } from "blume";
import { z } from "zod";
export default defineConfig({
frontmatter: {
extend: {
owner: z.string(),
reviewedAt: z.coerce.date().optional(),
},
},
});
Any Standard Schema library works — Zod (whichever version your project installs), Valibot, ArkType. Keys outside the extension stay strictly validated, so typo-catching is unchanged. See Custom keys for the validation semantics.
GitHub
Point Blume at your repository with github. It powers the header repository link and the Edit on GitHub and Give feedback page actions:
github: {
owner: "acme",
repo: "docs",
}
| Option | Default | Description |
|---|---|---|
owner |
— | GitHub account or organization that owns the repository. |
repo |
— | Repository name. |
branch |
"main" |
Branch that edit links point at. |
dir |
— | Path from the repo root to the project root (for monorepos). |
Last modified
Show a “Last updated on …” line at the bottom of each page. Off by default; set lastModified to true to derive each page’s date from its git history:
lastModified: true,
| Value | Description |
|---|---|
false |
Disabled (default). |
true |
Read the date from git history (commit dates). |
{ type: "git" } |
Same as true, written explicitly. |
{ type: "frontmatter" } |
Never run git — use only the lastModified frontmatter field. |
The git source reads the most recent commit that touched each file, so it works in any git repository — including monorepos — and needs the repo’s history at build time (avoid a shallow --depth 1 checkout in CI). A page’s own lastModified frontmatter always wins, which is handy for pinning a date or for files that aren’t committed yet:
---
title: My page
lastModified: 2026-06-20
---
When enabled, the date is also emitted as schema.org dateModified in the page’s structured data.
Date format
Both the “Last updated” stamp and the changelog timeline render their dates through the same dateFormat, so they read alike. Dates always render in the site’s locale; dateFormat controls the shape. It defaults to the long form (July 21, 2026, 2026年7月21日):
dateFormat: { dateStyle: "long" },
dateFormat is a pass-through to Intl.DateTimeFormat options. Use a dateStyle preset for a length:
dateFormat: { dateStyle: "medium" },
Or the individual component fields for a numeric house style like 2026/07/21:
dateFormat: { year: "numeric", month: "2-digit", day: "2-digit" },
| Option | Description |
|---|---|
dateStyle |
Preset length: "full", "long", "medium", or "short". Can’t be combined with the component fields. |
weekday, era, year, month, day |
Individual components, e.g. year: "numeric", month: "2-digit". |
timeZone |
IANA time zone. Defaults to UTC, so a date reads the same regardless of where the site builds. |
calendar, numberingSystem |
Calendar system (e.g. "japanese") and numbering system (e.g. "arab"). |
SEO
Open Graph images, RSS feeds, and JSON-LD structured data, grouped under seo. See the SEO guide for metadata, frontmatter overrides, and the full reference.
seo: {
og: { enabled: true },
rss: { enabled: true, types: ["blog", "changelog"] },
sitemap: true,
robots: true,
structuredData: true,
}
| Option | Default | Description |
|---|---|---|
og.enabled |
auto | Per-page Open Graph images — on when a site URL is set. |
rss.enabled |
true |
Build feeds for blog and changelog content. |
rss.types |
["blog", "changelog"] |
Content types that each get a feed. |
rss.limit |
50 |
Maximum items per feed. |
sitemap |
true |
Generate sitemap.xml (needs deployment.site). |
robots |
true |
Generate robots.txt with a Sitemap link. |
structuredData |
true |
Emit schema.org JSON-LD in each page’s head. |
These work best with an absolute deployment.site for full URLs.
Table of contents
The on-this-page outline is on by default and lists H2–H3 headings. Turn it off, or change the heading range, with toc:
export default defineConfig({
toc: false, // hide it everywhere
});
Or narrow the heading range instead:
export default defineConfig({
toc: { minHeadingLevel: 2, maxHeadingLevel: 4 },
});
Feature options
Each of these has its own guide. The config field is the entry point:
| Field | What it configures | Guide |
|---|---|---|
theme |
Accent color, corner radius, fonts, light/dark mode | Theming |
navigation |
Explicit sidebar and header tabs | Navigation |
search |
Provider (Orama, Pagefind, Algolia, and more) and indexing | Search |
markdown |
Markdown rendering options — code blocks, heading anchors, image zoom | Syntax |
ai |
llms.txt, Ask AI, and the hosted MCP server for coding agents |
AI |
analytics |
Vercel, PostHog, and custom scripts | Analytics |
seo |
Metadata, OG images, feeds, structured data | SEO |
deployment |
Output mode, adapter, and site URL | Deployment |
redirects |
Permanent and temporary redirects | Deployment |
integrations |
Astro integrations appended after Blume’s built-ins | Customization |
Precedence
Settings resolve from lowest to highest priority, so you only override what you need:
Blume defaults
blume.config.ts
Folder meta
meta.ts for a section’s title and ordering.
Page frontmatter