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})Top-level options
Section titled “Top-level options”actions
Section titled “actions”- 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, },})markdownUrl
Section titled “markdownUrl”- 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.
injectRoute
Section titled “injectRoute”- 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,})collections
Section titled “collections”- 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}' }, ],})Why path exists
Section titled “Why path exists”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-*.
What a collection needs
Section titled “What a collection needs”Two things, neither of them enforced by the plugin:
- A
titlein 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
.mdfiles 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.
renderMarkdown
Section titled “renderMarkdown”- Type:
'raw' | 'simple' | { module: string }— seeRenderMarkdownConfig - Default:
'raw'
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.
linkAlternate
Section titled “linkAlternate”- Type:
boolean |LinkAlternateConfig - Default:
false
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.
llmsTxt
Section titled “llmsTxt”- Type:
boolean |LlmsTxtConfig - Default:
false
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.
prompt
Section titled “prompt”- 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.mdroute. 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}',})triggerLabel
Section titled “triggerLabel”- Type:
string - Default:
'Copy page'
Visible label of the dropdown trigger button next to the page title.
starlightLlmActions({ triggerLabel: 'AI tools',})trigger
Section titled “trigger”- 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',})closeOnAction
Section titled “closeOnAction”- 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,})toastDuration
Section titled “toastDuration”- Type:
number(positive integer, milliseconds) - Default:
3000
How long the toast notification stays visible after an action completes.
starlightLlmActions({ toastDuration: 5000,})preOpenDelay
Section titled “preOpenDelay”- 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,})pageOptOut
Section titled “pageOptOut”- 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.
printNotice
Section titled “printNotice”- Type:
boolean |PrintNoticeConfig - Default:
false
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
PrintNoticeConfigobject — customise the warning, supply branding (logo + site name), or both. false(or omit) — disabled.
See Print/PDF snapshot notice for the full guide.
ActionsConfig
Section titled “ActionsConfig”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}actions.copyMarkdown
Section titled “actions.copyMarkdown”- Type:
boolean - Default:
true
Show the Copy as Markdown item. See Copy as Markdown.
actions.viewMarkdown
Section titled “actions.viewMarkdown”- Type:
boolean - Default:
true
Show the View as Markdown item. See View as Markdown.
actions.printPdf
Section titled “actions.printPdf”- 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.
actions.openIn
Section titled “actions.openIn”- 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.
OpenInConfig
Section titled “OpenInConfig”interface OpenInConfig { enabled?: boolean; // default: true label?: string; // default: 'Open in…' providers?: ProvidersConfig;}openIn.enabled
Section titled “openIn.enabled”- 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.
openIn.label
Section titled “openIn.label”- Type:
string - Default:
'Open in…'
Submenu label.
openIn.providers
Section titled “openIn.providers”- 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.
providers
Section titled “providers”Object keyed by provider id. Each value is either:
true— enable with defaults. Required for default-off providers.false— disable.- A
ProviderOverrideobject — 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.
ProviderOverride
Section titled “ProviderOverride”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;}enabled
Section titled “enabled”Whether the provider appears in the submenu. Equivalent to passing
<id>: false directly.
Display label. Default: provider’s built-in label (e.g. 'ChatGPT').
description
Section titled “description”Short description shown under the label. Default: provider’s built-in
description (e.g. 'Open this page in ChatGPT').
prompt
Section titled “prompt”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.
strategy
Section titled “strategy”One of 'url-prompt', 'inline-content', 'clipboard-open'. See
Concepts: per-provider strategies.
maxBytes
Section titled “maxBytes”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.
fallbackStrategy
Section titled “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.
RenderMarkdownConfig
Section titled “RenderMarkdownConfig”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.
'simple'
Section titled “'simple'”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:
npm install @astrojs/mdx unified rehype-parse rehype-remark remark-gfm remark-stringify hast-util-select unist-util-removeIf any are missing, the build fails at config time and names the exact install command, rather than failing partway through page rendering.
{ module }
Section titled “{ module }”- 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' },})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.
LinkAlternateConfig
Section titled “LinkAlternateConfig”interface LinkAlternateConfig { type?: string; // default: 'text/markdown' absolute?: boolean; // default: false}Passing true instead of an object is shorthand for {} — enabled with both
defaults.
linkAlternate.type
Section titled “linkAlternate.type”- 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.
linkAlternate.absolute
Section titled “linkAlternate.absolute”- 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.
export default defineConfig({ site: 'https://example.com', // …});<link rel="alternate" type="text/markdown" href="https://example.com/guides/example.md" />LlmsTxtConfig
Section titled “LlmsTxtConfig”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.
llmsTxt.title
Section titled “llmsTxt.title”- 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', },})# Acme Platform DocumentationllmsTxt.description
Section titled “llmsTxt.description”- 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.', },})# Acme Platform Documentation
> Guides, API reference, and runbooks for the Acme platform.llmsTxt.promote
Section titled “llmsTxt.promote”- 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.
llmsTxt.demote
Section titled “llmsTxt.demote”- 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*'], },})llmsTxt.exclude
Section titled “llmsTxt.exclude”- 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/**'], },})llmsTxt.subsets
Section titled “llmsTxt.subsets”- 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.
PrintNoticeConfig
Section titled “PrintNoticeConfig”interface PrintNoticeConfig { branding?: false | PrintNoticeBranding; warning?: false | PrintNoticeWarning;}Either field can be false to render only the other half.
printNotice.branding
Section titled “printNotice.branding”- 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.
printNotice.warning
Section titled “printNotice.warning”- Type:
false | PrintNoticeWarning - Default: built-in defaults when
printNotice: trueor thewarningfield is omitted under aprintNoticeobject
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.
Frontmatter options
Section titled “Frontmatter options”In addition to plugin options, individual pages can opt out via frontmatter:
---title: A page with no AI dropdownllmActions: false---The frontmatter key is configurable via pageOptOut. See
Per-page opt-out.