pterodocs
v0.5.0
Published
Publish a Docusaurus site to WordPress as a tree of Gutenberg pages.
Maintainers
Readme
pterodocs
Your documentation, where people actually find it.
Publishes a Docusaurus site to WordPress as a tree of native Gutenberg pages, so the docs you write once are indexed under your own domain without being rewritten by hand.
Install · Usage · Configuration · How it works · Plugin · Status
What is it?
A one-way publisher. Docusaurus stays the source of truth; WordPress becomes a mirror. Every page it writes is real Gutenberg block markup — headings, lists, tables, code, admonitions, images — so the result is editable in WordPress rather than a wall of embedded HTML.
It does not invent its own idea of your site. It asks Docusaurus, using Docusaurus's own loader, which means resolved sidebars (autogenerated ones included), versions, locales, draft and unlisted flags, permalinks and previous/next links all come from the same code that builds your site.
Install
npm install --save-dev pterodocspterodocs runs inside a Docusaurus project and expects @docusaurus/core to be present.
It pulls in @pterodocs/core, @pterodocs/docusaurus and @pterodocs/wordpress, which are
published in lockstep with it.
Installing from a git URL is not supported: the repository is an npm workspace, and a git install would pack only its private root.
Usage
npx pterodocs render # render every page locally; contacts nothing
npx pterodocs sync --dry-run # say what would change on the site
npx pterodocs sync # create and update
npx pterodocs sync --prune # also trash pages whose document is gone
npx pterodocs doctor # check config, credentials and permissionsCredentials come from the environment, never from the config file:
export WP_URL=https://example.com
export WP_USER=someone
export WP_APP_PASSWORD='xxxx xxxx xxxx xxxx' # Users → Profile → Application PasswordsWithout them every command still renders, and reports what it would have done.
Configuration
pterodocs.config.mjs beside your Docusaurus config:
import { defineConfig } from 'pterodocs';
export default defineConfig({
site: { sidebars: ['docs'], versions: 'last', locales: 'default' },
target: { type: 'wordpress', root: '/docs' },
});Anything Docusaurus already knows — the site URL, base URL, route base path, locales, markdown format, admonition keywords — is read from your Docusaurus config and is not repeated here.
Every option, with its default and what reads it, is at https://onyx.ac/products/pterodocs/docs/config/ — published by pterodocs itself.
llms.txt
Every run writes llms.txt and llms-full.txt — the index and the full text described at
llmstxt.org — into the output directory, and stores them on the
documentation root page for the WordPress plugin to serve:
https://example.com/<root>/<base>/llms.txt
https://example.com/<root>/<base>/llms-full.txtThey are built from what the run published rather than from the Docusaurus build, and that
is the whole point of them: the links are the WordPress URLs, the selection is the sidebars,
locales and versions your configuration names, and no docusaurus build has to have run.
A build-time plugin writes a file describing the Docusaurus site, which is a different site.
The files sit at the documentation root rather than at the top of the domain. The convention
is /llms.txt, but that file is supposed to describe everything published at a domain, and
pterodocs only knows about its own tree — so claiming it would mean promising the site and
delivering a subtree, and would collide with any SEO plugin that generates one. The plugin
advertises the real location from a <link rel="alternate"> on documentation pages and a
comment in robots.txt.
Serving them needs the plugin, because WordPress serves nothing statically. Without it the files are still written to the output directory, and the run reports that the site refused to store them.
llms: {
index: true, // write llms.txt
full: true, // write llms-full.txt
publish: true, // store both on the site for the plugin to serve
title: '', // heading; the site's own title by default
description: '', // summary; the documentation root's description by default
}How it works
Pages mirror your documentation URLs, so /docs/guides/sync on Docusaurus becomes
/<root>/guides/sync/ on WordPress. A directory becomes a page of its own, titled by its
sidebar category. A page is identified by its parent and its slug, so re-running only
rewrites what actually differs, and a second run reports everything as unchanged.
How it looks
WordPress renders core blocks with almost no opinion, so pterodocs publishes a stylesheet with them: the navigation loses its list markers and becomes a sticky column that scrolls on its own, code gets a frame and a monospace face, tables scroll instead of overflowing, and the layout runs full width. Fences are tokenised at publish time, so code is highlighted with no JavaScript on the page at all.
Every colour is mixed from currentColor, so it follows your theme into dark
rather than assuming light, and every rule is wrapped in :where(), so a theme
that already styles these class names keeps its own opinions.
The stylesheet is stored on each page, which is the only place a site running
nothing can read it from. It is the same few kilobytes on every page, so it is
also written to <output>/docs.css: paste that into Appearance, Customise,
Additional CSS once and set render.styles to 'none', and the pages carry
nothing.
render: {
styles: 'inline', // 'none' once the CSS lives on the site
highlight: true, // tokenise fences at publish time
}The WordPress plugin
Optional, and worth installing. pterodocs writes ordinary core blocks, which are
correct but plain: no syntax highlighting, tables that overflow, a sidebar that
is a bare expanded list. The plugin in packages/wordpress/plugin styles them.
It registers no block types, so nothing about your content becomes dependent on it. Deactivate it and the documentation is still there, still readable, still navigable.
npm run --workspace @pterodocs/wp-plugin build # writes pterodocs.zipUpload that under Plugins, Add New, Upload Plugin. Defaults live at Settings, pterodocs; any single block can override them from the block inspector.
Two things to know. Its class prefix must match render.classPrefix — set it on
the settings page, which needs no re-publishing; pterodocs doctor says whether
the two agree. And because a sync rewrites a page's content, an override set on a
block in the WordPress editor is replaced the next time that page is published:
the settings page is the durable place for a preference.
Once it is installed, render.blocks: 'plugin' lets pterodocs carry instructions
core blocks cannot express — chiefly a fence's highlighted line range, which is
otherwise reported as dropped. It travels in the block comment rather than in
markup, so WordPress stores the same content either way.
Status
Working, and published.
The publisher has been verified against a real 47-page documentation set: rendering matches the script it was extracted from on 42 of those pages, and every one of the remaining five differs only where pterodocs is now correct.
Not yet exercised against a site that uses them: versions, locales, and the Docusaurus build plugin. The WordPress plugin is newer still — its PHP and JavaScript are checked in CI and its markup decisions are covered by tests on the publisher's side, but its behaviour in a browser has not been through a release on a live site.
Roadmap
Next, in the order they would help most:
- A page template the plugin provides.
target.templatealready names one, and pterodocs assigns it to every page and reconciles it like any other field — but the slug has to match a template the theme ships, so a site whose theme offers nothing full width has nothing to point it at. The plugin could carry its own: a template built for documentation, with no constrained content column to escape from. That is where the awkward part of the stylesheet comes from —alignfull, and then re-adding the page padding the theme was already applying — and three of this cycle's layout bugs lived in those two rules. Classic and block themes register templates by different mechanisms, so it is two implementations behind one setting, anddoctorshould report which one the site got. pterodocs preview. Render, then write a browsable page showing the result at a phone width and a desktop one, with the navigation reconstructed — WordPress renderscore/page-listserver-side, so a rendered file has only its placeholder and the sidebar cannot otherwise be seen. Built by hand three times while developing the stylesheet, and it produced two false bug reports by quietly clipping its own output, which is the argument for it being a command with tests rather than a script.- An example Docusaurus site. The fixture the integration test uses is four documents, which is enough to prove the loader works and not enough to show a sidebar, an admonition, tabs or a wide table. A fuller example would give the preview something honest to run against, and would make the goldens representative of what people actually publish.
Licence
CC-BY-SA-4.0. See LICENCE.md.
