Skip to content

Configuration reference

Every option you can pass to starlightLlmActions(). All options are optional; calling it with no arguments uses the defaults shown below.

starlightLlmActions({
// top-level options shown here
})
  • Type: ActionsConfig
  • Default: all on except printPdf (off); five built-in providers enabled by default — see Providers

Controls which dropdown items render and how the Open in… submenu behaves. See ActionsConfig below.

starlightLlmActions({
actions: {
copyMarkdown: true,
viewMarkdown: true,
printPdf: false,
openIn: true,
},
})
  • Type: string
  • Default: '/{slug}.md'

URL template the plugin uses for the page’s markdown source. {slug} is substituted with the page’s path segments. The template must start with / and contain {slug}; anything else fails the build with a message naming the value it got.

The leading slash is what lets the plugin join the template onto Astro’s base — a site served from /docs/ gets /docs/guides/example.md. Write the path as it appears after the base, not relative to the current page.

The home page is the one page whose path segments are not its entry id: its URL is /, so it has none, while its entry id is index. When a separator follows the placeholder, the root substitutes to nothing and the separator goes with it, so '/{slug}/index.md' puts the home page’s Markdown at /index.md rather than at /index/index.md. With '/{slug}.md' there is no way to write zero segments before a bare extension, so the root keeps index and still resolves to /index.md.

The default route is injected automatically. Sites that publish raw text under a different extension can override:

starlightLlmActions({
markdownUrl: '/{slug}.txt',
})

If your site already publishes per-page markdown via a different route, set injectRoute: false and point markdownUrl at it.

  • Type: boolean
  • Default: true

Whether the plugin should inject its own markdown route at the pattern derived from markdownUrl. Set to false if your site already publishes per-page markdown (e.g. via a custom HTML→markdown pipeline) and you only want the dropdown UI.

starlightLlmActions({
markdownUrl: '/raw/{slug}',
injectRoute: false,
})
  • Type: (string | CollectionConfig)[]
  • Default: ['docs']

The Content Collections the plugin publishes Markdown for. Every collection listed here gets a .md route per entry, a line in llms.txt, and its pages in llms-full.txt; a page in a collection not listed here gets none of that, and the Page Actions dropdown leaves out its Markdown items rather than pointing them at a URL that does not exist.

A bare string names a collection whose entry ids already are its site paths, which is true of Starlight’s own docs. Anything else needs the object form:

type CollectionConfig =
| string
| {
name: string;
path?: string; // default: '{id}'
};
Field Required Description
name yes Collection name, as declared in src/content.config.ts.
path no Site path for one entry, with {id} standing for the entry id. No leading or trailing slash.
starlightLlmActions({
collections: [
'docs',
{ name: 'changelog', path: 'changelog/entry/{id}' },
],
})

Starlight derives a docs page’s URL from its entry id, so the two are the same string. A collection with its own route file is under no such obligation: a changelog collection whose entries are 0-11-0 and 0-12-0 might serve them at /changelog/entry/0-11-0, and path is how you say so.

Getting it wrong is what the .md convention cannot survive — Markdown at /changelog/0-11-0.md for a page published at /changelog/entry/0-11-0/ is a URL no agent will guess. The template is validated at config time: it must contain {id} exactly once and carry no surrounding slashes.

