---
title: Configuration file
description: Every option in blume.config.ts, from site metadata and content sources to the links that lead into each individual feature configuration guide.
sidebar:
  label: blume.config.ts
---

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.

```ts blume.config.ts lineNumbers
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):

```ts blume.config.ts lineNumbers
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:

```ts blume.config.ts
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:

```ts blume.config.ts lineNumbers
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 `text`** and the brand uses your site `title` (the default).
- **Set `text: ""`** to show the mark alone — handy when the logo image already includes the wordmark.
- **Set `text` with no `image`** for 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.

A dark mark disappears against dark browser chrome, so you can ship a second file for dark mode. Add a `-dark` sibling of your icon file — the same name and directory, with `-dark` before the extension (`icon.png` → `icon-dark.png`) — and Blume emits both icons behind a `prefers-color-scheme` media query, plus a plain light tag for browsers and crawlers that ignore media queries on icons:

```
my-docs/
├─ blume.config.ts
├─ icon.png          ← light mode
├─ icon-dark.png     ← dark mode
└─ docs/
```

Only the sibling of the icon Blume picked is used — a `-dark` file with a different name stays ignored, so an unrelated file can't pair with your mark by accident. The dark file is optional; with only one icon, Blume emits a single tag as before. Blume's own fallback mark ships both variants.

### 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. Unlike the favicon, there's no `-dark` sibling here — iOS ignores media queries on home-screen icons, so a dark variant could never be served.

### Banner

Show a site-wide announcement bar above the header. Pass a string, or an object with a link and a dismiss button:

```ts blume.config.ts
banner: "Docs are in beta — expect changes.",
```

```ts blume.config.ts lineNumbers
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](/docs/content) for how files become routes.

```ts blume.config.ts lineNumbers
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. |
| `types` | `{}` | Per-type content definitions — custom frontmatter keys scoped to pages of one `type`. See [Frontmatter](#frontmatter). |

Static assets live in `public/` — a file at `public/logo.png` is served at `/logo.png`, so a reference like `![](/images/create.png)` resolves against `public/images/create.png`. Images referenced by **relative path** (`![](./diagram.png)`) live next to your content instead, and are [optimized at build time](/docs/content/syntax#links-and-images).

## 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](/docs/content/syntax#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:

```ts blume.config.ts lineNumbers
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:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { z } from "zod";

export default defineConfig({
  frontmatter: {
    extend: {
      owner: z.string(),
      reviewedAt: z.coerce.date().optional(),
    },
  },
});
```

Any [Standard Schema](https://standardschema.dev) 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](/docs/reference/frontmatter#custom-keys) for the validation semantics.

Keys under `extend` apply site-wide. To require keys only on pages of one content type — an RFC's `status`, a runbook's `service` — declare them per type under `content.types` instead:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { z } from "zod";

export default defineConfig({
  content: {
    types: {
      rfc: {
        facets: ["domain", "status"],
        frontmatter: {
          domain: z.string(),
          status: z.enum(["draft", "review", "enforced"]),
        },
      },
    },
  },
});
```

A key can be declared site-wide or per-type, not both. See [Per-type keys](/docs/reference/frontmatter#per-type-keys) for how the scoping resolves.

`facets` names the custom keys whose values become filterable metadata: they ride along on search documents (`blume-search.json` and the MCP index), and the [MCP tools](/docs/configuration/ai#mcp-server) accept a `filters` input matching against them, so an agent can retrieve, say, only `enforced` RFCs in the `architecture` domain. Each facet must be a declared custom key — per-type or site-wide — and only string (or stringified number/boolean) values facet.

## GitHub

Point Blume at your repository with `github`. It powers the header [repository link](/docs/content/navigation#repository-link) and the **Edit on GitHub** and **Give feedback** [page actions](/docs/content/navigation#page-actions):

```ts blume.config.ts lineNumbers
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:

```ts blume.config.ts
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. CI platforms usually check out a shallow clone, which silently drops most dates (the build warns with `BLUME_SHALLOW_GIT_HISTORY` when that happens): on Vercel, set the `VERCEL_DEEP_CLONE=true` environment variable; with `actions/checkout`, set `fetch-depth: 0`. A page's own `lastModified` frontmatter always wins, which is handy for pinning a date or for files that aren't committed yet:

```mdx page.mdx
---
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](/docs/advanced/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日`):

```ts blume.config.ts
dateFormat: { dateStyle: "long" },
```

`dateFormat` is a pass-through to [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) options. Use a `dateStyle` preset for a length:

```ts blume.config.ts
dateFormat: { dateStyle: "medium" },
```

Or the individual component fields for a numeric house style like `2026/07/21`:

```ts blume.config.ts
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](/docs/configuration/seo) for metadata, frontmatter overrides, and the full reference.

```ts blume.config.ts lineNumbers
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`](/docs/deployment) 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`:

```ts blume.config.ts
export default defineConfig({
  toc: false, // hide it everywhere
});
```

Or narrow the heading range instead:

```ts blume.config.ts
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](/docs/configuration/theming) |
| `navigation` | Explicit sidebar and header tabs | [Navigation](/docs/content/navigation) |
| `search` | Provider (Orama, Pagefind, Algolia, and more) and indexing | [Search](/docs/configuration/search) |
| `markdown` | Markdown rendering options — code blocks, heading anchors, image zoom | [Syntax](/docs/content/syntax) |
| `ai` | `llms.txt`, Ask AI, and the hosted MCP server for coding agents | [AI](/docs/configuration/ai) |
| `analytics` | Vercel, PostHog, and custom scripts | [Analytics](/docs/configuration/analytics) |
| `seo` | Metadata, OG images, feeds, structured data | [SEO](/docs/configuration/seo) |
| `deployment` | Output mode, adapter, and site URL | [Deployment](/docs/deployment) |
| `redirects` | Permanent and temporary redirects | [Deployment](/docs/deployment#redirects) |
| `integrations` | Astro integrations appended after Blume's built-ins | [Customization](/docs/configuration/customization#astro-integrations) |

## Precedence

Settings resolve from lowest to highest priority, so you only override what you need:

<Steps>
  <Step title="Blume defaults">A sensible default for every field.</Step>
  <Step title="blume.config.ts">Your project-wide configuration.</Step>
  <Step title="Folder meta">
    [`meta.ts`](/docs/content/meta) for a section's title and ordering.
  </Step>
  <Step title="Page frontmatter">Per-page overrides win.</Step>
</Steps>
