The standard Papyrus front end
One renderer for static Papyrus sites: settings in projection.json, a theme, brand assets, and a few named hooks. What it produces and how a site configures it.
The standard Papyrus front end
A Papyrus static site is a web/ folder, and Papyrus renders it. The renderer writes the pages, listings, tag pages, sitemap, robots.txt, a 404 page, RSS and Atom feeds, and every head tag that search engines and social cards read. A site supplies settings, a theme, brand assets, navigation and redirect data, and, only when settings are not enough, a few named hooks. It does not write its own projector, sitemap writer, feed generator or edge function.
We moved our own sites onto it so that a fix or a feature lands once and every site gets it. Pilobol.us is the reference case, a publication with no sections at all.
The smallest site
{ "site": { "name": "Example" } }
That one setting gives a home page, one page for each published item, a paginated archive, tag pages when items have tags, sitemap.xml, robots.txt, 404.html, feed.xml and atom.xml. Sections are something you add, not something you must have.
projection.json
Every block is optional except the site's name, and unknown keys are an error, so a typo does not pass silently. The blocks are:
- site: name, tagline, description, language, production URL, author.
- urls: the page address style (
/x.html,/x/or/x) and the sections, if the site has any. - nav and footer: links, dropdown groups, columns.
- seo: share image, Twitter handles, the JSON-LD type for each kind of page, breadcrumbs.
- sitemap and robots: sitemap style,
lastmod, extra routes, extra lines. - feeds: RSS and Atom, how many entries, the paths.
- listings and collections: page size, which types are listed, tag pages, listings chosen by tag or section.
- redirects: exact redirects and anchored patterns, compiled into the edge function at deploy time.
- forms: public forms described as data.
- showcase: a logo wall.
- agents and pageActions: the agent-readable files and the page actions.
- hooks: the path of the site's hooks module.
Theme and brand assets
The look is a stylesheet and a set of --papyrus-* tokens, plus the site's fonts, icons and images. Component CSS ships as a baseline and a site can turn it off.
Hooks
When settings are not enough, a site names a hooks module. A hook is a small function with a documented name: it can replace the masthead or footer, change a card, add raw markup to the head, set the body class, or supply a page's body. Whatever a site does not hook, the standard renderer does. This site uses a hook for its page layout and nothing else.
Feeds, sitemap and SEO
RSS and Atom are generated from the same entries as the listings, so a feed never disagrees with the site. The sitemap carries lastmod. Each page's head is written by one builder, in one order: icons, feed autodiscovery, sitemap link, robots, description, canonical URL, Open Graph, Twitter, article times and tags, and JSON-LD with breadcrumbs. A site that is not the production site gets noindex on every page and a robots.txt that disallows everything, and never sends analytics.
Listings, tags and collections
Items without a section live at the root. Sections add listings at /<base>/ with pagination, a dated grouping by week or month, and optional archives by month or year. Tag pages and collections are listings chosen by a tag or section selector. Link-only sections list cards that point elsewhere and stay out of the sitemap and feeds.
Forms
A public form is data: its fields, a honeypot field, the texts, and an endpoint. With an endpoint, the form posts JSON and reports success only when the answer is a 2xx. With no endpoint it renders as a disabled form under a notice that it is not open yet, so nothing can be submitted and success is never faked.
The Built with Papyrus wall
A site can have a logo wall fed by published items. Each record is a name, a one-line description, a logo, a link and a kind. Adding a file adds a tile, with no code change. Ours is the Built with Papyrus page. The wall falls back to the site's name when it has no logo.
Page blocks, islands and the family footer
Marketing pages need full-width sections, columns and calls to action, and some need interactive widgets. Three parts of the standard front end cover that, and blocks and islands are in use on cyclotron.anth.us, chattic.us and tactus.anth.us. The family footer is on Cyclotron and Tactus, and Tactus also has grouped navigation.
Page blocks. In a site whose content is a directory of Markdown files, a line on its own can start a block: ::band is a full-width section (with a tone, a size and an eyebrow line), ::column starts the next column, ::button is a link styled as a button, and ::island is a mount point. They are opt-in, since a file with no ::band line is converted as before, and a bad attribute is a build error that names the file. The twin of a page with blocks is its Markus source: blocks stay as written, and an island carries a one-line summary of what it shows. Blocks do not work through the browser editor, which still rejects them. A {{name}} placeholder in a page is filled from a values object in projection.json, and a backslash before it prints the placeholder itself.
Islands. An island is a mount point for code the site owns, such as a chart or a carousel. Core finds the mount points and loads the site's script only on pages that have one. The script registers a function for each island name, and a missing or failing island logs a warning and never stops the page or the other islands. islands.prerender names a file of server-rendered HTML for named islands, so a page has its content before the script runs.
Header and footer. The brand can be a logo and a word instead of the site name, and navigation links can be grouped into a dropdown that works without a script. footer.family renders the Anthus family footer: the site's own columns, the "Made by Anthus AI Solutions" band listing our live sites, and the community links. urls.style can also be clean, which addresses a page as /x rather than /x.html; Cyclotron uses it.
Smaller options. These are in Papyrus 5.19.0, the release this site is built with:
site.dateFormatsets how dates print, as astrftimepattern; the default is ISO dates.site.ledeFieldnames a front matter field whose text is printed as the article's lede under its title.seo.isBasedOnFieldnames a field holding the URL of the original a page is based on, and it appears asisBasedOnin the page's JSON-LD. Cyclotron uses it.seo.articleTypeslists entry types that are not listed but still reportog:typearticle and Article JSON-LD.urls.sections.<name>.groupByacceptsfield:<name>, which groups a listing by a front matter field, with entries that lack it last under "Other". The other values areweekandmonth.pageActions.typesgives Copy Markdown and View as Markdown to further entry types, such as guides and standing pages. Cyclotron, Chattic and Tactus use it.
Agent-readable output
The standard front end also publishes a Markdown twin of every page, llms.txt, llms-full.txt, Markdown by Accept header, and page actions on articles and guides. That has its own page, including what does not work yet.
Running it
Papyrus is open source and the front end is part of it, so the documentation for building a static site is in the repository with the code. This site, p.apyr.us, is built the same way: its content is Markdown files, its settings are a projection.json, and its layout is one hooks module.