The resulting path is what every other option works in. markdownUrl is built from it, and the globs in llmsTxt match it — so a changelog entry is changelog/entry/*, not 0-*.

Two things, neither of them enforced by the plugin:

  • A title in its schema. It becomes the # heading of the page’s Markdown and its label in every index. A collection without one fails the build with the offending entry named.
  • A route that renders it. The plugin publishes the Markdown twin of an HTML page; it does not create the HTML page. A collection with no route file gets .md files pointing nowhere.

A draft: true entry is skipped, in this collection as in docs. A schema with no draft field simply has no drafts to skip.

How the injected route renders each page body. The default emits the page’s unprocessed source, which on an MDX-heavy site means import statements and component tags rather than prose an AI agent can read directly.

starlightLlmActions({
renderMarkdown: 'simple',
})

'simple' renders the page the way Starlight does and flattens the result back to plain Markdown, so components resolve to lists, code fences, and headings. It requires optional dependencies you install yourself. See RenderMarkdownConfig below.

With injectRoute: false there is no route for the plugin to render, so the setting changes nothing about your output — but 'simple' still requires its dependencies to be installed whenever it is set.

Inject a per-page alternate-link tag pointing at that page’s Markdown URL, so crawlers and agents can discover the Markdown without knowing the plugin’s URL convention.

starlightLlmActions({
linkAlternate: true,
})

Every non-draft page then carries:

<link rel="alternate" type="text/markdown" href="/guides/example.md" />

The href follows markdownUrl and respects Astro’s base, and non-ASCII slugs are percent-encoded.

Two kinds of page get no tag, because the injected route generates no Markdown for them: drafts, and the 404 page. See LinkAlternateConfig below.

Generate site-level indexes: /llms.txt, /llms-full.txt, and one /llms-{subset}.txt per named subset.

starlightLlmActions({
llmsTxt: true,
})

llms.txt is the llmstxt.org index — your site title, its description, a link to each bundle, and a link to every page’s own Markdown route. llmsTxt.title and llmsTxt.description override the first two when your header wordmark reads poorly as a corpus name. llms-full.txt is every page concatenated into one file.

The bundles run the same renderer the Markdown route does, so every Markdown surface on your site carries identical output.

Requires site in your Astro config, because llms.txt links have to be absolute — an agent handed the file as a blob has no base to resolve against. The plugin throws at config time if site is unset.

See LlmsTxtConfig below for ordering, exclusion, and named subsets.

  • Type: string
  • Default: 'Read {md_url}. I want to ask questions about it.'

Default prompt sent to every “Open in…” provider that doesn’t define its own providers.<id>.prompt. Placeholders:

  • {md_url} — absolute URL of the current page’s .md route. Preferred for LLMs that fetch URLs, since they get clean markdown instead of rendered HTML.
  • {url} — absolute URL of the current page (rendered HTML).

The resolved prompt (with placeholders replaced) is what gets URL-encoded into provider URL templates and copied to the clipboard for clipboard-open providers.

starlightLlmActions({
prompt: 'Read this Acme docs page and answer in plain English: {md_url}',
})
  • Type: string
  • Default: 'Copy page'

Visible label of the dropdown trigger button next to the page title.

starlightLlmActions({
triggerLabel: 'AI tools',
})
  • Type: 'click' | 'hover'
  • Default: 'click'

Controls how the dropdown opens.

  • 'click' — the default. The trigger button must be clicked to open the menu; a second click closes it.
  • 'hover' — the menu opens when the cursor hovers over the trigger button. Clicking pins the menu open so it stays visible while you interact with it; a second click closes it. Keyboard navigation is identical in both modes.
starlightLlmActions({
trigger: 'hover',
})
  • Type: boolean
  • Default: true

Whether the dropdown closes after an item is clicked. Set to false to keep the menu open, which is useful when chaining several actions in a row.

Has no effect when trigger is 'hover'.

starlightLlmActions({
closeOnAction: false,
})
  • Type: number (positive integer, milliseconds)
  • Default: 3000

How long the toast notification stays visible after an action completes.

starlightLlmActions({
toastDuration: 5000,
})
  • Type: number (non-negative integer, milliseconds)
  • Default: 300

How long to wait after showing the toast before opening a new tab, for providers using the clipboard-open strategy. The delay gives the toast time to render before focus shifts away; set it to 0 to open immediately.

starlightLlmActions({
preOpenDelay: 0,
})
  • Type: string | false
  • Default: 'llmActions'

Frontmatter key that, when set to false on a page, hides the dropdown on that page. Set this option to false to disable the opt-out entirely. Set it to a string to use a different frontmatter key.

starlightLlmActions({
pageOptOut: 'showAiTools', // page sets `showAiTools: false`
})

See Per-page opt-out for details.

Snapshot disclaimer shown when the page is printed (Cmd/Ctrl+P, the dropdown’s PDF button, or browser menu). Hidden on screen; visible only in @media print.

  • true — enable the warning admonition with built-in defaults.
  • A PrintNoticeConfig object — customise the warning, supply branding (logo + site name), or both.
  • false (or omit) — disabled.

See Print/PDF snapshot notice for the full guide.

The actions object’s full shape:

interface ActionsConfig {
copyMarkdown?: boolean; // default: true
viewMarkdown?: boolean; // default: true
printPdf?: boolean; // default: false
openIn?: boolean | OpenInConfig; // default: true
}
  • Type: boolean
  • Default: true

Show the Copy as Markdown item. See Copy as Markdown.

  • Type: boolean
  • Default: true

Show the View as Markdown item. See View as Markdown.

  • Type: boolean
  • Default: false

Show the Download as PDF item. Off by default; the print dialog is always reachable via Cmd/Ctrl+P. See Download as PDF.

  • Type: boolean | OpenInConfig
  • Default: true

The Open in… submenu. true enables the five default-on providers; false removes the submenu. An OpenInConfig object lets you relabel the submenu and configure providers individually.

interface OpenInConfig {
enabled?: boolean; // default: true
label?: string; // default: 'Open in…'
providers?: ProvidersConfig;
}
  • Type: boolean
  • Default: true

Whether the submenu renders. Equivalent to setting openIn itself to a boolean, but useful when you want to keep other OpenInConfig settings around while temporarily disabling the submenu.

  • Type: string
  • Default: 'Open in…'

Submenu label.

  • Type: ProvidersConfig
  • Default: five default-on providers enabled (ChatGPT, Claude, Gemini, GitHub Copilot, Perplexity); the other eleven are off until opted into.

Per-provider configuration. See providers below.

Object keyed by provider id. Each value is either:

  • true — enable with defaults. Required for default-off providers.
  • false — disable.
  • A ProviderOverride object — customise label, prompt, URL template, strategy, etc. Also opts in any default-off provider.
  • undefined (omitted) — provider keeps its built-in default state.
interface ProvidersConfig {
// Default-on
chatgpt?: boolean | ProviderOverride;
claude?: boolean | ProviderOverride;
copilot?: boolean | ProviderOverride;
gemini?: boolean | ProviderOverride;
perplexity?: boolean | ProviderOverride;
// Default-off (opt in with `true` or an override object)
aistudio?: boolean | ProviderOverride;
cursor?: boolean | ProviderOverride;
deepseek?: boolean | ProviderOverride;
duckduckgo?: boolean | ProviderOverride;
grok?: boolean | ProviderOverride;
huggingchat?: boolean | ProviderOverride;
kagi?: boolean | ProviderOverride;
mistral?: boolean | ProviderOverride;
phind?: boolean | ProviderOverride;
t3chat?: boolean | ProviderOverride;
youcom?: boolean | ProviderOverride;
}

For the full catalog and per-provider defaults, see Configuration: Providers.

Every field is optional; unset fields fall back to the provider’s built-in defaults.

interface ProviderOverride {
enabled?: boolean;
label?: string;
description?: string;
prompt?: string;
url?: string;
strategy?: 'url-prompt' | 'inline-content' | 'clipboard-open';
maxBytes?: number;
fallbackStrategy?: 'url-prompt' | 'inline-content' | 'clipboard-open';
icon?: string | false;
}

Whether the provider appears in the submenu. Equivalent to passing <id>: false directly.

Display label. Default: provider’s built-in label (e.g. 'ChatGPT').

Short description shown under the label. Default: provider’s built-in description (e.g. 'Open this page in ChatGPT').

Prompt template specific to this provider. Falls back to the global prompt if unset.

URL template. Placeholders are URL-encoded at click time:

  • {prompt} — the resolved prompt (with {url} and {md_url} substituted).
  • {prompt_with_markdown}{prompt} + "\n\n" + the full page markdown.

For clipboard-open providers, this is the static URL opened after the prompt is copied to the clipboard.

One of 'url-prompt', 'inline-content', 'clipboard-open'. See Concepts: per-provider strategies.

For inline-content only. Maximum number of bytes the assembled URL may grow to. When the URL would exceed this, the plugin falls back to fallbackStrategy.

Strategy used when the inline-content URL exceeds maxBytes. Cursor’s default is 'url-prompt'.

  • An SVG filename under the package’s icons/ folder (e.g. 'cursor.svg').
  • An absolute URL.
  • false — hide the icon.
type RenderMarkdownConfig = 'raw' | 'simple' | RenderMarkdownModule;
interface RenderMarkdownModule {
module: string;
}

The default. Emits the Content Collection entry’s body verbatim — the original Markdown or MDX source, imports and component tags included.

Renders the page to HTML the way Starlight does, then flattens that HTML back to plain Markdown. Starlight’s <Tabs>, <FileTree>, <Steps>, and Expressive Code blocks arrive as ordinary Markdown lists, code fences, and headings instead of component tags.

Unknown custom elements are unwrapped, keeping their text content. Add data-mdast="ignore" to any element whose rendered output is pure chrome to drop it and its children entirely.

The flattening pipeline is heavy, so the plugin does not install it for you:

Terminal window
npm install @astrojs/mdx unified rehype-parse rehype-remark remark-gfm remark-stringify hast-util-select unist-util-remove

If any are missing, the build fails at config time and names the exact install command, rather than failing partway through page rendering.

  • Type: string

Module specifier for your own renderer, whose default export is a MarkdownRenderer. Relative paths resolve against your Astro project root.

starlightLlmActions({
renderMarkdown: { module: './src/render-markdown.ts' },
})
src/render-markdown.ts
import type { MarkdownRenderer } from 'starlight-llm-actions';
const render: MarkdownRenderer = async (entry, context) => {
return entry.body ?? '';
};
export default render;

Return the body only — the route still prepends the # title heading and the description blockquote. Throwing falls back to that page’s raw source with a warning, exactly as 'simple' does.

The value must be a module specifier rather than a function, because plugin config is serialized on its way into the injected route and a function cannot survive that trip.

interface LinkAlternateConfig {
type?: string; // default: 'text/markdown'
absolute?: boolean; // default: false
}

Passing true instead of an object is shorthand for {} — enabled with both defaults.

  • Type: string
  • Default: 'text/markdown'

The tag’s type attribute. Change it if your markdownUrl serves something else, e.g. 'text/plain' for a .txt template.

  • Type: boolean
  • Default: false

Emit a full URL instead of a root-relative path. The origin comes from site in your Astro config; the plugin throws at config time if site is unset, rather than emitting a broken href.

astro.config.mjs
export default defineConfig({
site: 'https://example.com',
// …
});
<link rel="alternate" type="text/markdown" href="https://example.com/guides/example.md" />

Passed to llmsTxt.

{
title?: string; // default: Starlight's title
description?: string; // default: Starlight's description
promote?: string[]; // default: ['index*']
demote?: string[]; // default: []
exclude?: string[]; // default: []
subsets?: { label: string; description?: string; paths: string[] }[];
}

Passing true instead of an object is shorthand for {} — enabled with all defaults.

Every pattern here matches a page’s site path (guides/example, not /guides/example/) through picomatch, the same glob dialect starlight-llms-txt uses. An existing pattern list ports over unchanged.

For a docs page the site path is the entry id. For a collection configured with a path template, it is the id run through that template — so one pattern dialect covers every collection, written the way the URLs read.

  • Type: string
  • Default: Starlight’s title

Name of the corpus. It becomes the llms.txt H1, the phrase in the “Complete documentation” set description, and the product name in every bundle’s <SYSTEM> preamble.

Starlight’s title is the site’s header wordmark, and a lot of sites keep that short — a site titled DOCS opens its llms.txt with # DOCS, which tells an agent nothing about whose docs it just fetched. The llms.txt spec makes that H1 the one required element in the file, so this is the way to satisfy it without renaming your header.

starlightLlmActions({
llmsTxt: {
title: 'Acme Platform Documentation',
},
})
llms.txt
# Acme Platform Documentation
  • Type: string
  • Default: Starlight’s description

Summary of the corpus, emitted as the blockquote under the H1.

Starlight’s description is a meta-description written for search results, which is a different job from telling an agent what the corpus covers. Setting this also gives the blockquote to a site that has no Starlight description at all — without either, the line is omitted.

Each field falls back on its own, so overriding the title leaves your existing description in place.

starlightLlmActions({
llmsTxt: {
title: 'Acme Platform Documentation',
description: 'Guides, API reference, and runbooks for the Acme platform.',
},
})
llms.txt
# Acme Platform Documentation
> Guides, API reference, and runbooks for the Acme platform.
  • Type: string[]
  • Default: ['index*']

Site paths sorted to the top of every index. Earlier patterns outrank later ones, so promote: ['index*', 'getting-started/**'] puts the home page first and the getting-started section second.

Pass [] to turn the default off and order everything alphabetically.

  • Type: string[]
  • Default: []

Site paths sorted to the end. Earlier patterns come before later ones, so the first pattern lands just after the un-ranked pages and the last pattern lands dead last.

A page matching both promote and demote is demoted — a page you pushed to the end was named more deliberately than one swept up by a broad promote glob.

starlightLlmActions({
llmsTxt: {
promote: ['index*', 'getting-started/**'],
demote: ['reference/**', 'changelog*'],
},
})
  • Type: string[]
  • Default: []

Site paths dropped from llms.txt and llms-full.txt. Drafts are already excluded.

This does not hide a page: an excluded page still serves its own Markdown route, and still carries its <link rel="alternate"> tag if linkAlternate is on. It only stops appearing in the two site-wide indexes.

A subsets entry whose paths name an excluded page still carries it, so a section too large for llms-full.txt can ship as its own bundle.

starlightLlmActions({
llmsTxt: {
exclude: ['internal/**', 'sandbox/**'],
},
})
  • Type: { label: string; description?: string; paths: string[] }[]
  • Default: []

Named slices of the docs, each emitted as its own bundle and listed in llms.txt. Useful when one section is large enough that an agent should be able to fetch it without the rest of the site.

starlightLlmActions({
llmsTxt: {
subsets: [
{
label: 'REST API',
description: 'the complete REST API reference',
paths: ['api/**'],
},
],
},
})

That emits /llms-rest-api.txt and adds this line to llms.txt:

- [REST API](https://example.com/llms-rest-api.txt): the complete REST API reference
Field Required Behavior
label yes Human-readable name. Lowercased and hyphenated to build the file name, so 'REST API' becomes /llms-rest-api.txt.
description no Appended after the subset’s link in llms.txt.
paths yes Globs matching the site paths to include. At least one. Takes precedence over exclude.

A subset inherits the promote/demote ordering, so its pages keep the relative order llms-full.txt gives them. It does not inherit exclude: paths is the narrower statement and wins, which is what lets one config drop a section from llms-full.txt and publish it as a bundle at the same time.

paths that match no page is a build error rather than a bundle holding nothing. The globs run against site paths, so write guides/example, not /guides/example.md.

Two labels that reduce to the same file name are a config error rather than a silent overwrite, and 'Full' is reserved for /llms-full.txt.

interface PrintNoticeConfig {
branding?: false | PrintNoticeBranding;
warning?: false | PrintNoticeWarning;
}

Either field can be false to render only the other half.

  • Type: false | PrintNoticeBranding
  • Default: none

Branding row rendered above the page H1 in print/PDF.

interface PrintNoticeBranding {
logo?: PrintNoticeLogo;
siteName?: string;
}
interface PrintNoticeLogo {
src: string; // URL or site-relative path
alt?: string;
height?: string; // CSS height, default '1.5rem'
}

src is emitted as written. If your Astro config sets a base, include it (/my-docs/logo.svg), or the image resolves against the domain root and the branding row renders broken.

  • Type: false | PrintNoticeWarning
  • Default: built-in defaults when printNotice: true or the warning field is omitted under a printNotice object

Warning admonition rendered below the page H1 in print/PDF.

interface PrintNoticeWarning {
title?: string; // default: 'Documentation Snapshot'
message?: string[]; // each entry renders as a <p>
showUrl?: boolean; // default: true
showDate?: boolean; // default: true
urlLabel?: string; // default: 'Live version: '
dateLabel?: string; // default: 'Exported: '
}

See Print/PDF snapshot notice for examples.

In addition to plugin options, individual pages can opt out via frontmatter:

---
title: A page with no AI dropdown
llmActions: false
---

The frontmatter key is configurable via pageOptOut. See Per-page opt-out.