Redocly
Redocly v0.1.5
Write, lint and document APIs.
From the developer
Declared capabilities
- Read
- Write
Declared limitations
- A question about another vendor's product, Swagger UI. The Redocly docs do not cover it. Expected: the assistant answers from general knowledge or says that the question is outside the Redocly docs. It calls no Redocly tool and does not present Redoc settings as Swagger UI settings.
- A change to a Reunite project. The plugin can only read the docs and report a docs problem. It cannot change projects. Expected: the assistant says that it cannot change the project, and it can link the docs steps. It does not claim that the domain is set, and it does not call submit-feedback.
- A feedback request without the page or the problem. submit-feedback needs both. Expected: the assistant asks which page is wrong and what is wrong. It does not call submit-feedback until the user gives both, and it does not invent a page or a problem.
Not specified in the available declarations: pricing & account requirements.
Onboarding example
Lint my OpenAPI description with Redocly CLI and explain each problem.
Sources & dates
Developer declarations; not independently tested. Listing last checked Nov 5, 2026. Package observed Nov 4, 2026.
Description
From the marketplace listing
Write, lint and document APIs with Redocly. Skills for Redocly CLI, lint rules and Redocly docs projects, plus live search across the Redocly documentation through the Redocly MCP server.
Language: English · Automatically detected from descriptions.
Changes
No changes recorded yet. Follow this plugin to keep track of future changes.
First seen Nov 2, 2026. Last seen Nov 5, 2026.
Similar plugins
Find competitors to follow as you develop your plugin.
-
ReadMe MCP server packaged as a plugin. Search, read, and update your ReadMe docs and API reference from your editor.
Shared terms: document, documentation, server, mcp
-
Developer Hub MCP helps users search Sabre Developer Hub documentation by artifact name or content, inspect artifact details, fetch docum...
Shared terms: document, documentation, mcp, search
-
Sinch Build skills and developer documentation for Codex: Conversation API (SMS, RCS, WhatsApp), Voice, Verification, Numbers, 10DLC, Fax...
Shared terms: document, apis, documentation, server
-
Local, zero-token documentation discovery telemetry for Codex and Claude Code.
Shared terms: document, documentation, live
-
Account-linked, versioned developer documentation with official source URLs via OAuth 2.1 MCP.
Shared terms: document, documentation, mcp, search
Keywords
Files & skills
File archives
Skill instructions
recheck-config4.92 KB
---
name: recheck-config
description: Use when you create or tune the recheck block and recheck/* presets in redocly.yaml for a project — choose presets, set severities from measured counts, add exceptions, adopt a baseline, and validate the result.
---
# Configure recheck in redocly.yaml
Configure `redocly recheck` for a project the way its maintainers would: measure first, decide per rule, and record intent.
The documentation is at [redocly.com/docs/cli/recheck](https://redocly.com/docs/cli/recheck); favor it over memory for rule names and options.
## How the configuration is shaped
Presets go in the root `extends`.
Adjustments go in the `recheck` block, with rules nested under `rules`.
The `lint` command ignores both, so they live next to the API ruleset:
```yaml
extends:
- recommended
- recheck/markdown
- recheck/prose
recheck:
excludes:
- CHANGELOG.md
rules:
recheck/line-length: off
recheck/no-duplicate-heading:
severity: warn
assertions:
no-duplicate-heading:
siblingsOnly: true
```
Rules of the shape:
- A rule key is `<namespace>/<name>`: `recheck/line-length`, `google/no-via`, or your own, such as `acme/product-names`.
- A severity string (`off`, `info`, `warn`, `error`) adjusts a rule that a preset defines.
For a key no preset defines, it is a configuration error, because the rule has no assertions.
- A rule object merges over the preset's rule, but its `assertions` merge by assertion id: one option you set on a `swap` or `pattern` assertion replaces the preset's whole word list.
To reject one term from a preset's list, turn the rule off, restate the full list, or use an inline directive.
- A new prose rule needs `severity`, `message`, and `assertions`.
A new Markdown rule, such as `recheck/list-length`, needs only `severity` and `assertions`.
- The block does not accept `extends` or `baseline`; both are configuration errors.
- The command reads the root block only.
A `recheck` block under `apis.<name>` does nothing and prints a warning.
## Start a new config
1. Begin with the structural preset, and run recheck to validate it:
```yaml
extends:
- recheck/markdown
```
```bash
npx @redocly/cli recheck docs
npx @redocly/cli check-config
```
The `recheck` command reports configuration errors before it lints anything, and `check-config` validates the block's shape.
2. Add prose presets deliberately, not wholesale.
`recheck/prose` is a small starter at `warn`.
`recheck/google` and `recheck/microsoft` are large style guides, detection-only, that need tuning before they help.
`recheck/plain-language`, `recheck/inclusive-language`, and `recheck/technical-english` compose onto either.
3. If the project writes Markdoc tags, add `recheck/markdoc` to `extends` and `markdoc: true` to the block.
Without the flag, the Markdoc rules never report.
## Tune severities from measurements, not taste
1. Run the whole document set and count findings per rule:
```bash
npx @redocly/cli recheck docs --summary=json --summary-path=recheck-summary.json
```
The summary's `breakdown` has the count per rule and severity.
For examples of each rule's findings, run `--rule=<name>` and read a few.
2. Decide per rule from the counts:
- `error` — enforce now; the document set is clean or you fix it in the same change.
- `warn` — a worklist; visible, not blocking.
- `off` — decided against; keep a YAML comment that says why, or the decision is lost.
3. Prefer fixing content over configuring around it.
Run `--fix` first; most structural findings repair themselves.
Review the fix list before you commit: fixes that re-indent lines can move content out of an indented Markdoc tag body.
## Exceptions, in order of preference
1. Fix the content.
2. The top-level `excludes` list, or a rule's `excludes` or `appliesTo`, for generated or frozen content such as archives and vendored docs — with a comment naming the reason.
3. `exceptions.lines` for a recurring true positive that must stay as written.
4. An inline `<!-- recheck-disable-next-line rule-name -->` for a single line.
## Adopt strictness on a large document set with a baseline
When the document set has too many errors to fix at once, record them and gate only new ones:
```bash
npx @redocly/cli recheck docs --generate-baseline
```
Commit `.redocly.recheck-baseline.yaml`.
The command writes it next to `redocly.yaml`, and later runs pick it up by presence; there is no configuration key.
It records errors only, as one count per file per rule.
Counts only step down: when findings get fixed, the run reports stale entries, so regenerate the baseline and commit the diff.
## Finish
- `npx @redocly/cli recheck docs` and `npx @redocly/cli check-config` report no configuration errors.
- Every `off` and every exception carries a comment with its reason.
- The project's contributing docs say how contributors run recheck locally, and CI runs it with `--format=github-actions`.
recheck-lint2.78 KB
---
name: recheck-lint
description: Use when you write or edit Markdown in a project whose redocly.yaml has a recheck block or a recheck/* preset in extends — run redocly recheck on the touched files before you commit them or hand them over, and fix what it finds.
---
# Lint Markdown with recheck before you commit it
This project lints Markdown structure and prose with `redocly recheck`.
Your Markdown must pass it, the same as code must pass its tests.
## When this applies
Look for a `redocly.yaml` in the project root or above the files you touched.
This skill applies when that file has a `recheck` block, or a `recheck/*` preset in its root `extends`, for example:
```yaml
extends:
- recommended
- recheck/markdown
recheck:
rules:
recheck/line-length: off
```
If neither exists, skip this skill.
The command checks nothing in that case and says so.
## What to do
1. After you write or edit Markdown files, run recheck on them:
```bash
npx @redocly/cli recheck <file-or-directory>
```
Pass the files you touched, not the whole repository, so the report is yours to act on.
2. If it reports errors, run the auto-fix first — most structural findings repair themselves:
```bash
npx @redocly/cli recheck <file-or-directory> --fix
```
The command lists every edit it made.
Read the list: a fix inside a Markdoc tag body or a code sample deserves a look.
3. Fix the remaining errors by hand.
Read each message; the rule name links the finding to its intent.
To see one rule at a time, add `--rule=<name>` with the name the report prints.
4. Re-run until it reports no errors, and only then commit or output the content.
The command exits with code 1 while errors remain.
## Rules of conduct
- Fix content instead of suppressing findings.
Suppress only a true positive that must stay as written, with an inline directive on that one line:
```markdown
<!-- recheck-disable-next-line recheck/rule-name -->
```
Name the rule by its full key or by the short name the report prints.
Never disable a rule project-wide to make your change pass, and never edit the `recheck` block to get past a finding.
- Warnings and info findings do not block you.
Leave them unless the task asks for cleanup.
- If a `.redocly.recheck-baseline.yaml` file sits next to `redocly.yaml`, the baseline already covers old findings; only new findings are yours to fix.
Do not run `--generate-baseline` to absorb findings your change introduced.
If your change removed old findings, the run reports stale baseline entries; regenerate the baseline then, and commit the smaller file with your change.
- A finding you believe is a false positive is worth reporting to the maintainer, not silently suppressing.
- Do not add `markdownlint` or `vale` comments.
Recheck does not read them.
redocly11.9 KB
---
name: redocly
description: >-
Set up, write, preview, and publish documentation with Redocly, and work on existing Redocly projects.
Use when the user wants to set up Redocly in a repository, create a docs site, document an API from an OpenAPI, AsyncAPI, or GraphQL description, preview docs locally, publish through Reunite, clone a Redocly-hosted project, or run Redocly CLI.
Also use when editing a project with redocly.yaml, sidebars.yaml, or a @theme folder, and when unsure which Redocly skill applies.
---
# Redocly
Redocly makes the tools for API documentation and developer portals.
## Redocly products
- **Realm** is the engine.
It converts a folder of content into the site: Markdown pages, API descriptions (OpenAPI, AsyncAPI, GraphQL, SOAP) that become API reference docs, and React pages.
The file path of a content file is its URL.
`redocly.yaml` holds the site settings, `sidebars.yaml` the navigation, and `@theme/` the custom styles and components.
None of them is required.
- **Reunite** is the cloud platform.
It hosts the project's Git repository, or connects to one on GitHub, GitLab, Bitbucket, or Azure DevOps.
It builds a preview for every pull request, publishes the production branch, and provides the browser editor, access control, and analytics.
- **Redocly CLI** is the open-source command-line tool, run as `npx @redocly/cli` (or `redocly` after `npm i -g @redocly/cli`).
| Command | Use |
| --- | --- |
| `preview` | Serve the project locally with live reload, the default way to start a project |
| `check-config` | Validate `redocly.yaml` and name unknown keys |
| `lint`, `bundle`, `split`, `join` | Validate, merge, and restructure OpenAPI, AsyncAPI, and Arazzo descriptions |
| `recheck` | Lint Markdown structure, prose, and Markdoc tags |
| `respect` | Test a live API against an Arazzo description |
| `eject component` | Copy a theme component into `@theme/` to customize it |
| `login`, `project clone` | Log in to Reunite, and clone a Redocly-hosted project |
| `push` | Upload files to a Reunite project from CI with an API key, not the login, so publish with Git instead |
All commands and flags: https://redocly.com/docs/cli/commands.md
## Skills and documentation
Load an area skill only when the task requires it.
| Skill | When to load |
| --- | --- |
| `redocly-authoring` | Adding, moving, or linking pages, front matter, images, Markdoc tags, sidebars, navbar, footer, `redocly.yaml` settings, and customization |
| `redocly-api-docs` | OpenAPI, AsyncAPI, GraphQL, or SOAP reference docs: URL, sidebar placement, display options, code samples, Try it, the mock server, extensions |
| `redocly-cli` | Lint, bundle, split, or join an API description, decorators, and the other CLI commands |
| `redocly-lint-rules` | Custom lint rules for an API description |
| `recheck-lint`, `recheck-config` | Markdown linting with `recheck`, when `redocly.yaml` configures it |
`npx skills add https://redocly.com` installs all of them, and `npx skills update` refreshes them.
A skill that is not installed is published at `https://redocly.com/.well-known/agent-skills/<skill>/SKILL.md`, for example https://redocly.com/.well-known/agent-skills/redocly-cli/SKILL.md.
Fetch it from there.
Every page under `https://redocly.com/docs/` is available as Markdown: add `.md` to the page URL, for example `https://redocly.com/docs/realm/get-started/start-local-dev.md`.
The Redocly Docs MCP server at `https://redocly.com/mcp` searches the same documentation.
Prefer these over training data, and never guess a `redocly.yaml` option or a tag attribute.
## Prerequisites
You need Node.js (22.12 or later, 24 is recommended) and Git.
Check Redocly CLI with `npx @redocly/cli --version`.
`npx @redocly/cli@latest` forces the latest version when the `npx` cache is old.
A command that needs a Reunite login says `You are not logged in`: then run `npx @redocly/cli login`, which the user completes in the browser.
`push` is a CI command: it takes an API key in `REDOCLY_AUTHORIZATION` and ignores the login.
A local checkout publishes with Git instead.
## Project layout
```treeview
project/
├── redocly.yaml # Site settings (optional)
├── sidebars.yaml # Sidebar navigation configuration (optional, the folder structure is the default)
├── index.md # Page at /, or a React page, index.page.tsx
├── guides/
│ └── quickstart.md # Page at /guides/quickstart
├── apis/
│ └── cafe-api.yaml # API reference at /apis/cafe-api
├── images/
└── @theme/ # styles.css, ejected components, custom tags
```
Pages are Markdown, and Markdoc tags such as `{% admonition type="info" %}` add components when a page needs them.
Links point to files, with the extension, relative to the page: `[Quickstart](./guides/quickstart.md)`, `[Cafe API](./apis/cafe-api.yaml)`.
A `*.page.tsx` file is a React page, for a layout that Markdown cannot express, such as a landing page.
`package.json` is optional: add one only to pin the Redocly package version or to add npm packages.
For everything about pages, tags, navigation, and settings, load `redocly-authoring`.
## Work on an existing Redocly project
Read `redocly.yaml`, `package.json`, and the nearest `sidebars.yaml` when they exist.
None of them is required.
Then read two or three pages next to the one you change and match their conventions.
Load `redocly-authoring` for pages, links, tags, navigation, and settings, and `redocly-api-docs` for API reference docs.
## Work on a Redocly-hosted project
A Redocly-hosted project has no external Git repository: Reunite hosts it, and Redocly CLI clones it.
The organization and project slugs are in the Reunite URL, `https://app.cloud.redocly.com/org/<organization>/project/<project>`:
```bash
npx @redocly/cli login
npx @redocly/cli project clone <organization>/<project>
cd <project>
```
`project clone` creates the folder as an ordinary Git repository.
It also makes Redocly CLI the Git credential helper for Reunite in that repository, so `git pull` and `git push` need no token.
When the Reunite host is not `app.cloud.redocly.com`, add `--residency eu` (for `app.cloud.eu.redocly.com`) or `--residency https://<host>` to both commands.
If the CLI does not know the `project` command, retry once with `npx @redocly/cli@latest`.
If it is still unknown, report the CLI version to the user and stop.
Git access is in early access.
If the clone answers that Git access is not enabled for the organization, tell the user and stop.
They can keep editing in the Reunite editor, connect the project to their own Git provider in **Settings > Git hosting**, or download a ZIP there.
## Set up Redocly in an existing codebase
When the user wants docs for a repository, run this workflow yourself.
Ask the user only for information or approvals you cannot supply.
1. Look at the folder.
`redocly.yaml`, `sidebars.yaml`, or `@theme/` means a Redocly project already exists: see Work on an existing Redocly project.
Otherwise ask the user whether the repository is already a Redocly project: an API description alone can be one.
2. Ask the user where the docs live: at the repository root or in a folder such as `docs/`.
At the root, `index.md` sits next to the code, and a root `README.md` is ignored.
In a folder, run the Redocly commands from it (`cd docs`).
Reunite builds only that folder, so move an API description that sits outside it into the folder.
Check first that no code or CI reads the description at its current path.
3. Create `index.md` with a heading and a paragraph when there is no Markdown page.
Nothing else is required: an API description anywhere in the project renders at its path.
4. When the codebase exposes an API without a description, offer to write a basic OpenAPI description from the code.
Document the API as the description has it.
Do not hide operations, add decorators, or reformat the file unless the user asks, and mention those options in your summary instead.
Validate it with `npx @redocly/cli lint <file>`.
Add `--extends recommended` when `redocly.yaml` defines no rules.
Or infer one from recorded HTTP traffic with `npx @redocly/cli generate-spec` (experimental): https://redocly.com/docs/cli/commands/generate-spec.md
Offer basic pages too, such as a getting-started guide written from the README and the code.
Write them with the `redocly-authoring` skill.
5. Preview and verify, as below.
Stop here unless the user asked to publish now.
Leave the changes uncommitted and list the Publish steps in your summary.
## Preview and verify
```bash
TERM=dumb redocly preview # with Redocly CLI installed
TERM=dumb npx @redocly/cli preview # without
```
`redocly preview` is the default way to start a project.
No install step is needed unless the project has a `package.json`.
Then install its dependencies with the package manager of the lock file first, and run its start script when it has one.
Always set `TERM=dumb`: without it the preview opens an interactive console whose output you cannot read.
The preview never exits: run it in the background with its output in a file outside the project.
When you are done, stop the process you started, by its process ID or background task.
Never stop processes by name or pattern, such as `pkill -f preview`, which kills the user's other previews.
The first run downloads Realm and takes a minute.
The preview writes nothing into the project folder.
The preview prints its URL, `http://localhost:4000` by default or the next free port when 4000 is taken, so read the URL from the output.
It rebuilds when a file changes.
`-p` picks another port, and `-d docs` (`--project-dir`) previews a project in a subfolder.
Its output is your feedback loop.
After every build it reports the Markdoc errors and broken links.
A clean build prints `Status: No errors found`.
Then verify:
1. Read the preview output after the rebuild: no errors, and the page count went up when you added a page.
2. Open the page at the URL the preview printed plus the page path.
The file path is the URL: `guides/quickstart.md` is `/guides/quickstart`, and `index.md` is `/`.
Use a browser tool when you have one, or `curl -s` to read the HTML.
Check that the content, the sidebar entry, and the links are there.
3. In the browser, read the console: a clean page logs no errors.
4. After a change to `redocly.yaml`, run `npx @redocly/cli check-config`, which names unknown keys.
The preview is the check for the site.
There is no local production build, Reunite builds it.
## Publish
Reunite publishes the site from the project's Git repository.
Publish only when the user asks for it.
A project that is not in Reunite yet needs these steps first, and the user does them in the browser:
1. Sign in to Reunite, or sign up at `https://app.cloud.redocly.com`.
2. **Create new project**, then **Connect existing repository** or choose Redocly-hosted, then the Git provider, organization, repository, and branch.
GitHub needs the Redocly GitHub App installed on the organization first: https://redocly.com/docs/realm/reunite/project/connect-git/github.md
3. Select the monorepo folder when the docs live in one, so that Reunite builds the same folder as the local preview.
When that folder is not on the default branch yet, merge the first pull request before you create the project.
Then, for a change the user wants published, commit on a branch (the current feature branch, or a new one) and push it:
```bash
git switch -c update-quickstart
git add guides/quickstart.md
git commit -m "Update the quickstart"
git push -u origin update-quickstart
```
For a project connected to a Git provider, open the pull request there, and Reunite builds a preview for it.
For a Redocly-hosted project, Reunite rejects pushes to the default branch.
After a successful push, Git prints two Reunite links: one to open a pull request for the branch and one to the branch's builds.
Give both links to the user.
Merging the pull request builds and publishes the production site, so the user decides when to merge.
redocly-api-docs8.47 KB
---
name: redocly-api-docs
description: >-
Use when adding or changing API reference docs in a Redocly project: rendering an OpenAPI, AsyncAPI,
GraphQL, or SOAP (WSDL) description, its URL and place in the sidebar or navbar, code samples, the
Try it (Replay) console, the mock server, x- extensions such as x-tagGroups or x-codeSamples, or
embedding one operation or schema in a Markdown page.
Not for linting, bundling, or splitting API descriptions (use redocly-cli).
---
# Add API reference docs to a Redocly project
## The file is the reference
Put the API description anywhere in the project.
Realm renders it as reference docs at the path of the file, with a sidebar built from its tags and operations.
OpenAPI, AsyncAPI, and GraphQL files need no configuration, and WSDL needs the SOAP plugin first, see below.
| File | URL |
| --- | --- |
| `apis/cafe-api.yaml` | `/apis/cafe-api` |
| `api-reference/index.yaml` | `/api-reference` |
Supported descriptions: OpenAPI (any version, YAML or JSON), AsyncAPI 3.0, GraphQL SDL (`.graphql`, `.gql`), and WSDL with the SOAP plugin.
For a new description, use the latest OpenAPI or AsyncAPI version unless the user asks for another.
To change the URL, move or rename the file, and the mock server path below follows it.
Link to the reference by its file path, like any page:
```yaml
# sidebars.yaml, navbar, or footer
- page: apis/cafe-api.yaml
label: Cafe API
```
The internal sidebar of the reference comes from the description itself.
Shape it with `x-tagGroups` and the other extensions below, not with `sidebars.yaml`.
Render the description as it is.
Hiding operations, with `x-internal` and a decorator or by leaving a tag out of `x-tagGroups`, and reformatting the file are the user's decisions, so ask first.
Full reference: https://redocly.com/docs/realm/content/api-docs/add-openapi-docs.md
## AsyncAPI, GraphQL, and SOAP
- AsyncAPI 3.0 files render like OpenAPI files, with `asyncapi` options and their own extensions: https://redocly.com/docs/realm/content/api-docs/add-asyncapi-docs.md
- GraphQL SDL files get a sidebar from their queries, mutations, and types, and `graphql.menu` groups them: https://redocly.com/docs/realm/content/api-docs/add-graphql-docs.md
- SOAP needs `@redocly/portal-plugin-soap-api` and `@redocly/realm` in `package.json`, `plugins: ['@redocly/portal-plugin-soap-api/plugin.js']` in `redocly.yaml`, and `.wsdl` files: https://redocly.com/docs/realm/content/api-docs/add-soap-docs.md
## OpenAPI display options
Options go under the top-level `openapi` key of `redocly.yaml` for every API, or under `apis.<name>.openapi` for one API.
Both merge, and the per-API value wins.
With one API, the top-level key is enough.
Use the per-API form for a setting that must not apply to APIs added later.
`asyncapi` and `graphql` work the same way.
```yaml
openapi:
layout: stacked
hideReplay: true
codeSamples:
languages:
- lang: curl
- lang: Python
apis:
cafe_api:
root: apis/cafe-api.yaml
openapi:
hideReplay: false
```
| Option | Default | Effect |
| --- | --- | --- |
| `layout` | `three-panel` | `stacked` moves the samples into the middle panel |
| `hideReplay` | `false` | Hide the Try it buttons |
| `codeSamples.languages` | a default set | Which generated samples to show, in order, by `lang` (`curl`, `JavaScript`, `Python`, ...), and `label` renames a tab. Supported values: https://redocly.com/docs/realm/config/openapi/code-samples.md |
| `hideDownloadButtons` | `false` | Hide the download links, which does not hide the description |
| `downloadUrls` | - | Download links: a list of `title` and `url` |
| `schemaDefinitionsTagName` | - | List all schemas in a sidebar section with this name |
| `showExtensions` | `false` | Show `x-` fields in the docs |
| `sortRequiredPropsFirst` | `false` | Required properties first |
| `excludeFromSearch` | `false` | Keep the API out of search and `llms.txt` |
All options: https://redocly.com/docs/realm/config/openapi.md
An `apis` entry is optional.
Add one only when an API needs its own lint rules, decorators, or rendering options, or a CLI shortcut name such as `redocly lint cafe_api`.
Its key is a name, or `name@version`, and `root` is required.
It does not add a page and does not set the URL: https://redocly.com/docs/realm/config/apis.md
## Code samples
Realm generates request samples in the configured languages.
For a language it does not generate, or a hand-written sample, add `x-codeSamples` to the operation.
`source` is inline code or a `$ref` to a file:
```yaml
paths:
/orders:
post:
x-codeSamples:
- lang: Python
source:
$ref: ../code-samples/create-order.py
```
When `codeSamples.languages` is set, list the sample's `lang` there too, or the sample does not show.
Full reference: https://redocly.com/docs/realm/config/openapi/code-samples.md
## Try it and the mock server
Replay is the Try it console.
Hide it for all APIs or one API with `hideReplay`, or for one operation with `x-hideReplay: true`.
It sends requests through a built-in CORS proxy, so the API needs no CORS headers.
The mock server is on by default and answers with the examples from the description at `/_mock/<path-to-description>/`, for example `/_mock/apis/cafe-api/`.
Turn it off with `mockServer.off: true`: https://redocly.com/docs/realm/config/mock-server.md
Prefilling request values, headers, or auth for Replay is a customization (an ejected `configure.ts`): https://redocly.com/docs/realm/customization/configure-request-values.md
## OpenAPI extensions
| Extension | Where | Effect |
| --- | --- | --- |
| `x-tagGroups` | root | Group tags in the sidebar, and a tag in no group does not show |
| `x-displayName` | tag | Tag label in the sidebar |
| `x-traitTag` | tag | A tag that describes operations without grouping them |
| `x-tags` | schema | Add a schema to a tag section |
| `x-badges` | operation, parameter, schema | Badges with `name`, `color`, `position` |
| `x-enumDescriptions` | schema | Describe enum values, and only listed values show |
| `x-rbac` | any object | Team-to-role map that hides the object from other users |
| `x-seo` | operation | Title, description, and image of the operation page |
| `x-webhooks` | root | Webhooks in OpenAPI 3.0 and earlier |
```yaml
x-tagGroups:
- name: Ordering
tags: [Menu, Orders]
- name: Account
tags: [Customers]
```
All extensions: https://redocly.com/docs/realm/content/api-docs/openapi-extensions.md
## API content in Markdown pages
Show one operation or schema in a guide with the API content tags:
```markdoc
{% openapi-code-sample descriptionFile="../apis/cafe-api.yaml" operationId="createOrder" language="curl" /%}
```
`openapi-response-sample` shows the response and `replay-openapi` embeds Try it for one operation.
Both take the same `descriptionFile` and `operationId` (or `pointer`) attributes.
`json-schema` takes a `schema` object: `{% json-schema schema={ "$ref": "../apis/cafe-api.yaml#/components/schemas/Order" } /%}`.
Language values are case-sensitive, as in `codeSamples`.
References: https://redocly.com/docs/realm/content/markdoc-tags/openapi-code-sample.md, https://redocly.com/docs/realm/content/markdoc-tags/replay-openapi.md, https://redocly.com/docs/realm/content/markdoc-tags/json-schema.md
## Verify
1. For OpenAPI and AsyncAPI, `npx @redocly/cli lint apis/cafe-api.yaml` validates the description.
Add `--extends recommended` when `redocly.yaml` configures no rules.
GraphQL and WSDL files are checked by the preview only.
Lint rules, bundling, and the other commands are in the `redocly-cli` skill (https://redocly.com/.well-known/agent-skills/redocly-cli/SKILL.md), and custom rules in `redocly-lint-rules` (https://redocly.com/.well-known/agent-skills/redocly-lint-rules/SKILL.md).
2. Follow Preview and verify in the `redocly` skill (https://redocly.com/.well-known/agent-skills/redocly/SKILL.md).
Run `TERM=dumb npx @redocly/cli preview` in the background, read its output, and open the reference page.
3. After a change to `redocly.yaml`, run `npx @redocly/cli check-config`.
## Common mistakes
| Mistake | Correct approach |
| --- | --- |
| An `apis` entry to publish or move an API | The file path sets the URL |
| `hideReplay` or other options directly under `apis.<name>` | Under `openapi`, or `apis.<name>.openapi` |
| `x-tagGroups` that leaves out a tag | List every tag, as tags in no group do not show |
| `hideDownloadButtons` to protect an API | It hides buttons only, so use `x-rbac` or `access.rbac` |
| A code sample language that is not generated | Add it with `x-codeSamples` |
redocly-authoring13.2 KB
---
name: redocly-authoring
description: >-
Use when writing or changing content in a Redocly project: adding, moving, or renaming pages, choosing
file names and URLs, links and images, page titles and front matter, Markdoc tags such as admonitions,
tabs, cards, code groups, and partials, the sidebar, navbar, and footer, or site settings in redocly.yaml.
Not for API reference docs from OpenAPI, AsyncAPI, or GraphQL files (use redocly-api-docs).
---
# Write content for a Redocly project
## The folder is the site
Realm builds the site from the project folder.
Every content file is a page, and the path of the file is the URL of the page:
| File | URL |
| --- | --- |
| `index.md` | `/` |
| `guides/quickstart.md` | `/guides/quickstart` |
| `guides/index.md` | `/guides` |
| `apis/cafe-api.yaml` | `/apis/cafe-api` (API reference, see `redocly-api-docs`) |
| `changelog.page.tsx` | `/changelog` (React page) |
A file named `index` is the page of its folder.
To change a URL, move or rename the file, and add the old URL to `redirects` in `redocly.yaml` when the page was published.
Some folders are not pages.
`_partials/` (by default) holds reusable content, and files in `static/` are copied to the site root as they are: `static/robots.txt` is served at `/robots.txt`.
Realm reserves `@theme/` (customizations), `@l10n/` (translations), `@api/`, and `@skills/`.
Any other folder that starts with `@` is a content version.
Two files that resolve to the same URL, such as `payments.md` and `payments/index.md`, get numbered URLs, so avoid them.
Full reference: https://redocly.com/docs/realm/content/project-structure.md
## Before you write
Read `redocly.yaml` and the nearest `sidebars.yaml` when they exist, as a project can have neither.
Read two or three pages next to the one you add and match their file naming, heading style, and image location.
Default to kebab-case file names when the folder has no pattern.
Search for existing content before you add a page: the right change may be a new section or a link.
## Pages
A page is Markdown with optional YAML front matter.
The first H1 is the page title.
Front matter holds the settings of one page, such as a search title that differs from the H1 or a description.
Omit it when the defaults do:
```markdown
---
seo:
title: Rate limits for the Acme API
description: Request limits, the 429 response, and how to retry.
---
# Rate limits
```
`slug: /custom-url` serves the page at a different URL.
Use it only when the author wants the file to stay where it is.
The other page settings, such as `rbac`, `markdown.toc`, `feedback`, or `navbar.hide`, are in the front matter reference: https://redocly.com/docs/realm/config/front-matter-config.md
## Links and images
Link to files, with their extension, relative to the page: `[Quickstart](./quickstart.md)`, `[Cafe API](../apis/cafe-api.yaml)`, `[Install](../setup/index.md#install)`.
A path that starts with `/` resolves from the project root.
Do not link to built URLs of the same site.
Realm resolves file links for locales and versions, and the preview reports a broken file link.
After moving a file, fix the links to it and the relative links inside it.
Store images where the project already stores them, otherwise in an `images/` folder next to the page.
Reference them with relative paths and alt text: ``.
Files that need a fixed URL, such as `robots.txt`, go in `static/`.
Full reference: https://redocly.com/docs/realm/content/links.md
## Markdoc tags
Pages are Markdown, and Markdoc tags in `{% ... %}` add components when a page needs them.
A tag with content has a closing tag, and a tag without content ends with `/%}`.
Put block tags on their own lines.
```markdoc
{% admonition type="warning" name="Rate limit" %}
Requests above 100 per minute get HTTP 429.
{% /admonition %}
```
Strings are quoted, numbers and booleans are not: `columns=2`, `expanded=true`.
Child tags work only inside their parent: `tab` in `tabs`, `card` in `cards`, `numbered-item` in `numbered-list`.
| Tag | Use | Reference |
| --- | --- | --- |
| `admonition` | Callout. `type` is `info`, `warning`, `success`, `danger`, or `idea`, and the title attribute is `name` | https://redocly.com/docs/realm/content/markdoc-tags/admonition.md |
| `tabs` / `tab` | Switchable panels, and each `tab` has `label` | https://redocly.com/docs/realm/content/markdoc-tags/tabs.md |
| `code-group` | Fenced code blocks as tabs, named by the `title` fence attribute | https://redocly.com/docs/realm/content/markdoc-tags/code-group.md |
| `cards` / `card` | Link tiles, and `card` has `title` and `to` | https://redocly.com/docs/realm/content/markdoc-tags/cards.md |
| `accordion` | Collapsible section with a `title`, wrapped in `accordion-group` for an FAQ | https://redocly.com/docs/realm/content/markdoc-tags/accordion.md |
| `table` | Table with rich content in cells, where a Markdown pipe table is enough for simple data | https://redocly.com/docs/realm/content/markdoc-tags/table.md |
| `img` | Image with size, caption, lightbox, or one file per color mode | https://redocly.com/docs/realm/content/markdoc-tags/img.md |
| `icon` | Inline Font Awesome icon: `{% icon name="solid check" /%}` | https://redocly.com/docs/realm/content/markdoc-tags/icon.md |
| `partial` | Insert a file from a partials folder: `{% partial file="/_partials/note.md" /%}` | https://redocly.com/docs/realm/content/markdoc-tags/partial.md |
| `code-snippet` | Code block from a file, or a range of its lines | https://redocly.com/docs/realm/content/markdoc-tags/code-snippet.md |
| `if` / `else` | Conditional content with `equals`, `includes`, `and`, `or`, `not` on variables such as `$frontmatter` or `$rbac.teams` | https://redocly.com/docs/realm/content/markdoc-tags/if-else.md |
| `numbered-list` / `numbered-item` | Procedure steps on a rail, each step a `numbered-item` | https://redocly.com/docs/realm/content/markdoc-tags/numbered-list.md |
| `prompt` | An AI prompt that readers copy or open in Claude, ChatGPT, or Cursor with one click | https://redocly.com/docs/realm/content/markdoc-tags/prompt.md |
| `connect-mcp` | A button that connects an AI client to the site's MCP server | https://redocly.com/docs/realm/content/markdoc-tags/connect-mcp.md |
| `login-button` | A login button shown only to users who are not logged in | https://redocly.com/docs/realm/content/markdoc-tags/login-button.md |
| `code-walkthrough` | Interactive tutorial: steps on the left, highlighted code on the right | https://redocly.com/docs/realm/content/markdoc-tags/code-walkthrough.md |
| `inline-svg` | An SVG file rendered inline, so CSS can style it | https://redocly.com/docs/realm/content/markdoc-tags/inline-svg.md |
| `markdoc-example` | Markdoc source next to its rendered result | https://redocly.com/docs/realm/content/markdoc-tags/markdoc-example.md |
All tags, with every attribute: https://redocly.com/docs/realm/content/markdoc-tags/tag-library.md
Tags that show an operation or schema from an API description are in the `redocly-api-docs` skill (https://redocly.com/.well-known/agent-skills/redocly-api-docs/SKILL.md).
A project can define its own tags in `@theme/markdoc/schema.ts`, so read that file before using one.
A fenced code block takes Markdoc attributes after the language, such as `{% title="redocly.yaml" %}`.
The `treeview` language renders a folder tree, and `mermaid` renders a diagram.
Markdoc processes `{% ... %}` sequences inside a code block, and nothing else.
Add `{% process=false %}` to the fence to show them as text.
Full reference: https://redocly.com/docs/realm/content/markdown.md
## Navigation
Without a `sidebars.yaml`, Realm generates the sidebar from the folder structure.
With one, a page shows in the sidebar only when a sidebar file lists it, or lists its folder with `directory`.
```yaml
- page: index.md
label: Overview
- group: Guides
page: guides/index.md
items:
- page: guides/quickstart.md
- page: guides/rate-limits.md
- directory: guides/advanced
- page: apis/cafe-api.yaml
label: Cafe API
- href: https://status.example.com
label: Status
external: true
```
Paths in a sidebar file are relative to that file.
The label defaults to the page title.
Large sites split into navigation tabs, one per section with its own sidebar.
Tabs are top-level `tab` entries of `sidebars.yaml`: https://redocly.com/docs/realm/navigation/navigation-tabs.md
Another option is one sidebar file per section, joined with `$ref` entries or linked from the navbar.
Leave a page out of every sidebar file to show it without a sidebar, such as a landing page.
Sidebars: https://redocly.com/docs/realm/navigation/sidebars.md
The navbar and footer are the `navbar` and `footer` objects in `redocly.yaml`, each with an `items` list.
The items take the same `page`, `href`, `label`, `external`, and `group` keys as a sidebar, with paths relative to the project root:
```yaml
navbar:
items:
- page: guides/index.md
label: Guides
```
Navbar: https://redocly.com/docs/realm/config/navbar.md and footer: https://redocly.com/docs/realm/config/footer.md
## Site settings
`redocly.yaml` in the project root configures the whole site.
Paths in it are relative to the project root.
```yaml
seo:
projectTitle: Acme docs
siteUrl: https://docs.acme.example
redirects:
'/old-guide/':
to: '/guides/quickstart/'
```
A redirect source and its `to` start with `/` and leave out `index`, and `*` is valid only as the last segment.
Redirects: https://redocly.com/docs/realm/config/redirects.md
Common keys: `seo`, `redirects`, `logo`, `navbar`, `footer`, `search`, `banner`, `feedback`, `breadcrumbs`, `ignore`, `access` (login and role-based access), `analytics`, `l10n`, `products`, and `env`.
Validate the file with `npx @redocly/cli check-config`, which names unknown keys.
All options: https://redocly.com/docs/realm/config.md
## Beyond content
These exist, so load their page before changing them, and prefer the smallest change:
- Styling: CSS variables in `@theme/styles.css`, the logo and favicon in `logo`, fonts in `links`. https://redocly.com/docs/realm/branding/customize-styles.md
- Versioned content: one `@<version>` folder per version. https://redocly.com/docs/realm/content/versions.md
- Translations: `@l10n/<locale>/` folders. https://redocly.com/docs/realm/content/localization/localize-content.md
- Several products with a product picker: `products` in `redocly.yaml`. https://redocly.com/docs/realm/config/products.md
- Page templates: a React layout applied to a set of Markdown pages. https://redocly.com/docs/realm/customization.md
- Login and role-based access: `access` in `redocly.yaml`. https://redocly.com/docs/realm/config/access.md
A React page is a `*.page.tsx` file.
Its path is its URL, like a Markdown page, and it sets its front matter in code:
```tsx
import React from 'react';
export const frontmatter = { seo: { title: 'About Acme' } };
export default function AboutPage() {
return <h1>About Acme</h1>;
}
```
Use one for a layout that Markdown cannot express, such as a landing page, and import the theme components it needs from `@redocly/theme`: https://redocly.com/docs/realm/customization/create-react-page.md
A project can define its own Markdoc tags: a tag name with typed attributes, declared in `@theme/markdoc/schema.ts`, rendered by a React component from `@theme/markdoc/components.tsx`.
Authors then write it like a built-in tag, for example `{% product-card name="Orders" /%}`.
Build one when a block repeats across pages with varying data and a partial is not enough: https://redocly.com/docs/realm/customization/build-markdoc-tags.md
Every part of the site is a React component, and any of them can be ejected.
`npx @redocly/cli eject component 'Footer/**'` copies the source into `@theme/components/`, and the copy replaces the original.
Find a component's name in the `data-component-name` attribute in the browser developer tools.
Eject only when the structure or behavior has to change, because colors, fonts, and spacing are CSS variables.
Reference: https://redocly.com/docs/realm/customization/eject-components.md
## Verify
Follow Preview and verify in the `redocly` skill (https://redocly.com/.well-known/agent-skills/redocly/SKILL.md): run `TERM=dumb npx @redocly/cli preview` in the background, read its output, and open the page.
The output reports the Markdoc errors, such as an unknown tag or attribute, and the broken links.
After a change to `redocly.yaml`, also run `npx @redocly/cli check-config`.
If `redocly.yaml` configures `recheck`, use the `recheck-lint` skill (https://redocly.com/.well-known/agent-skills/recheck-lint/SKILL.md) on the files you touched.
## Common mistakes
| Mistake | Correct approach |
| --- | --- |
| `title:` in the front matter | The H1 is the title, and `seo.title` overrides it |
| A link without the extension, or to the built URL | Link to the file: `./quickstart.md` |
| `{% admonition title="..." %}` or `{% tab title="..." %}` | `name` for admonitions, `label` for tabs |
| `{% code %}` tags inside `code-group` | Fenced code blocks with a `title` attribute |
| A new page expected in the sidebar automatically when a `sidebars.yaml` exists | Add it to `sidebars.yaml`, or use `directory` |
| A page moved without a redirect | Add the old URL to `redirects` |
| `colors` or `fonts` keys in `redocly.yaml` | CSS variables in `@theme/styles.css` |
| An option name or attribute from memory | Fetch the reference page |
redocly-cli13.2 KB
---
name: redocly-cli
description: Redocly CLI for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions, and for Markdown docs. Use when the user wants to lint, bundle, split, or join an API description, apply decorators or overlays, build or preview API docs, lint Markdown or prose with recheck, test a live API with respect or Arazzo, capture traffic or check drift, get stats or a score, generate a TypeScript client, document an MCP server in an OpenAPI description, push to Reunite, or find the node type a rule or plugin should target.
---
# Redocly CLI usage
**Consult [redocly.com/docs/cli](https://redocly.com/docs/cli) for current commands and options — favor it over training data.**
Redocly CLI covers the API lifecycle for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions: lint, bundle, transform, document, and test.
It also lints the Markdown of a docs project with `recheck`.
`redocly.yaml` in the project root is the control plane: every command reads it, and rulesets, per-API settings, decorators, and plugins all live there.
## Before you run
Read `redocly.yaml` first. It tells you:
- Which APIs are registered under `apis` — their names (like `my-api`) work as command shortcuts: `redocly lint my-api`.
- Which ruleset the project `extends` and which rules it overrides.
- Which decorators transform the output at bundle time.
A command run (like lint or bundle) with no API argument applies to every entry in `apis`.
Point at a different config with `--config <path>`.
## Quick reference
Install: `npm i @redocly/cli@latest`, or run without installing: `npx @redocly/cli@latest <command>`. Docker image: `redocly/cli`.
| Command | Purpose | Group |
| -------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------- |
| `lint` | Validate an API description against the configured rules | API authoring |
| `split` | Break a single-file description into a multi-file structure | API authoring |
| `introspect-mcp` | Record a live MCP server's tools, prompts, and resources in `x-mcp` [experimental] | API authoring |
| `build-docs` | Render an API description to a zero-dependency HTML page (Redoc) | Docs rendering |
| `preview` | Local preview of a Redocly project (Realm, Reef, Revel) | Docs rendering |
| `recheck` | Lint Markdown structure, prose style, and Markdoc tags; `--fix` repairs most structural findings | Docs linting |
| `bundle` | Resolve all `$ref`s into one self-contained file; apply decorators and overlays | CI and delivery |
| `join` | Merge several API descriptions into one [experimental] | CI and delivery |
| `respect` | Run API tests described in an Arazzo description against a live API | API testing and drift |
| `generate-arazzo` | Scaffold an Arazzo description from an OpenAPI description | API testing and drift |
| `proxy` | Capture live HTTP traffic into a HAR file, with optional validation [experimental] | API testing and drift |
| `drift` | Report differences between recorded traffic and an OpenAPI description, with optional `--coverage` [experimental] | API testing and drift |
| `generate-spec` | Infer an OpenAPI 3.2 description from recorded traffic; `--openapi-version 3.1` targets 3.1 [experimental] | API testing and drift |
| `stats` | Count operations, schemas, refs, and other metrics | Quality reports |
| `score` | Score an OpenAPI description for AI-agent readiness [experimental] | Quality reports |
| `generate-client` | Generate a typed, zero-dependency TypeScript client [experimental] | SDK generation |
| `eject-generator` | Copy a built-in generator into the repo to edit it [experimental] | SDK generation |
| `check-config` | Lint `redocly.yaml` itself | Redocly configuration |
| `inspect-node-types` | Show the node type at a pointer, or list every node with its type [experimental] | Redocly configuration |
| `push` | Push an API description to Reunite | Reunite platform |
| `push-status` | Track an in-progress push to Reunite | Reunite platform |
| `completion` | Generate shell autocomplete commands | Shell setup |
Exit codes: `0` success, `1` problems found or execution failed, `2` configuration error.
## Configure with redocly.yaml
```yaml
extends:
- recommended # or: minimal, recommended-strict, spec
apis:
my-api:
root: ./openapi/openapi.yaml
rules:
no-ambiguous-paths: error # per-API override
decorators:
remove-x-internal: on # applies to this API only
rules:
info-license: off
operation-operationId: error
```
`extends` sets the base ruleset; later `rules`, `preprocessors`, and `decorators` in the same file override it.
Rule severities: `error` (fails validation), `warn` (reported, still valid), `off`.
### Configurable rules
When a governance requirement has no built-in rule, declare one under `rule/<name>` in `redocly.yaml`.
Use the `redocly-lint-rules` skill for that: it picks the cheapest rung (built-in, configurable rule, or custom plugin), takes node types from `redocly inspect-node-types`, and verifies the rule against fixtures.
## Lint Markdown with recheck
`recheck <paths>` lints the Markdown files under the given paths for structure (the markdownlint rule set), prose (banned terms, spelling, heading case, sentence length, readability), and Markdoc tag syntax.
Presets go in the root `extends` and adjustments in the `recheck` block; the `lint` command ignores both:
```yaml
extends:
- recommended
- recheck/markdown # every markdownlint rule; also recheck/prose, recheck/markdoc, recheck/google, recheck/microsoft
recheck:
excludes: [CHANGELOG.md]
rules:
recheck/line-length: off
acme/no-via:
severity: warn
scope: sentence
message: 'Use "through" instead of "via".'
assertions:
pattern: { ignoreCase: true, tokens: ['\bvia\b'] }
```
With no `redocly.yaml`, the command uses `recheck/markdown`.
With a `redocly.yaml` that has neither a preset nor a block, it checks nothing.
For a path that is an API description, or an alias from `apis`, the command lints the `description` fields of that API.
An existing file or folder wins over an alias with the same name.
Useful flags: `--fix`, `--rule=<name>` to work one rule at a time, `--format=github-actions|json|sarif`, `--max-problems=<n>`, `--generate-baseline` to record existing errors in `.redocly.recheck-baseline.yaml` and fail only on new ones, and `--readability` for per-file scores.
Silence one line with `<!-- recheck-disable-next-line <rule> -->` rather than turning the rule off.
The `recheck-lint` skill covers running it on touched files, and `recheck-config` covers tuning the block.
## Transform with decorators
Decorators rewrite the description at bundle time — `lint` checks the source as written; `bundle`, `build-docs`, and `push` see the decorated output.
Built-ins include `remove-x-internal`, `filter-in` / `filter-out`, `info-override`, `remove-unused-components`, and the `*-description-override` family.
## Apply overlays
`bundle` applies Overlay 1.0, 1.1, and 1.2 files to the bundled description, before decorators: pass `--overlay <file>` (repeatable, applied in order), or list them under `overlays` in an `apis` entry, with paths relative to `redocly.yaml`.
`--overlay` replaces the configured list.
Action targets run against the bundled output, so check them against `redocly bundle <api>` without overlays; a target that matches nothing changes nothing and isn't reported.
File `$ref`s in overlay values are relative to the overlay file and get bundled; decorators such as `remove-x-internal` see the overlay's changes.
Two `apis` entries with the same `root` and different `overlays` and `output` publish two versions of one API.
## Extend with custom plugins
When configurable rules and built-in decorators can't express the requirement, escalate to a [custom plugin](https://redocly.com/docs/cli/custom-plugins): a JavaScript module exporting rules, decorators, preprocessors, or config, keyed by document format:
```js
export default function myPlugin() {
return {
id: 'my-plugin',
rules: {
oas3: {
'operation-id-not-test': () => ({
Operation(operation, { report, location }) {
if (operation.operationId === 'test') {
report({ message: 'operationId must not be "test".', location });
}
},
}),
},
},
};
}
```
Visitor keys are node types: confirm them with `redocly inspect-node-types <api>`.
Register it in `redocly.yaml` (paths relative to the config file) and reference its rules as `<plugin-id>/<rule-name>`:
```yaml
plugins:
- ./plugins/my-plugin.js
rules:
my-plugin/operation-id-not-test: error
```
## Generate a TypeScript client
`generate-client <api> --output client.ts` turns an OpenAPI description into a typed client — one self-contained file with zero runtime dependencies (auth, retries, middleware, typed SSE, pagination included).
Configure it durably under a `client` block in `redocly.yaml` instead of flags:
```yaml
client:
generators: [typescript, zod] # add-ons: tanstack-query, swr, mock, transformers, or a plugin path
outputMode: split
pagination: # config-only, no CLI flag
style: cursor
cursorParam: after
nextCursor: /page/endCursor
items: /items
apis:
my-api:
root: ./openapi/openapi.yaml
clientOutput: ./src/api/client.ts
```
With `apis.<name>.clientOutput` set, a bare `redocly generate-client` generates every opted-in API.
`client.telemetry: on` (config-only) makes the `typescript` generator also write `<output>.telemetry.ts`: register `use(telemetry({ sdkName, sdkVersion, client }))` to count calls per operation and send the counts once a minute (no request data; `REDOCLY_SDK_TELEMETRY=off` or `DO_NOT_TRACK=1` turns sending off).
An API's own `client` block replaces the top-level one wholesale — repeat the shared fields in it.
With `client.codeSamples: true`, it also writes `<output>.code-samples.yaml`, an overlay that adds `x-codeSamples`; add it to the API's `overlays` (or pass `--overlay`) so `bundle` puts the samples in the description.
## Test a live API
`respect` executes an [Arazzo](https://spec.openapis.org/arazzo/latest.html) description as a test suite against a running API, asserting real responses match the description.
Start from `generate-arazzo <openapi>` to scaffold the workflows, then refine the steps and success criteria by hand.
## Workflow
1. Read `redocly.yaml` to learn the registered APIs, ruleset, and decorators.
2. Make the change — spec edit, rule config, or decorator.
3. Verify:
- `redocly lint` exits `0` (or reports only warnings you expected).
- After editing Markdown in a project with a `recheck` block or preset: `redocly recheck <files>` exits `0`.
- After editing `redocly.yaml`: `redocly check-config` reports no problems.
- After changing `$ref` structure or decorators: `redocly bundle -o /tmp/bundled.yaml` succeeds and the output contains what you intended.
## Gotchas
- v2 is ESM-only: Node.js v22.12.0+ (or v20.19.0+).
- `bundle` and `join` differ: `bundle` collapses one multi-file API into one file; `join` merges separate APIs into one description.
- `respect` currently covers only synchronous HTTP flow.
- Any `redocly.yaml` in the working directory configures every command — a stray one changes lint results silently.
- `--extends` on the command line sets the base ruleset for that run; useful for a quick `--extends=spec` conformance check.
- `generate-client` needs the `typescript` package (6.x) available at generation time; the generated client itself compiles with any TypeScript, including 7.
## Resources
- [Command reference](https://redocly.com/docs/cli/commands)
- [Built-in rules](https://redocly.com/docs/cli/rules)
- [Decorators](https://redocly.com/docs/cli/decorators)
- [Configuration](https://redocly.com/docs/cli/configuration)
- [Markdown and prose linting](https://redocly.com/docs/cli/recheck)
redocly-lint-rules26.2 KB
---
name: redocly-lint-rules
description: Implement a Redocly CLI lint rule in a user's redocly.yaml or custom plugin for an OpenAPI, AsyncAPI, or Arazzo description. Use when the user wants a convention enforced, asks for a configurable rule or a plugin rule, debugs a rule that never reports or gives false positives, or migrates rules from another linter such as Spectral.
---
# Implementing a Redocly CLI lint rule
Turn a check written in plain language ("every array schema must declare `items`") into a Redocly lint rule that works and is tested.
**The ladder.** Redocly gives three ways to do a check. The first is the cheapest to maintain.
1. **Built-in rule** — one of the [built-in rules](https://redocly.com/docs/cli/rules/built-in-rules) does the check.
2. **Configurable rule** — one or more `rule/*` entries in `redocly.yaml` do the check with assertions.
3. **Custom plugin** — JavaScript code that implements your own rule with the visitor pattern. Use this rung last.
Do the checks one at a time.
Start each check at rung 1.
Go down one rung only when the current rung cannot do the check.
Take all syntax from the [official docs](https://redocly.com/docs/cli/) or the [cookbook](https://github.com/Redocly/redocly-cli/tree/main/cookbook).
Trust the docs, not your memory.
Use modern syntax, such as ESM instead of CommonJS. If the project has its own style, follow the project.
## 1. Triage
For each check, write down three things: the node it looks at, what must be true, and what a violation looks like.
Triage each check on its own. Different checks can land on different rungs.
Get node names from the `inspect-node-types` command, not from memory.
The type tree is different in each specification and each version.
The names it prints are the `subject.type` of a configurable rule, and the visitor keys of a plugin.
| to learn | run |
| ------------------------------------- | ------------------------------- |
| the type at one location | `--pointer='#/paths/~1orders'` |
| every node, or every node of one type | no flag, or `--type=Schema` |
| which types the description uses | `--summary` |
| **where a type can sit** | `--type=<Type> --parents` |
| the ancestors of one node | `--pointer=<pointer> --parents` |
The last two rows give a rule its scope.
When a type sits below more than one parent, the rule needs a `where` gate.
Without that gate, the rule also reports the branches you do not want.
The command shows the description in front of you. It does not show everything the specification permits.
When the command does not print a branch, that branch is unproven. It is not absent.
The command does not shorten step 3. It gives you type names. It does not tell you whether your assertions express the check.
Walk the ladder for each check:
- Search the [built-in rules list](https://redocly.com/docs/cli/rules/built-in-rules) for the concept.
A trailing slash in a path gives `no-path-trailing-slash`.
When the config already extends a ruleset that turns the built-in on, tell the user. Do not add the rule again.
**Use the built-in when its description matches the check, even when it reports more than the rule you replace.**
A built-in is usually a better version of the same idea, so its extra findings are the check done correctly.
When you write the narrower behavior by hand, you exchange a maintained rule for code that you must maintain.
Go down one rung only when the built-in checks something different.
Report which built-in you took, and what it adds.
- Make sure the built-in supports the spec versions that the project lints.
Some built-ins support one version only.
`no-invalid-media-type-examples` supports OpenAPI 3.x, so it leaves a Swagger 2.0 check unenforced.
Redocly prints `Unused rules found in redocly.yaml` when an enabled rule cannot apply to a document.
Correct that line. It is not noise.
- Try assertions and a `where` gate. See "Configurable rules" below.
One check can need several `rule/*` entries, one for each case or location. That is still better than a plugin.
- Write a plugin only when configurable rules cannot express the check, also in combination.
First work through the three dials below.
A plugin can do any check, but it is code. It adds complexity, maintenance, and its own bugs.
- Tell the user to [file an issue](https://github.com/Redocly/redocly-cli/issues) only when no configurable rule can close a real gap.
Triage is complete when each check has a rung.
Give one solution for each check: the highest rung that works.
Report only what the user must act on: a mismatch between a description and an implementation, an unproven rule, or a decision that could go another way.
Report in the chat. Never write a summary file.
The config records the rest, so do not narrate the rung of each check.
### Migrate from another linter
- Migrate the **effective** ruleset, not only the rules written in the file.
An `extends`, or an inherited base, turns on more rules at their default severities. Each of those rules is also a check.
Resolve that base and count the rules before you start. Then you know the true size of the job.
- Take each check from two places: the description of the rule, and its implementation.
Never take it from your memory of the rule name.
When the two disagree, use the description. The description says what the author wanted, and the implementation can hold a bug.
Tell the user about the difference.
- Skip a rule with severity `off`. Do not migrate it, not even as a disabled entry.
When the enabled rules are done, list each skipped rule on one line. Then the user can decide.
- For Spectral, read the map table in the [migration guide](https://redocly.com/docs/cli/guides/migrate-from-spectral) first.
Many Spectral core rules map to one built-in. An empty cell means that you go down the ladder.
- When you copy a function from the source, you also copy its bugs into a stricter host.
Redocly stops the whole document when a rule throws, but the source linter can stop one rule only.
An unguarded match, such as `v.match(/…/)[0]` on a value that can fail to match, removes every finding in that file.
Add a guard to the code you copy. Then lint a document that holds the difficult shape.
## 2. Configure
Write to the user's `redocly.yaml`. Use that exact name, with no leading dot.
When the file exists, read it first, and keep the existing `extends` and rules.
Set each rule to `error`, unless the user asks for another level.
When you migrate, keep the source severity and choose the nearest Redocly level.
Only `error`, which fails the run, `warn`, and `off` exist, so `info` and `hint` become `warn`.
Reference every file you create from the config, and exercise it in the lint run. Leave no dead files.
**A config with no `extends` inherits nothing** — not `recommended`, and not even `struct`.
The default base applies only when no config file exists.
So a `redocly.yaml` runs exactly the rules that it names.
The built-in rung exists only when `extends` names a ruleset, or when the config lists each built-in itself.
With neither, a document that breaks the specification three times lints clean.
**Wire every rule.**
A plugin in `plugins:` does nothing when its rules are absent from `rules:`, and Redocly gives no warning.
After you write the config, list the rule ids that must be active.
Confirm that each id is in `rules:` or in a spec-scoped block.
When all rules are in, compare your list with the predefined [rulesets](https://redocly.com/docs/cli/rules): `minimal`, `recommended`, `recommended-strict`, and `spec`.
- When your rules are close to one ruleset, extend it.
Keep only the differences as overrides: other severities, rules to turn off, and checks that the ruleset does not hold.
- A ruleset also turns on rules that the user did not ask for.
Read what it adds, and override each rule that contradicts or repeats a rule you wrote.
A ruleset that turns on a check which the source sets to `off` is an error to correct, not a benefit.
- When the overrides are as many as the rules, or more, keep the plain list and no `extends`.
## 3. Verify
A rule is **unproven** until a live run makes it report.
Prove each rule with `npx redocly lint <api-file>` in two directions:
- a **violating** fixture: the rule reports, at the correct location;
- a **conforming** fixture: the run stays silent.
A false positive is the common failure, so a rule that passed only the violating fixture is unproven.
Write the violating fixture to separate this rule from the rule next to it: the rule you replace, or its neighbour in the config.
Two errors need that separation:
- **Put a falsy value in the violating fixture.**
A rule that must reject an empty `description` must report on `description: ''`.
When you put `''` in the conforming fixture only, you prove nothing, because a presence assertion accepts it.
The rule then looks correct and the gap stays.
- **A built-in that replaces two source checks needs a fixture that breaks only the second check.**
`no-unsafe-markdown` reports `<script>`, but never `eval(`.
A fixture that holds both gives the built-in credit for a check it does not do.
To keep the output clean, pass a small temporary config with `--config`.
Delete that config and the fixtures when you finish.
Every snippet you deliver must be correct as written: it parses as YAML, and it behaves as you claim.
**You are done when each check names a violating fixture that reported, and a conforming fixture that stayed silent.**
Name those fixtures in your report.
A count is not evidence.
A clean run on the project's own files is not evidence either: it shows only that nothing reports there, and that is true for a correct rule and for a dead rule.
When you verify the risky rules and assume the others, the delivery is unproven. Say so, and name each unproven rule.
A conforming fixture proves only the shapes inside it.
A spec version you did not try, or a location that `--parents` showed and you did not exercise, stays untested. Report that.
When a live run is impossible, mark the rule unverified. Do not claim that it works.
You are also done when each requested check is in the config or on the skipped list, and the project holds only the config and plugin files that you created.
## Configurable rules
Docs: [configurable rules](https://redocly.com/docs/cli/rules/configurable-rules).
Worked examples: [cookbook/configurable-rules](https://github.com/Redocly/redocly-cli/tree/main/cookbook/configurable-rules).
A rule is an entry under `rules:` with a name that starts with `rule/`:
```yaml
rules:
rule/my-check:
subject:
type: Schema # a node type from the docs' type list
property: title # optional
assertions:
defined: true
where: [] # optional gate
message: Custom problem text.
severity: error
```
Plain `rules` applies to every document that the project lints. Keep rules there by default.
Move a rule under a spec-scoped key only when the check needs something that exists in one spec version, such as `nullable` in OAS 3.0, or when the user asks for the scoping.
The keys are `oas2Rules`, `oas3_0Rules`, `oas3_1Rules`, `oas3_2Rules`, `async2Rules`, `async3Rules`, `arazzo1Rules`, and `overlay1Rules`.
A rule inside one key does not run for the other versions, so a check that the project needs on two versions needs an entry that both versions reach.
Scope a plugin rule inside the plugin instead, in its `rules` map.
### What an assertion tests
Two facts cause most false positives and most silent misses.
**An absent value passes.**
Nearly every assertion holds when the value it checks is absent.
`const: array` on `property: type` matches a schema typed `array`, and also every schema with no `type`.
So when a `where` entry tests a property value, add `defined: true` beside the value assertion if the property can be absent.
**Presence is not value.**
`defined`, `required`, `requireAny`, `disallowed`, and `mutuallyExclusive` test whether a key exists.
They say nothing about the content, so `defined: true` on `description` passes for `description: ''`.
Assert the value instead: `nonEmpty: true` rejects an empty string, and `const`, `enum`, and `pattern` test the content.
This is the most frequent defect in practice.
It looks correct in review, and only a violating fixture that holds `''` shows it.
Two results of these facts:
- `const: <value>` is also the way to say "this field must be falsy", because the assertion holds for that value and for an absent field.
- `requireAny` counts keys only.
"At least one of these fields, and not empty" has no assertion form. That check is a real plugin case.
### What the assertion inspects
- `property: name` — the value of that field on the subject node.
- `property: [name, url, email]` — each listed field on its own.
Every field that fails becomes its own problem with its own JSON pointer, such as `#/info/contact/name`.
- `property` absent — the **keys** of the subject node.
For example, `subject: { type: Paths }` with `assertions: { notPattern: /.+\/$/ }` runs on the path strings themselves.
### Aim the subject, then gate it
Three dials aim a rule. Resolve them in this order.
**1. Subject: the node that owns what you assert.**
- A check about the **keys that a map holds** targets the map type itself.
The maps of responses, headers, schema properties, and paths are each their own type.
So "this map must hold that key" is one `required` on one subject. Do not search the parent.
- A check about a **value** targets the node that holds the field, plus `property`.
- `type: any` with a `property` checks that property everywhere it appears.
- Confirm the type name with `inspect-node-types`, on a document of the spec version that the rule targets.
A named type can exist in one version and not in another.
`HeadersMap` is a type in OpenAPI 3. In OpenAPI 2 the response `headers` map is anonymous and has no type name, so a `HeadersMap` subject under `oas2Rules` never reports.
Redocly warns about an unresolved rule id, but **not** about an unresolved `subject.type`, so a wrong type gives you a rule that never reports and never complains.
**2. Parent-key filters: `filterInParentKeys`, `filterOutParentKeys`, `matchParentKeys`.**
These filters match the **key of the subject node inside its parent**.
So they work only where that key varies: a method inside a path item, a status code inside a responses map, a path string inside a paths map.
`matchParentKeys` takes a regular expression, so it reaches a class of keys, and a negative lookbehind inside it excludes a shape.
The common error is a filter one level too high.
The key of a `Responses` node is always `responses`, so `filterInParentKeys: [put]` on a `Responses` subject matches nothing and the rule never reports.
The method varies one level above. Reach it with `where`:
```yaml
rule/put-responses-must-include-200:
subject:
type: Responses # the key here is always `responses`, so no filter belongs on it
assertions:
required: ['200']
where:
- subject:
type: Operation
filterInParentKeys: [put] # the key that varies
assertions:
defined: true
```
**3. `where`: the ancestor gate.**
Build the gate from `--type=<Type> --parents`.
Each chain in that output is a place where the subject sits, and the gate keeps the chain you want.
Each entry names a type that must sit above the subject, and the rule skips every node that is not inside a matching ancestor.
The assertions of a gate entry are filters, never checks: when one fails, the node drops out in silence instead of becoming a problem.
The mechanics are exact:
- Entries run in document order, outermost first. Each type can appear in one entry only.
- The chain **can skip levels**. Name the ancestors that matter, not every node between them.
- The subject's own type **can also appear in the last entry**.
That is how the gate carries a parent-key filter or an exclusion while the subject stays whole.
- An entry of the subject's own type narrows the subject by one of its properties, such as "this schema, when its `type` is `array`".
- One entry can carry both a parent-key filter and assertions, so it says "inside an ancestor with this key **and** this value".
- References resolve at the gate, so entries see resolved nodes.
- For a Schema subject with a `where`, evaluation stops at the first schema level that matches the gate.
- A gate cannot exclude a nested `callbacks` branch.
An operation inside a callback still reaches a rule gated on `Paths`.
Say so. Do not claim that the gate excludes it.
When one subject becomes hard to aim, write one rule for each case.
Several small `rule/*` entries stay readable, and they are still better than a plugin.
### Recipes
A check that looks like plugin work usually needs one of these. Try them before you choose a plugin.
**Gate a check about paths with the paths map.**
The type that holds a path also holds webhook names and callback keys, so a rule about the shape of a URL also reports those unless the gate names the paths map:
```yaml
rule/put-path-ends-with-parameter:
subject:
type: Operation
filterInParentKeys: [put]
assertions:
defined: false
where:
- subject:
type: Paths # without this entry the rule also reports webhooks and callbacks
assertions:
defined: true
- subject:
type: PathItem
matchParentKeys: /[^}]$/
assertions:
defined: true
```
Run `--type=<Type> --parents` on the type you want to target.
Every chain it prints is a place where the rule reports, and each chain you do not want needs a gate entry.
**Require a value, not only a key.**
`additionalProperties` must not sit beside `properties`, but `additionalProperties: false` stays permitted:
```yaml
rule/no-additional-properties-with-properties:
subject:
type: Schema
property: additionalProperties
assertions:
const: false
where:
- subject:
type: Schema
property: properties
assertions:
defined: true
```
**Guard a value gate against an absent value.**
The `defined: true` keeps this rule off a schema that declares no `type`:
```yaml
rule/array-items:
subject:
type: Schema
assertions:
required: [items]
where:
- subject:
type: Schema
property: type
assertions:
const: array
defined: true
message: The 'items' field is required for schemas of array type.
```
A value assertion cannot match a list, so this gate misses the OpenAPI 3.1 union form `type: [array, 'null']`.
Cover that version with a second entry, or report that the rule is narrower than the source.
**Locate by ancestor, and skip the levels between.**
"The media types of the 200 and 400 responses of one path" needs two gate entries, and no entry for the operation or the responses map:
```yaml
rule/health-media-type:
subject:
type: MediaTypesMap # keys, so `const` checks the media type itself
assertions:
const: application/health+json
where:
- subject:
type: PathItem
matchParentKeys: '/health'
assertions:
defined: true # a pure gate: this ancestor must exist
- subject:
type: Response
filterInParentKeys: ['200', '400']
assertions:
defined: true
```
**Filter the subject from the gate, and exclude with it.**
```yaml
rule/post-should-define-requestBody:
subject:
type: Operation
assertions:
required: [requestBody]
where:
- subject:
type: PathItem
matchParentKeys: /^([\w-\{\}/.](?<!/actions))*$/ # the lookbehind excludes a URI shape
assertions:
defined: true
- subject:
type: Operation # the subject's own type, in the last entry
filterInParentKeys: [post]
assertions:
disallowed: [deprecated] # a filter, so a deprecated operation drops out
```
**Gate on the value of an ancestor**, to say "only inside a node that looks like this":
```yaml
where:
- subject:
type: Operation
property: deprecated
assertions:
defined: true
const: true
```
**Combine assertions in one rule.** All of them must hold:
```yaml
assertions:
requireAny: [minLength, maxLength, enum]
mutuallyRequired: [minLength, maxLength]
```
**Match without case.** `pattern` and `notPattern` accept inline regular expression flags, so a header-name check needs no plugin:
```yaml
assertions:
notPattern: '/^(authorization|content-type|accept)$/i'
```
**Assert on the reference, not on the resolved value.**
`ref: true` requires a `$ref`, `ref: false` requires an inline value, and a string requires the unresolved `$ref` to match:
```yaml
subject:
type: MediaType
property: schema
assertions:
ref: /^#\/components\/.*/
```
**Bound a list.** `minLength` and `maxLength` apply to lists and to strings, so a list type as the subject limits how many members it holds. Use `maxLength: 1` for a single server.
**Forbid a node type.** A subject with `defined: false` and no `property` rejects a construct anywhere in the description.
**Record an exception.** When the rule is correct and a few nodes are real exceptions, keep the pattern strict and record those nodes in `.redocly.lint-ignore.yaml`.
**YAML gotcha.** Quote a `message` that holds a colon followed by a space, or the config fails to parse:
```yaml
message: 'Schemas with type: array must define items.'
```
## Custom plugin rules
Docs: [custom rules](https://redocly.com/docs/cli/custom-plugins/custom-rules).
Worked examples: [cookbook/custom-plugin-rules](https://github.com/Redocly/redocly-cli/tree/main/cookbook/custom-plugin-rules).
The full visitor guide is at https://raw.githubusercontent.com/Redocly/redocly-cli/refs/heads/main/.claude/rules/walker-visitors-nodes.md.
A plugin is a module. Its default export is a **function** that returns the plugin object:
```js
export default function myPlugin() {
return {
id: 'my-plugin',
rules: {
oas3: {
'schema-title-defined': SchemaTitleDefined,
},
},
};
}
```
Rules are keyed by spec flavor: `oas2`, `oas3`, `async2`, `async3`, and `arazzo`.
Register the same rule under several flavors when the check applies to all of them.
### Register and use
```yaml
# redocly.yaml
plugins:
- ./my-plugin.js
rules:
my-plugin/schema-title-defined: error
```
The config name of the rule is `<plugin id>/<rule name>`.
**Both halves are required.**
A plugin whose rules are absent from `rules:` is delivered and dead.
### The visitor pattern
A rule is a factory that returns a visitor object keyed by node type.
The walker traverses the document, resolves each `$ref`, and calls your hooks.
Never parse the document yourself, and never walk into it by hand.
Confirm each visitor key with the `inspect-node-types` command.
A key that is wrong or invented is never called, and nothing reports the mistake.
There is no `Reference` node, because a `$ref` and its target share the type of the target.
Watch the singular and the plural: the map of component schemas is `NamedSchemas`, and `NamedSchema` matches nothing.
Keep the rule small. Express everything through the visitors that the walker gives you.
A plugin rule needs no traversal helper, no second walker, and no resolver of its own.
The walker already reaches every node of a type, and it resolves each `$ref` before `leave`.
Use another visitor key before you write a helper.
```js
function SchemaTitleDefined() {
return {
Schema: {
enter(schema, ctx) {
if (schema.type === 'object' && !schema.title) {
ctx.report({
message: 'Object schemas must define a title.',
location: ctx.location.key(),
});
}
},
},
};
}
```
- The hooks are `enter(node, ctx)`, `leave(node, ctx)`, and `skip(node, ctx)`.
`leave` runs after the children, when every `$ref` is resolved.
Write the hooks as an explicit object with `enter` and `leave` keys, as in the example.
- Keep state that crosses nodes in the factory scope.
Collect in the `enter` hooks, and report in `Root.leave`.
### The context object
Use only these fields. The context is not the document, and it holds no path and no root.
| use | for |
| ----------------------------------- | -------------------------------------------------------------- |
| `ctx.report({ message, location })` | report the problem |
| `ctx.location.child([...])` | point at one field |
| `ctx.location.key()` | point at the key of the node — a report location, not a string |
| `ctx.key` | the key of the node itself, as a value |
| `ctx.parent` | the parent node |
| `ctx.resolve(ref)` | resolve a `$ref` to its target |
| `ctx.rawNode` | the unresolved node, when you need the `$ref` itself |
| `ctx.specVersion` | the spec version of the document |
There is no `ctx.document`, no `ctx.root`, and no `ctx.path`.
A rule that reads one of them throws.
**A rule that throws stops the whole document.**
Every finding from every rule is lost for that file, not only the findings of your rule.
So an invented context field costs much more than a rule that never reports.
`ctx.key` is the reason a method or a status code belongs on the node whose key varies.
Inside a `Responses` visitor, `ctx.key` is always `responses`, so `if (ctx.key !== 'delete') return` never passes.
That is the same error as a parent-key filter one level too high.
### Read resolved nodes, not raw children
Inside `enter`, a child that is a `$ref` is still a reference.
So `response.schema`, or the `name` of a parameter, is `undefined` for every member that is a `$ref`.
That turns into a false positive on an ordinary document: a uniqueness check reports "duplicate name: undefined", and a required-field check reports a field that is present behind the reference.
Use one of these instead:
- report in `leave`, where every `$ref` below the node is resolved;
- visit the type of the child, so the walker gives you the resolved node;
- call `ctx.resolve(ref)`.
Prove the rule with a fixture whose members are `$ref`s.
A fixture with everything inline does not test this.
Release notes
First release. Redocly skills for Redocly CLI commands, redocly.yaml configuration, custom lint rules, Markdown linting with recheck, and Redocly docs projects, plus the Redocly MCP server, which searches the current Redocly documentation and can report a documentation problem to the Redocly docs team.
Declared in the saved package. Remote tools may change independently.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package license
- MIT
- Package author
- Redocly
- Keywords
- See keywords
- Review scenarios
- 5 positive · 3 negativeScenario counts, not test results.
Declared capabilities
- Read
- Write
Package observed Nov 4, 2026.
Technical details
- First seen
- Nov 2, 2026 · 18:00 UTC
- Last seen
- Nov 5, 2026 · 00:00 UTC
- Collection status
- Collected
plugin_asdk_app_6ad620393af88191af4aafc6d67d3780
Download plugin data (JSON)