Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview

Custom sources

Plug any backend into Blume with custom() — pass an object that implements the ContentSource interface, or a built-in engine with custom serializers.

Any object implementing the ContentSource interface can be passed to custom(), which is how an adapter with custom serializers — or any backend not built in — plugs in without its SDK touching the core install:

import { defineConfig } from "blume";
import { custom, filesystem } from "blume/sources";
import { sanitySource } from "blume/sources/sanity.ts";

export default defineConfig({
  content: {
    sources: [
      filesystem({ root: "docs" }),
      custom(
        sanitySource({
          name: "guides",
          prefix: "guides",
          projectId: "abc123",
          dataset: "production",
          query: `*[_type == "guide"]`,
          // Map custom Portable Text blocks to Blume components
          serializers: {
            callout: (block) => `<Callout>${block.text}</Callout>`,
          },
        })
      ),
    ],
  },
});

A source built with one of Blume’s engine factories, like sanitySource above, is rebuilt on the running command’s context, so it reads drafts under --preview and keeps its snapshot in .blume/cache the way the built-in adapter does. A source of your own can do the same by implementing withContext(ctx) and returning itself rebuilt on that context.

custom() takes a ContentSource instance rather than an options object, so there is nothing to pass the shared options prefix or pollInterval to. The source sets its own prefix property, and re-fetching is whatever its watch method implements. It carries a live instance rather than plain data, so it declares no runtime dependency or secret of its own — the instance manages those itself.

A custom source that reads local files should set sourcePath on each entry and contentRoot on the source itself. sourcePath names the file in diagnostics and resolves relative images beside it; contentRoot bounds the git log that dates pages, so without it the source’s pages get no git-derived “Last updated” date.

Last updated on September 27, 2026

Was this page helpful?