add-docs-page

$npx mdskill add langchain-ai/docs/add-docs-page

A page is not finished when the MDX file exists. It also needs a navigation entry, and a move or deletion needs a redirect. CI enforces both.

SKILL.md

.github/skills/add-docs-pageView on GitHub ↗
---
name: add-docs-page
description: Add, move, rename, or delete a page on the LangChain docs site. Covers choosing the source directory, writing frontmatter, placing the entry in src/docs.json navigation, adding redirects, and verifying with the lint and broken-link gates. Use when asked to add a new doc or page, move or rename a page, put something in the nav, or add a redirect.
license: MIT
metadata:
  author: langchain
  version: "1.0"
---

# Add or move a docs page

A page is not finished when the MDX file exists. It also needs a navigation
entry, and a move or deletion needs a redirect. CI enforces both.

Read `AGENTS.md` for the navigation map, the style guide, and the frontmatter
rules. This skill covers the procedure around them and does not repeat them.

## Step 1. Choose the source directory

Use the "Source directory summary" table in `AGENTS.md` to go from subject to
directory. Two traps:

- Directory names do not match navigation names. `src/langsmith/fleet/` appears
  as "No-code agents"; `src/langsmith/managed-deep-agents*.mdx` appears under
  Build, not under a LangSmith menu.
- Lifecycle menus mix products. Build draws from both `src/oss/` and
  `src/langsmith/`; Test, Deploy, and Monitor all draw from `src/langsmith/`.

Never write to `build/`. It is Mintlify output, regenerated by `make build`.

## Step 2. Write the page

Required frontmatter:

```yaml
---
title: Clear, concise page title
description: SEO summary with no markdown, no links, and no backticks
---
```

For OSS pages that differ by language, use `:::python` and `:::js` fences in one
file rather than writing two files. The build pipeline emits both versions.

