LLM Markdown Documentation Spec
Overview#
The Pulumi Registry generates terminal-friendly markdown documentation for every resource and function in every provider package. All docs for a package are bundled into a single JSON file (llm-docs.json), with each entry containing pure markdown (no HTML, no Hugo shortcodes) designed for consumption by LLMs and rendering in a terminal. They contain documentation for all supported languages in a single entry, using a chooser comment syntax that a consumer filters at render time to show only the user's selected language.
File Location#
All LLM docs for a package are bundled in a single JSON file at:
https://www.pulumi.com/registry/packages/{package}/api-docs/llm-docs.json
Examples:
https://www.pulumi.com/registry/packages/aws/api-docs/llm-docs.jsonhttps://www.pulumi.com/registry/packages/random/api-docs/llm-docs.json
LLM Sitemap#
Each package has a sitemap at:
https://www.pulumi.com/registry/packages/{package}/llm-sitemap.json
This is only generated for top-level package pages, not for subpages.
Chooser Comment Syntax#
All multi-language content in cli.md files is wrapped in chooser comments. These are HTML comments (valid in markdown) that delimit language-specific blocks.
Format#
<!-- chooser: language -->
<!-- option: typescript -->
[content for TypeScript]
<!-- /option -->
<!-- option: python -->
[content for Python]
<!-- /option -->
<!-- option: go -->
[content for Go]
<!-- /option -->
<!-- option: csharp -->
[content for C#]
<!-- /option -->
<!-- option: java -->
[content for Java]
<!-- /option -->
<!-- option: yaml -->
[content for YAML]
<!-- /option -->
<!-- /chooser -->Rules#
- A
<!-- chooser: language -->block always ends with<!-- /chooser -->. - Each
<!-- option: {tag} -->block ends with<!-- /option -->. - Language tags are:
typescript,python,go,csharp,java,yaml.- Note: TypeScript is used for Node.js (the tag is
typescript, notnodejs).
- Note: TypeScript is used for Node.js (the tag is
- Not all languages are present in every chooser block. A language is omitted if the provider doesn't support it or if no data exists for that language.
- Chooser blocks can appear anywhere in the document: examples, constructors, properties, methods, etc.
- Chooser blocks are never nested. A chooser always contains flat option blocks.
How to Filter#
To display a single language, a consumer should:
- Find all
<!-- chooser: language -->...<!-- /chooser -->regions. - Within each region, extract only the content between
<!-- option: {selected-language} -->and<!-- /option -->. - Replace the entire chooser block with just the extracted content.
- If the selected language has no option in a given chooser, omit the entire block or show a fallback message.
Document Structure: Resources#
The title, description, and deprecation status are provided as structured metadata fields in each CLIDocEntry object (see JSON Bundle Format below). They are not embedded in the content field.
The content field contains the remaining sections in order. All sections are conditional (present only if the data exists).
1. Example Usage (optional)#
## Example Usage
<!-- chooser: language -->
<!-- option: typescript -->
{Example Title}
{code snippet}
<!-- /option -->
...
<!-- /chooser -->Examples are per-language. Each example has a title (as a markdown paragraph, not a heading) and a fenced code block with the language tag.
2. Create Resource (optional)#
## Create {Title} Resource
<!-- chooser: language -->
<!-- option: typescript -->
```typescript
new Bucket(name: string, args: BucketArgs, opts?: CustomResourceOptions)Parameters#
- name (string) — The unique name of the resource.
- args (BucketArgs) — The arguments to resource properties.
- opts (CustomResourceOptions) — Options for the resource.
...
Constructor syntax is a fenced code block. Parameters are a markdown list with bold names, optional type in parentheses, and optional description after an em dash.
### 3. Resource Properties
```markdown
## {Title} Resource Properties
### Inputs
<!-- chooser: language -->
<!-- option: typescript -->
- **bucketName** (string) *(required)* *(replaces on change)*
The name of the bucket.
- **acl** (string | CannedAcl)
The canned ACL to apply.
<!-- /option -->
...
<!-- /chooser -->
### Outputs
<!-- chooser: language -->
<!-- option: typescript -->
- **arn** (string)
The ARN of the bucket.
- **id** (string)
The provider-assigned unique ID.
- **accessKeySecret** (string) *(secret)*
The generated secret access key.
<!-- /option -->
...
<!-- /chooser -->
Each property is a markdown list item:
**{name}** ({type1} | {type2})— bold name, types in parens (pipe-separated if union)*(required)*suffix if required*(secret)*suffix if the schema marks the property secret*(replaces on change)*suffix if changing the property replaces the resource*(deprecated)*suffix if deprecated- Suffixes appear in that order, and a property may carry several
- Description on the next line, indented with 2 spaces
4. Methods (optional)#
## Methods
### {Method Title}
> **Deprecated:** {message}
{description}
<!-- chooser: language -->
<!-- option: typescript -->
bucket.getPolicy(args) -> GetPolicyResult
<!-- /option -->
...
<!-- /chooser -->
#### Arguments
<!-- chooser: language -->
...
<!-- /chooser -->
#### Result
<!-- chooser: language -->
...
<!-- /chooser -->
5. Look up Existing Resource (optional)#
## Look up Existing {Title} Resource
### State Inputs
<!-- chooser: language -->
...
<!-- /chooser -->6. Supporting Types (optional)#
## Supporting Types
### {Type Name}
{description}
<!-- chooser: language -->
...
<!-- /chooser -->7. Import (optional)#
## Import
{import instructions in plain markdown}8. Footer#
---
*{Package Display Name} · {Version}*Always present. A horizontal rule followed by the package name and version in italics.
Document Structure: Functions#
A function cli.md file has a similar but simpler structure. As with resources, title, description, and deprecation are in the CLIDocEntry metadata fields, not in the content.
The content field contains:
-
Example Usage (optional) — same format as resources
-
Usage — function signatures per language in a chooser block:
## Usage <!-- chooser: language --> <!-- option: typescript --> ```typescript getSubnet(args) -> GetSubnetResult...
-
Inputs (optional) — properties in chooser block
-
Outputs (optional) — properties in chooser block
-
Supporting Types (optional) — same as resources
-
Footer — same as resources
LLM Sitemap Format#
The llm-sitemap.json file describes the navigable pages for a package.
Schema#
{
"pages": [
{
"title": "Overview",
"path": "/registry/packages/{name}/"
},
{
"title": "Installation & Configuration",
"path": "/registry/packages/{name}/installation-configuration/"
},
{
"title": "API Docs",
"path": "/registry/packages/{name}/api-docs/",
"hasChildren": true,
"children": [
{
"title": "{Module Name}",
"path": "/registry/packages/{name}/api-docs/{module}/"
}
]
},
{
"title": "How-to Guides",
"path": "/registry/packages/{name}/how-to-guides/"
}
]
}Fields#
| Field | Type | Description |
|---|---|---|
pages |
array | Top-level navigation entries for the package |
pages[].title |
string | Display title for the page |
pages[].path |
string | URL path relative to the site root |
pages[].hasChildren |
boolean | (optional) true if this entry has child pages |
pages[].children |
array | (optional) Child page entries, same shape as pages[] |
Notes#
- Only the "API Docs" entry has
hasChildrenandchildren. - Children list the top-level modules/sections under API docs. Individual resources and functions within modules are not listed in the sitemap — they are discoverable from the module pages or by constructing URLs from the provider schema.
- The sitemap is only generated for package root pages. Requesting
llm-sitemap.jsonfor a subpage returns an empty JSON object{}. - "Installation & Configuration" and "How-to Guides" entries are only present if those pages exist for the package. Only a handful of packages have "How-to Guides" pages today — they are the hand-maintained migration guides (for example, under
aws,gcp,eks, andkubernetes).
Content Conventions#
- No HTML: All HTML tags are stripped during generation.
<br>becomes a newline,<a href>links are converted to markdown[text](url),<code>becomes backticks, etc. - No Hugo shortcodes: All Hugo-specific syntax is resolved before markdown generation.
- Code fences: Always use triple backticks with a language tag (e.g.,
```typescript). - Property types: Displayed as plain text names. Generic types use angle brackets (e.g.,
List<string>). Union types are pipe-separated (e.g.,string | CannedAcl). - Whitespace: There may be extra blank lines between sections due to template rendering. Consumers should normalize whitespace for clean display.
JSON Bundle Format#
All LLM docs for a package are served as a single JSON file at llm-docs.json.
Schema#
{
"package": "random",
"packageVersion": "4.19.1",
"overview": "The Pulumi Random provider...\n\n## Modules\n\n- [index](index/)\n\n## Package Details\n\n**Repository:** ...",
"resources": {
"randomstring": {
"title": "RandomString",
"description": "The resource random.RandomString generates a random string.",
"content": "## Create RandomString Resource\n\n..."
},
"randompassword": {
"title": "RandomPassword",
"description": "Generates a random password.",
"content": "## Create RandomPassword Resource\n\n...",
"deprecated": true,
"deprecationMessage": "Use RandomString instead."
}
},
"functions": {
"s3/getBucket": {
"title": "aws.s3.getBucket",
"description": "Returns information about an S3 bucket.",
"content": "## Usage\n\n..."
}
}
}Fields#
| Field | Type | Description |
|---|---|---|
package |
string | Package name |
packageVersion |
string | Provider version |
overview |
string | Markdown overview with module list, package details |
resources |
object | Map of {module}/{resource} to CLIDocEntry |
functions |
object | Map of {module}/{function} to CLIDocEntry |
CLIDocEntry Fields#
| Field | Type | Description |
|---|---|---|
title |
string | Display title (e.g., aws.s3.Bucket) |
description |
string | Plain text description of the resource/function |
content |
string | Markdown body (examples, properties, methods, etc.) |
deprecated |
boolean | (optional) true if the item is deprecated |
deprecationMessage |
string | (optional) Human-readable deprecation message |
Overview Field#
The overview field contains a clean markdown representation of the package index page, including:
- Package description
- Module list (as markdown bullet links)
- Root-level resources and functions (if any)
- Package details (repository, license, notes, version)
This allows consumers to display the package overview without fetching and parsing the web index.md page.
Key Format#
Keys use the path {module}/{name} for resources/functions within a module, or just {name} for resources/functions at the package root (no module). These match the URL path segments used in the web API docs.
Examples:
randomstring— resource at package root (no module)s3/bucket— resource ins3modules3/getbucket— function ins3moduleec2/transitgateway/route— resource in nested module
Consumer Behavior#
- Fetch
llm-docs.jsonfor the package (one HTTP request). - Look up the resource or function by its key.
- Use the
title,description, anddeprecated/deprecationMessagemetadata fields directly — do not parse them fromcontent. - Apply chooser filtering to the
contentfield to show only the user's selected language. - Cache the JSON locally to avoid re-fetching on subsequent lookups within the same package.
Size Considerations#
- Small packages (e.g.,
random): ~200 KB - Large packages (e.g.,
aws,azure-native): may be several MB uncompressed - CloudFront serves with gzip compression, significantly reducing transfer size
- Consumers should cache downloaded bundles locally