Lifecycle Hooks

PluginLifecycleHooks — build and dev lifecycle hooks, transform chains, and chain signals for Boltdocs plugins.

hooks — PluginLifecycleHooksLink

Lifecycle hooks are the primary way plugins inject behaviour into the Boltdocs pipeline. Each hook receives a PluginContext as its first argument and can be synchronous or async.

Frontmatter & Route Pipeline hooksLink

HookSignatureDescription
frontmatter:transform(ctx, { frontmatter, filePath, rawContent }) => Record<string, unknown> | voidIntercepts & enriches document frontmatter (readingTime, lastUpdated, author data, custom tags).
routes:resolved(ctx, { routes }) => RouteMeta[] | voidFired after all documentation routes are crawled, normalized, and sorted.
ts
import { definePlugin } from 'boltdocs'

export default definePlugin({
  name: 'my-metadata-plugin',
  hooks: {
    'frontmatter:transform'(ctx, { frontmatter, rawContent }) {
      const words = rawContent.split(/\s+/).length
      return {
        ...frontmatter,
        readingTime: Math.ceil(words / 200),
        wordCount: words,
      }
    },
    'routes:resolved'(ctx, { routes }) {
      ctx.logger.info(`Resolved ${routes.length} total routes.`)
    },
  },
})

Search Contract hookLink

HookSignatureDescription
search:index(ctx, { documents, routes }) => unknown | Promise<unknown>Standardized search index hook. Receives SearchDocument[] to compile index files (FlexSearch, Pagefind, Algolia, Meilisearch).

SearchDocument interfaceLink

ts
interface SearchDocument {
  id: string
  path: string
  title: string
  content: string
  headings: Array<{ level: number; text: string; id: string }>
  frontmatter: Record<string, unknown>
  locale?: string
  version?: string
}
ts
import { definePlugin } from 'boltdocs'

export default definePlugin({
  name: 'my-search-plugin',
  hooks: {
    'search:index'(ctx, { documents }) {
      ctx.logger.info(`Indexing ${documents.length} documents for search...`)
      // Build search index...
    },
  },
})

Dev & Server lifecycle hooksLink

HookSignatureWhen it runs
dev:before(ctx: PluginContext) => void | Promise<void>Before the dev server starts listening
dev:after(ctx: PluginContext) => void | Promise<void>After the dev server is fully initialized
server:configure(ctx: PluginContext, { server, middleware }) => void | Promise<void>Configures dev/preview server HTTP middlewares and API endpoints
ts
import { definePlugin } from 'boltdocs'

export default definePlugin({
  name: 'my-dev-plugin',
  hooks: {
    'dev:before'(ctx) {
      ctx.logger.info('Starting dev server...')
    },
    'server:configure'(ctx, { middleware }) {
      middleware.useAt('/api/feedback', (req, res) => {
        res.end(JSON.stringify({ status: 'ok' }))
      })
    },
    'dev:after'(ctx) {
      ctx.logger.info('Dev server ready!')
    },
  },
})

Transform chain hooksLink

These hooks form a chain — the output of one plugin feeds the input of the next. They are executed in enforce order (pre → normal → post) within each phase.

HookSignatureWhen it runs
transform:source(ctx, { code, filePath, frontmatter? }) => { code, __signal? }On raw MDX source before MDX compilation
transform:mdx(ctx, { code, filePath, frontmatter? }) => { code, __signal? }On compiled MDX JavaScript after MDX compilation
transform:html(ctx, { html, path, route? }) => { html, __signal? }On rendered HTML during SSG generation

TransformSourceParamsLink

ts
interface TransformSourceParams {
  /** The raw or compiled code */
  code: string
  /** Absolute file path of the source document */
  filePath: string
  /** Parsed frontmatter, if available (undefined in early pipeline) */
  frontmatter?: Record<string, unknown>
}

TransformHtmlParamsLink

ts
interface TransformHtmlParams {
  /** The rendered HTML string for this page */
  html: string
  /** The route path (e.g. /docs/guides/start) */
  path: string
  /** The route metadata for the page, if available */
  route?: RouteMeta
}

Transform result & ChainSignalLink

ts
type ChainSignal = 'skip' | 'break'

type TransformResult<T> = T & { __signal?: ChainSignal }
SignalBehaviour
__signal: 'skip'The output of this hook is discarded; original params pass to the next plugin
__signal: 'break'The chain stops immediately — no further plugins run
ts
import { definePlugin } from 'boltdocs'

export default definePlugin({
  name: 'my-transform-plugin',
  hooks: {
    async transformSource(ctx, { code }) {
      const transformed = code
        .replace(/\$\$(.+?)\$\$/gs, '<BlockMath>$1</BlockMath>')
        .replace(/\$(.+?)\$/g, '<Math>$1</Math>')

      return { code: transformed }
    },
    async transformHtml(ctx, { html }) {
      return {
        html: html.replace('</body>', '<footer>© 2026 My Docs</footer></body>'),
      }
    },
  },
})

Execution orderLink

text
beforeBuild / beforeDev
  → frontmatter:transform
    → routes:resolved
      → transformSource (chain: pre → normal → post)
        → MDX compilation
          → transformMdx (chain: pre → normal → post)
            → HTML rendering (SSG)
              → transformHtml (chain: pre → normal → post)
                → search:index
                  → build:generate
afterBuild / afterDev
buildEnd

See alsoLink

Last updated on October 18, 2026

Was this page helpful?