What’s allowed: the three-clause test
Content belongs on docs.optimism.io only if at least one of the following is true (adapted from the Kubernetes content guide’s third-party content rules):- It documents first-party OP Stack software — software whose source of truth lives in Optimism repositories, such as the components listed on the Releases page.
- It documents third-party software that the OP Stack needs to function
— for example, an L1 execution client or key-management tooling that an
OP Stack chain cannot run without. Such content must be marked with the
<ThirdPartyContent>component. - It routes to canonical content that lives elsewhere — a selection, orientation, or hub page whose job is to send readers to the right canonical home (for example, a curated matrix of SDKs that links each SDK’s own documentation).
Link, don’t restate: the dual-sourcing ban
Wherever a canonical source already exists, link it — never restate it. The Kubernetes content guide states the reason plainly: dual-sourced content “requires double the effort to maintain and grows stale more quickly.” In practice:- Never paraphrase normative protocol text. Explain the concept in your own words at explanation depth, then deep-link the exact section of the OP Stack specifications for the normative definition.
- Never copy reference material from another living document. If a component’s book, README, or upstream API reference already documents something, link to it.
- Never fork a table of facts (versions, addresses, activation times, flag lists) that another system maintains. Render from the source of truth or link it.
Canonical homes
One home per thing. The matrix below assigns a canonical home to each content type across the three layers of the OP Stack documentation surface — the protocol, the components, and the periphery — and states what docs.optimism.io holds for each.
Precedents for the matrix, clause by clause:
- Spec joined, never mirrored. Kubernetes documents feature lifecycles through its structured feature gates reference rather than copying design documents into prose.
- One identity page per component. Cloudflare publishes a uniform per-product content strategy so every product’s documentation set has the same shape.
- A curated matrix over the periphery. Stripe’s SDK page differentiates its client surfaces in one table; ethereum.org publishes written listing criteria so curation is policy application rather than per-PR debate.
- The component declares its docs home. Each component’s README should point at its canonical documentation, following the op-deployer README model.
Component reference
Every first-party component has one reference in these docs, covering its command-line interface, its JSON-RPC API, and its metrics, and every page of it is generated. This section is the convention that makes those references uniform across components; the generator and its lint enforce it, and reviewers cite it when a change would break it. Components covered: op-node, op-batcher, op-proposer, op-challenger, op-conductor, op-supernode, op-deployer, op-reth, and kona-node. A component joins the list by being added to the generator, never by a hand-written page.Source of truth
Every surface is generated from the component at a finalized release tag, never from a pre-release, and never fromdevelop except into the
develop version described under Versions.
- Command line: the
--helpoutput of the published release artifact (the Docker image published for the tag, or the release binary where one is published).--helpis the one surface every binary exposes the same way, whether it is a Go program built on urfave/cli or a Rust program built on clap, and it is by definition what the released binary accepts, so it cannot drift from the release the way a source parser run on the wrong commit can. geth and reth document their CLIs from the same output. - JSON-RPC: the method registrations in the component’s source at the
tag: the
rpc.APInamespaces the Go services register, and the#[rpc]and#[method]attributes on kona-node’s jsonrpsee traits. A component that inherits an upstream client’s RPC (op-reth) points at the upstream reference for the inherited namespaces and generates only the OP Stack additions. - Metrics: the metric definitions (name, type, labels, help string) in the component’s source at the tag, with the same upstream rule.
- Configuration schemas that have no
--help, such as the deploy config, are generated from the source at the same tag.
Rendering
One generator, one output shape, whatever the framework. Every flag renders as a table row (flag, environment variable, default, description), and the verbatim--help text follows in an expandable block so an operator can
match what their terminal shows. RPC pages render one section per namespace
with a table of methods (method, parameters, result, description). Metrics
pages render one table (name, type, labels, description).
Page tree
- One page per top-level command.
- Subcommand families collapse onto their parent page as one heading per
subcommand, so a subcommand stays deep-linkable by anchor
(
db/checksum#mdbx) without a page of its own. - Option blocks that every command repeats (logging, metrics, profiling,
RPC) hoist to one
global-optionspage per component. - One
rpcpage and onemetricspage per component, beside the command pages. - URLs are
reference/<component>/<version>/<page>and never deeper.
Versions
- One version per minor release line (
v1.19,v2.4), generated from the latest finalized patch in that line. The provenance header names the exact tag. - The newest line is the default and is tagged Latest. The newest three lines are published; older lines are removed, with redirects into the newest line.
- A patch release that changes a surface regenerates its line in place.
- One further version named
develop, tagged Unreleased, generated from thedevelopbranch on every merge that touches the component. Its provenance names the commit, and its pages carrynoindex: true, so unreleased behavior never appears in site search or search engines. It exists so that a reader who meets an<Unreleased>callout can see what is coming.
Provenance and no hand edits
- Every generated page opens with a
DO NOT EDITcomment, before any prose and after any imports, naming the exact tag (or commit, for thedevelopversion), linking the GitHub release, and naming the artifact it was generated from. The generator’s manifest records the tag, artifact, and content hash per component per version. - Release notes are linked, never copied. The provenance link is the pointer to what changed in that release; the generated release history pages remain the home of the notes.
- Nothing under the top-level
reference/directory is hand-written. Orientation (“how the CLI is organized”), advice on choosing values, and notes on selected flags live in guides in the persona tabs and link into the reference.pnpm lint:referencefails any page underreference/without the generated header. - A hand edit to a generated page fails the generator’s drift check on the next run; regenerate instead.
Transition. The component references published today live inside the
persona tabs (for example
chain-operators/reference/ and
node-operators/op-reth/cli/), some generated and some hand-maintained.
They move into the top-level reference/ tree as each component joins the
generator, and the rules above apply to a component once it has moved.
Until then, do not add a new hand-maintained flag table anywhere; add the
component to the generator instead.Regeneration and automation
Nobody runs the generator by hand, and nothing depends on an agent: every job below is a deterministic script in monorepo CI, and its output is verifiable by the generator’s own--check.
- On every finalized release tag, a CI job runs the generator against that release’s artifact and opens a docs pull request with the regenerated version, its manifest, and any redirects. The docs team reviews and merges it. A tag that has been public for a week with no such pull request is a pipeline bug; file it.
- On every merge to
developthat touches a covered component, the same job regenerates that component’sdevelopversion and merges it without review, since it carries no editorial content and is not indexed. - On every docs pull request, CI runs the docs lints: navigation, redirects, reference, and link policy.
- Weekly, CI regenerates the release history pages from
GitHub Releases into one rolling pull request, and runs the reference
lint in strict mode, which fails on an
<Unreleased>callout whose release has since been tagged and lists the callouts that name no version, so a maintainer can confirm what has shipped.
Documenting unreleased changes
docs.optimism.io deploys fromdevelop; components ship from release tags.
A component pull request that changes behavior should update the docs in the
same pull request, as follows:
- Generated reference: do nothing. The pipeline regenerates the component’s reference at the next finalized tag.
-
Hand-written guides and explainers: edit the prose in the same pull
request and wrap the changed statement in the
<Unreleased>component, naming the component. The release that will carry the change is usually not known when the change merges, so the version is optional; give it when you know it:Which renders: The callout is temporary. When it names a version,pnpm lint:referencewarns on every run once that tag exists, and the weekly CI sweep, which runs the lint in strict mode, fails until the callout is removed. The check lives in the weekly job rather than in pull-request CI so that a release tag appearing cannot turn an unrelated pull request red. When the callout names no version, the weekly sweep lists it, and a maintainer removes it after confirming the change has shipped. The lint also rejects a callout that names a component outside the covered list, or a version that is not a fullvX.Y.Z, since such a callout could never become stale. -
Removing a feature: add the callout, or set
deprecated: truein the page’s frontmatter, in the same pull request. Delete the page only after the release ships, with a redirect. -
Embargoed content that must not go live before a release uses the
flag:merge-pending-releasepull request label instead.
Marking third-party content
This section documents the
<ThirdPartyContent> component, following the
Kubernetes thirdparty-content shortcode
pattern: every third-party mention is stamped the same way, so third-party
content stays greppable and auditable.<ThirdPartyContent> component:
Citing the normative spec
This section documents the
<NormativeSpec> component, the standing
callout that applies the dual-sourcing ban
to protocol pages: every page whose subject is normatively defined in the
OP Stack specifications is stamped the same
way, so spec citations stay uniform and greppable.diataxis: explanation, primarily the concept pages in the OP Stack
section. An explainer page whose subject is normatively defined in the spec
must open with the <NormativeSpec> component, deep-linking the exact spec
section that defines its subject:
what names the subject the spec defines,
title and href name and deep-link the governing spec section (a rendered
specs.optimism.io URL on its current path, per the
link policy), and note states what the
page does instead of restating the spec.
Guides, tutorials, and reference pages do not open with the component.
They should not go deep into protocol workings at all — that depth belongs
on an explainer page or in the spec itself. Where a guide, tutorial, or
reference page needs to touch a spec-defined concept, link the relevant spec
section inline at the point of use instead.
One deliberate exception: the
hardfork registry pages are reference
pages, but the registry’s purpose is the spec pointer, so they carry the
same component with the spec URL from their structured frontmatter.
Next steps
- Read the style guide for voice, tone, and formatting conventions.
- Read the contributing guide for development setup and the pull request process.
- Have questions? Open an issue in the Optimism monorepo.