An independent implementation of the Blygger protocol; not affiliated with the Blygger project.
Soapbox for Blygger makes your WordPress site a publisher under the Blygger protocol, an open protocol for publishing on your own domain in a way other people’s software can subscribe to, archive completely, and quote exactly.
Once it is switched on, every post you publish also becomes an item of your blyg, at an address such as https://example.com/blyg/. For each item the plugin keeps:
Readers get a manifest, an RSS feed that any feed reader can use, a complete archive index, and a document for every item, with the content as both Markdown and clean, self-contained HTML.
The protocol is a draft. This plugin implements Blygger 0.3, which is before 1.0. Its wire format may change, and the plugin will follow it. What will not change is what publishing means: every address your blyg serves is a promise to keep answering there, and a pin cannot be undone. Read “Can I undo this?” below first.
Nothing is published until you say so. Activating the plugin publishes nothing. You choose where the blyg lives and how it is laid out, save that, and then switch publishing on. Before you run the backfill, which is the step that puts your existing posts out, do these four things:
This is an independent implementation. Soapbox for Blygger is not written by, endorsed by or affiliated with the authors of the Blygger protocol. The protocol’s specification is at blygger.org.
blyg support to your own post type.wp blyg backfill, status, pin, verify and origins.With its default settings this plugin makes no requests to any other server, and sends no data anywhere.
If, and only if, you switch on “Accept Webmentions from other blygs”:
blyg.json) of the blyg it says it belongs to, which is asked where that blyg keeps its items. Counting redirects, that is six HTTP GET requests at most, so three documents leave room for three redirects between them: a sender whose addresses each redirect twice will not verify, and should send the final addresses. Over its whole life a mention can be tried twice and put off three times, so thirty requests is the most one mention can ever cause. They go to the sender’s own server, whose address the sender chose, or to wherever it redirects.soapbox_blyg_webmention_client filter to name the visitor’s address.How a mention is handled. Your site answers the sender at once and looks nothing up while it does: the sender’s address is resolved, checked and fetched afterwards, by a background job. That job stops after twenty seconds in all, checked between each step; one step it cannot cut short is a DNS lookup, which can take as long as your server’s own resolver allows. Each sender is limited by the number of requests it makes, whether or not they are accepted.
Known limits. Each request is pinned to the address that was checked, which closes DNS rebinding. That needs PHP’s curl extension, which nearly every host has. A site without it, or one that sends its outbound requests through a proxy (WP_PROXY_HOST), cannot pin a request, so it verifies no Webmentions: they are accepted and then marked as failed. The templated layout’s manifest declares Blygger “0.3”, because the specification does not say what a manifest that uses the 0.4 construct should declare. IPv6-only senders are refused: a sender’s host must have an IPv4 address, and no address it has, in either family, may be private. WordPress only allows such requests on ports 80, 443 and 8080; a sender on another port is refused unless you add it with WordPress’s http_allowed_safe_ports filter. A mention is believed only when its item document is at exactly the address its own blyg gives for it, written plainly: an address with .. in it, or with characters percent-encoded that need not be, is refused. A document kept on another host than its blyg (a CDN, say) cannot be sent as the source itself; send the page that links to it, which must be on the blyg’s own host. In the templated layout, your own items are at addresses only your manifest names, so a receiver that implements Blygger 0.3 alone cannot verify a mention that names one of your items as its source; a receiver that reads the manifest’s templates can. That is one more reason the fixed layout is the one selected to begin with.
Caching. The plugin states its lifetime first, and relies on caches using the first of a repeated instruction, as RFC 9111 says and Varnish does. A cache that does not follow that rule could still use a lifetime your host appends. nginx’s proxy_cache and fastcgi_cache are one such case: when the host’s instruction arrives as a separate Cache-Control header line after the plugin’s, nginx uses the last line. This has not been tested with nginx in front. Site Health asks for the documents as a reader would and compares what it is given with the documents as they are now; if it is given an old copy, purge that cache and exclude the blyg’s paths from it as described above.
The source, with its tests, is at https://github.com/cyberscribe/soapbox-blyg. The scripts in build/ are compiled from assets/src/ there with npm run build.
The functions below are the stable API. Classes under src/ are internal. Guard calls with function_exists().
soapbox_blyg_register_origin( string $key, array $args ): void
soapbox_blyg_publish( string $origin, string $source_key, ?array $snapshot, array $opts = [] ): array
soapbox_blyg_reconcile( string $origin, array $snapshots_by_key, array $opts = [] ): array
soapbox_blyg_render_html( string $html, string $title, array $opts = [] ): array
soapbox_blyg_pin( string $id, int $version ): void
soapbox_blyg_get_status( string $origin, string $source_key ): ?array
soapbox_blyg_get_origin_url( string $origin ): string
Publishing your own post type. This is all it takes:
add_action( 'init', function () {
register_post_type( 'book', array(
'public' => true,
'label' => 'Books',
'supports' => array( 'title', 'editor', 'thumbnail', 'blyg' ),
) );
} );
Or, for a post type you do not control: add_post_type_support( 'book', 'blyg' );
Publishing something that is not a post. Register an origin, which is a second blyg at its own address, and hand the plugin snapshots. It decides what is a new version.
add_action( 'soapbox_blyg_register_origins', function () {
soapbox_blyg_register_origin( 'notes', array(
'path' => '/notes/',
'title' => 'Notes',
'author' => array( 'name' => 'Ada Example' ),
) );
} );
$rendered = soapbox_blyg_render_html( $html, $title );
soapbox_blyg_publish( 'notes', 'note:42', array(
'kind' => 'fragment',
'title' => $title,
'permalink' => 'https://example.com/notes/42/',
'content_md' => $rendered['content_md'],
'content_html' => $rendered['content_html'],
'media' => $rendered['media'],
) );
soapbox_blyg_publish( 'notes', 'note:42', null ); // Withdraws it.
Rules for snapshots: permalink must be an absolute http or https URL, since it is served as the item’s page; a thread must carry a transclusions array and a fragment must not; content_hash is always computed by the plugin. Give each item a page of its own if you want Webmentions to be able to name it by page.
Actions: soapbox_blyg_register_origins, soapbox_blyg_event (event, id, version, origin), soapbox_blyg_pinned (id, version, origin), soapbox_blyg_mention (status, mention, fields).
Actions: soapbox_blyg_purge_urls (the addresses whose documents changed in this request; see “Which caches does Soapbox for Blygger purge?”), soapbox_blyg_event, soapbox_blyg_pinned, soapbox_blyg_forget_stale.
Filters: soapbox_blyg_origin_args, soapbox_blyg_is_public, soapbox_blyg_snapshot, soapbox_blyg_rendered_body, soapbox_blyg_allowed_html, soapbox_blyg_cache_lifetime, soapbox_blyg_cache_control, soapbox_blyg_discovery_origin, soapbox_blyg_webmention_limits, soapbox_blyg_webmention_client, soapbox_blyg_fingerprint_meta.
Withholding posts with soapbox_blyg_is_public. Return false for a post and it is kept off the blyg, and withdrawn if it is already there: at its next save, and whenever anything of it would be served. Three things to know when you write one:
is_user_logged_in() and current_user_can() are false inside it. The question is whether the post is public, not whether the current user may read it.soapbox_blyg_fingerprint_meta filter, so that a change to one counts as a change to the post.do_action( 'soapbox_blyg_forget_stale' ) when that changes, or the feed and index may be up to a minute behind. An item’s own document is always checked afresh.If a plugin adds its own text to every post through the_content (a “Share this” heading, a list of related posts), remove it in soapbox_blyg_rendered_body, or it will be published in every item.
Capabilities: soapbox_blyg_manage (maps to manage_options) and soapbox_blyg_pin_item (maps to being able to edit and publish the post). Change either through map_meta_cap.
Templates: copy templates/soapbox-blyg-withdrawn.php or templates/soapbox-blyg-origin.php into your theme to change the page shown for a withdrawn item, or at the bare address of an origin you registered.
JavaScript sources are in assets/src/; the built files in build/ are produced from them with @wordpress/scripts. The bundled library, league/html-to-markdown (MIT), is in vendor-prefixed/ under a prefixed namespace.