Before writing a JSX tag you have not seen in `src/`, look it up on
[Mintlify components](https://mintlify.com/docs/components) or with the
`docs-mintlify` MCP server. A plausible name is not evidence: `<Important>` and
`<ItemTable>` both reached `main` and rendered as nothing.

Take every fact on the page from the product source, not from memory, a sibling
docs page, or the request that asked for the page. This covers behavior,
defaults, field names, version minimums, and examples. Use the
`verify-against-source` skill to find the repository that owns each claim. Never
fabricate a value to fill a gap. Leave the claim out, and list what you could
not confirm in the pull request body and in your reply to the requester.

## Step 3. Add the navigation entry

Navigation lives in `src/docs.json` under `navigation.products`. There are two
products: `products[0]` is AGENT DEVELOPMENT LIFECYCLE (Home, Build, Test,
Deploy, Monitor) and `products[1]` is PRODUCTS AND SETUP (LangSmith setup, LLM
Gateway, No-code agents, Engine, Deep Agents Code).

Each menu item is addressed by its `item` key, then nests one of two ways:

- `menu[].tabs[].pages[]` for most menu items.
- `menu[].dropdowns[].tabs[].pages[]` for Build, which has Python and
  TypeScript dropdowns.

A `pages` array holds page-path strings and `{"group": ..., "pages": [...]}`
objects, nested to any depth.

Three rules:

1. **A language-versioned OSS page needs two entries.** Build page paths carry
   the language segment (`oss/python/deepagents/overview` and
   `oss/javascript/deepagents/overview`), so one new page means one entry in the
   Python dropdown and one in the TypeScript dropdown. Omitting the TypeScript
   entry is the most common miss.
2. **Page paths omit the `src/` prefix and the file extension.**
   `src/langsmith/sandboxes.mdx` is `"langsmith/sandboxes"`.
3. **A new group leads with an index page:**
   `"pages": ["group/index", "group/page"]`.

Integration pages are the exception. Add them to the component's `index.mdx`
instead, and touch `docs.json` only when creating a brand-new component group.

## Step 4. Add redirects for a move, rename, or deletion

For a move or rename of a file that was already on `main`, run the repo's mover first. It rewrites cross-references
across the corpus, which hand-editing misses: every link to the page in every
form (`/oss/langchain/x`, `/oss/python/langchain/x`, `href="..."`), plus the
`src/docs.json` nav entries and redirect destinations that name it:

```bash
uv run docs mv src/langsmith/old-name.mdx src/langsmith/new-name.mdx --dry-run
```

Read any `Left ... links unchanged` warning in the output. It marks links the
mover could not rewrite safely, such as an unversioned `/oss/integrations/...`
link that also reaches the other language's page. Fix those by hand.

Drop `--dry-run` once the preview looks right. The mover does not add a
redirect, so add one to the `redirects` array in `src/docs.json`:

```json
{ "source": "/langsmith/old-name", "destination": "/langsmith/new-name" }
```

Redirect paths are site paths and start with `/`. A language-versioned page
needs a redirect per language, plus one for the unversioned path if the old URL
had one.

`scripts/check_removed_pages_redirects.py` runs in CI and fails the PR when a
page leaves the navigation and its source file is gone with no redirect. The
same script fails when `docs.json` names a page whose file does not exist, so a
typo in a page path is caught there rather than at build time.

## Step 4b. Two traps that fail silently

### Extract a snippet once a block appears on three pages

`AGENTS.md` covers how to add a snippet. The rule for **when**: the same block
repeated on three or more pages becomes one file under `src/snippets/`. A status
callout duplicated across a page family means the wording change that retires it
is an edit to every page in the family, and one will be missed.

Verify a new snippet reaches the build. The pipeline rewrites snippet imports to
language-specific paths, so `/snippets/langsmith/x.mdx` becomes
`/snippets/python/langsmith/x.mdx` in the output, and a missing target renders as
nothing at all rather than as an error:

```bash
ls build/snippets/python/langsmith/<name>.mdx build/snippets/javascript/langsmith/<name>.mdx
```

### Editing a heading moves its anchor

A heading's slug is derived from its text, so rewording one silently breaks every
`#anchor` link pointing at it, including links from other pages and entries in
`src/docs.json`. Grep before editing:

```bash
grep -rn 'use-with-the-langsmith-gateway' src/ --include=*.mdx --include=*.json
```

Changing only capitalization is safe, because slugs are lowercased. Changing a
word is not, and needs either a reworded inbound link or a redirect.
`<Step>` and `<Accordion>` accept an explicit `id`, which is how to keep a
landing spot that is no longer a heading.

## Step 5. Verify

Run all three, in this order:

```bash
make lint_prose FILES="src/path/to/page.mdx"
make check-mdx
make broken-links-with-anchors
```

When `make build` fails with `Required uv version >=0.9.26 does not match the
running version`, the local `uv` has drifted from the one `pyproject.toml`
expects. Run the pipeline directly rather than working around the build:

```bash
PYTHONPATH="$(pwd)" .venv/bin/python -m pipeline build
```

A fresh git worktree has no `.venv`, and the same version check blocks
`uv sync` from creating one. Point at the main checkout's interpreter instead;
`PYTHONPATH` still loads the pipeline code from the worktree:

```bash
PYTHONPATH="$(pwd)" <main-checkout>/.venv/bin/python -m pipeline build
```

A fresh worktree's `.mise.toml` is also untrusted, so the first commit fails
at the Vale hook with `Config files in <dir>/.mise.toml are not trusted`. Run
`mise trust` once in the worktree. The `docs-edit` skill covers the permanent
fix.

`make broken-links-with-anchors` depends on `build`, so it fails the same way.
Its link-check half runs on its own once the build output exists:

```bash
cd build && mint broken-links --check-anchors | tee /tmp/bl.txt
cd .. && python3 scripts/filter_mint_broken_links.py --check-anchors --input /tmp/bl.txt
```

Read `make broken-links-with-anchors` output by skipping to the `⎿` lines. Those are the only
real failures. A bare filename with no indented lines beneath it is an
OpenAPI-generated page that exists at deploy time but not locally.

Fix every Vale finding. CI blocks on `lint_prose`, and its most common failure is
a spaced em dash (`word — word` must be `word—word`).

## Step 6. Review the prose

Once the edit is complete and before committing, invoke the `docs-review` skill
on the files this pass changed. It runs in working-tree mode, so it needs no
checkout, and it covers the style-guide rules Vale cannot see: passive voice,
filler, product versus common noun capitalization, structure conventions, and
link text.

Run it on finished edits only. A review of a half-written section produces
findings that go stale as soon as writing resumes.

Skip this step for a change too small to have prose in it, such as a pure
`docs.json` reorder or a redirect-only fix.

## Step 7. Open the pull request

Commit, push, and open the PR however you usually do. If the change belongs on
an existing PR, use `docs-edit` instead. Two rules apply to the PR description:

- **Linear**: When the work has a Linear issue, link it with
  `Closes [DOC-N](<linear url>)` or `Refs [DOC-N](<linear url>)` as
  appropriate. `sync-linear-issue-from-pr.yml` only syncs status for
  docs-release-watch PRs labeled `release-watch-agent`, so a human PR does not
  move the issue.
- **AI disclosure**: End the description with one plain sentence, such as
  `Prepared with Claude Code.` Do not add a `🤖 Generated with [Claude Code](...)`
  badge or any similar badge. `check-pr-ai-attribution` fails a PR that carries
  one, because the squash merge copies it into the commit on `main`.

## Checklist

- [ ] File in the directory the source-directory table names, not `build/`.
- [ ] Frontmatter present, `description` free of markdown.
- [ ] Every claim checked against product source; unconfirmed claims cut and
      listed in the PR body.
- [ ] `src/docs.json` entry in the right product, menu item, tab, and group.
- [ ] Both language entries added if the page is language-versioned.
- [ ] Index page first if a new group was created.
- [ ] Redirect added for every moved, renamed, or deleted path.
- [ ] Inbound `#anchor` links checked before any heading was reworded.
- [ ] A block now on three or more pages extracted to `src/snippets/`, and its
      built `python/` and `javascript/` targets confirmed to exist.
- [ ] `make check-mdx` clean.
- [ ] `make lint_prose` clean, `make broken-links-with-anchors` shows no new `⎿` lines.
- [ ] `docs-review` run on the changed files, findings addressed.
- [ ] PR description references the Linear issue, if any, and has no AI badge.

More from langchain-ai/docs

SkillDescription
deep-agentsBuild batteries-included agents with planning, context management, subagent delegation, and sandboxed execution. Use for complex, multi-step tasks that need built-in capabilities.
deepagentsAnalyze Deep Agents Python library releases against LangChain Deep Agents docs and prepare one-feature documentation updates.
deepagents-codeAnalyze Deep Agents Code releases against LangChain Deep Agents Code docs and prepare one-feature documentation updates.
deepagentsjsAnalyze Deep Agents TypeScript releases against LangChain Deep Agents docs and prepare one-feature documentation updates for JS-facing content.
docs-code-samplesUse this skill when migrating inline code samples from LangChain docs (MDX files) into external, testable code files that are extracted by this repo’s snippet scripts and used as Mintlify snippets. Applies when extracting code blocks from documentation, creating runnable code samples, using snippet delineators, or wiring snippet output into MDX includes.
docs-editEdit a docs page that already has an open pull request, or revise a page in place. Covers checking out the PR's own branch instead of cutting a new one, forked cross-repository PRs, reading the real diff, checking the edit against related pages, and the verification to run before handing off. Use when asked to edit, revise, fix, or review changes on a named PR, branch, or existing page.
docs-restructureRestructure documentation that spans several pages. Covers splitting a page that has grown too long, moving sections between pages, retiring a page, and fixing a topic documented in two places at once. Use when asked to streamline, simplify, consolidate, or reorganize docs, or when a page review keeps producing edits without the page getting shorter.
docs-reviewReview changed docs prose against Vale and the AGENTS.md style guide, reporting findings with the rule each one breaks. Use after authoring or editing a page and before committing, or to review a PR, a branch, or the current diff.
docs-team-voiceWrite or revise documentation prose so it reads like the rest of this site, in the docs team's shared voice. Covers sentence length, cross-link density, how to state defaults and conditions, naming exact identifiers, and what to cut in a revision pass. Use when drafting or rewriting a page, a section, a concept overview, a how-to, a callout, or landing copy.
docs-tooling-notionDocument new or changed docs-team tooling on the Notion tooling pages. Decides which of the five pages owns the topic, what belongs in the repo instead of Notion, and how to edit safely through the Notion MCP server. Invoke it whenever work adds or changes a script under scripts/, a GitHub Actions workflow, a Makefile target, a PR check, a scheduled job, an agent, a skill, or an MCP server, and invoke it before handing off that pull request. Shipping tooling is itself the trigger, so do not wait to be asked to document it and do not wait for the PR to merge.