JSON Schema to Markdown
A JSON Schema is already the specification — what it is not is readable. This walks the schema and emits Markdown: a section per object, a table row per field, with its type, whether it is required, every constraint that applies, the default and the description. $ref pointers inside the document are followed, so a $defs-heavy schema comes out as a set of linked sections rather than a maze of pointers you have to hold in your head.
How to document a schema
- Paste the schema, or drop a
.jsonfile onto the left pane. - Pick the top heading level to match where the output is going —
##for a section inside an existing README,#for a standalone page. - Turn off the Constraints and Default columns for a narrow table that reads well on a docs site; leave them on for a reference page that has to be complete.
- Copy the Markdown into your README, wiki or docs generator. The anchors are GitHub-style slugs, so the cross-links between sections work as-is on GitHub, GitLab and most static site generators.
What ends up in each column
- Type — the declared
type, or what the schema implies:array of Xfor anitemsschema, a link for a$refor a nested object, andA | BforoneOfandanyOf.allOfjoins with+, because that is what it means. - Required — read from the enclosing object's
requiredarray, not from the field. This is the distinction schemas get wrong most often:requiredlives on the parent, so moving a property between objects silently changes whether it is mandatory. - Constraints — every bound in one cell:
minimum,maximum, the exclusive variants,multipleOf, lengths, item and property counts,pattern,format,uniqueItems,enumvalues,const, andadditionalProperties: false.readOnly,writeOnlyanddeprecatedshow up here too. - Default — the literal
default, JSON-encoded, so""andnullare distinguishable from an absent default. - Description —
description, falling back totitle, with the first entry fromexamplesappended when that option is on.
How $ref is handled
A pointer into the same document — #/$defs/Profile, #/definitions/Address,
or any other JSON Pointer — is resolved and rendered as its own section, with the field's Type cell
linking to it. That is what keeps the output readable: expanding every reference inline duplicates
the same table wherever a type is reused, and a recursive schema (a comment with replies, a tree
node) would never terminate.
A $ref that points outside the document cannot be resolved here, and rather than
rendering it as an empty object the pointer is printed literally and counted in the status line.
Run the schema through Bundle $refs first to inline the external
ones, then come back.
Definitions that nothing references are still part of the contract, so they are emitted at the end
marked (unreferenced). If your $defs block is a shared library and most of it
is irrelevant to this document, tick Skip unreferenced $defs.
Markdown table escaping
A pipe character ends a table cell and a newline ends the row, so a description containing either
would silently break the table. Both are neutralised: pipes are backslash-escaped and newlines
collapse to spaces. A multi-paragraph description therefore becomes one long line,
which is the price of putting it in a table at all — if your descriptions are prose, generate the
docs with the constraint columns off so there is room for them.
FAQ
Which drafts does it understand?
The keywords it reads are common to draft-07 and 2020-12, and it does not care which dialect $schema declares — $defs and definitions are both resolved. Draft-04's exclusiveMinimum: true boolean form is the one exception; it renders as a constraint but reads oddly. Run Migrate draft first if you have old schemas.
Why is a nested object a separate section instead of an indented table?
Because nesting depth in a schema is unbounded and Markdown tables do not nest. A section per object with links between them stays readable at any depth and survives being pasted into a docs site that reflows the content.
Can it go the other way — Markdown back to a schema?
No. Documentation loses information that a schema carries, so the round trip is not sound. Generate the schema from real data with the schema generator instead, then document it here.
Is anything uploaded?
No. The schema is parsed and rendered in your browser — nothing is sent anywhere.