---
title: "@dualmark/astro"
description: Astro 5 integration -- auto-generated .md routes, middleware, llms.txt.
---

<Tabs items={["bun", "npm", "yarn"]}>
<Tab value="bun">
```bash
bun add @dualmark/astro @dualmark/core @dualmark/converters
```
</Tab>
<Tab value="npm">
```bash
npm install @dualmark/astro @dualmark/core @dualmark/converters
```
</Tab>
<Tab value="yarn">
```bash
yarn add @dualmark/astro @dualmark/core @dualmark/converters
```
</Tab>
</Tabs>

## `dualmark(config)`

The default export is a factory returning an `AstroIntegration`.

```ts
import { defineConfig } from "astro/config";
import dualmark from "@dualmark/astro";

export default defineConfig({
  integrations: [dualmark({ /* config */ })],
});
```

## Config

```ts
interface DualmarkAstroConfig {
  siteUrl: string;

  collections?: Record<string, CollectionConfig>;

  staticPages?: StaticPageConfig[];

  parameterizedRoutes?: ParameterizedRouteConfig[];

  llmsTxt?: {
    enabled?: boolean;
    brandName?: string;
    description?: string;
    sections?: LlmsTxtSection[];
  };

  middleware?: { injectLinkHeader?: boolean };

  cacheControl?: string;
  noindex?: boolean;
}
```

## Collections

```ts
collections: {
  blog: {
    converter: "blog" | "case-study" | "changelog" | "compare" | "docs"
              | "feature" | "glossary" | "legal" | "pricing" | "pseo"
              | "tool" | "video"
              | ((entry) => string),  // or custom function

    slugStrategy?: "single" | "nested",  // single = /blog/<slug>, nested = /blog/<...>

    listingMetadata?: {
      title?: string;
      description?: string;
    },
  },
}
```

For each collection, the integration generates:

- `/<collection>/<slug>.md` for every entry
- `/<collection>.md` for the listing

## Static pages

```ts
staticPages: [
  { pattern: "/", render: () => "# Home\n\nWelcome." },
  { pattern: "/about", render: () => "# About" },
],
```

Generates `/index.md` and `/about.md`.

## Parameterized routes

```ts
parameterizedRoutes: [
  {
    pattern: "/tax/[country]",
    getStaticPaths: async () => [
      { params: { country: "us" } },
      { params: { country: "uk" } },
    ],
    render: ({ country }) => `# Tax in ${country.toUpperCase()}\n\n...`,
  },
],
```

Generates `/tax/us.md`, `/tax/uk.md`.

## Middleware

When `middleware.injectLinkHeader` is `true` (default), the integration injects a middleware that adds the `Link: <...>; rel="alternate"; type="text/markdown"` header on every HTML response.

## llms.txt

When `llmsTxt.enabled` is `true`, generates `/llms.txt` from the provided sections.

```ts
llmsTxt: {
  enabled: true,
  brandName: "Acme",
  description: "Acme's docs and blog.",
  sections: [
    {
      title: "Pages",
      links: [
        { title: "Home", href: "https://example.com/" },
        { title: "Blog", href: "https://example.com/blog" },
      ],
    },
  ],
}
```

## Programmatic API

```ts
import {
  createDualmarkIntegration,
  resolveConfig,
  resolveBuiltInConverter,
  DualmarkConfigError,
} from "@dualmark/astro";
```

- `createDualmarkIntegration(config)` -- same as default, named alias
- `resolveConfig(input)` -- validate + normalize config (throws `DualmarkConfigError` on invalid input)
- `resolveBuiltInConverter({ name, collectionName, baseConfig })` -- get a converter by name
