strapi-plugin-atproto#
Generic Strapi v5 plugin that publishes configured content-types to AT Protocol using the site.standard.publication and site.standard.document lexicons from Standard.site.
Features#
- Publishes a Standard.site publication record
- Maps arbitrary Strapi content-types to Standard.site document records
- Syncs on create, update, publish, unpublish, and delete through Strapi's document service middleware
- Stores remote AT URIs,
rkeys, CIDs, and sync status in plugin-owned content-types - Provides an admin UI for publication settings, field mappings, and manual resync actions
- Keeps AT Protocol app passwords out of the database by reading them from environment variables
Install#
pnpm add strapi-plugin-atproto
For local plugin development:
// config/plugins.ts
export default {
'strapi-plugin-atproto': {
enabled: true,
resolve: './src/plugins/strapi-plugin-atproto',
},
};
Environment variables#
The plugin reads AT Protocol credentials from environment variables.
ATPROTO_SERVICE=https://bsky.social
ATPROTO_IDENTIFIER=your-handle.example.com
ATPROTO_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
ATPROTO_ENABLE_AUTO_SYNC=true
ATPROTO_APP_PASSWORD is required for real sync operations. The identifier and service can also be overridden in the admin UI, but the password stays env-backed.
Plugin configuration#
Optional configuration values can be set in config/plugins.ts:
export default {
'strapi-plugin-atproto': {
enabled: true,
config: {
atprotoService: env('ATPROTO_SERVICE', 'https://bsky.social'),
atprotoIdentifier: env('ATPROTO_IDENTIFIER', ''),
atprotoAppPassword: env('ATPROTO_APP_PASSWORD', ''),
enableAutoSync: env.bool('ATPROTO_ENABLE_AUTO_SYNC', true),
},
},
};
Admin setup#
- Open the
AT Protocolplugin in the admin sidebar. - Fill in publication metadata:
- publication name
- publication URL
- optional description
- optional icon URL
- Confirm the AT Protocol service and identifier.
- Add one or more content-type mappings.
- For each mapping, select top-level fields for:
- title
- path
- description
- text content
- published at
- updated at
- tags
- cover image
- Save settings.
- Use
Publish publication recordto create or update thesite.standard.publicationrecord. - Use
Resync mapped contentto backfill existing entries.
Mapping behavior#
- Only enabled mappings are synced.
- For draft-and-publish types, the plugin syncs published versions.
- For content-types without draft-and-publish, the plugin treats saved entries as live.
- The selected path field is normalized into a leading-slash path.
pathPrefixlets you force mapped documents under a fixed prefix such as/blog.- Tag strings are comma-split; arrays are passed through as strings.
- Cover images and publication icons are fetched from their URLs, uploaded as AT blobs, and attached to records.
Stored plugin data#
The plugin creates two internal content-types:
publication-configStores publication metadata, mappings, and the publication record's AT URI /rkey/ CID.sync-stateStores per-entry remote metadata, sync hashes, status, and errors.
These types are hidden from the Content Manager and Content-Type Builder.
Verification requirements#
The plugin can publish valid Standard.site records, but your public site still needs to expose verification pointers.
Publication verification#
Serve this route on your publication domain:
/.well-known/site.standard.publication
The response body should be the publication AT URI:
at://did:plc:.../site.standard.publication/...
Document verification#
Each rendered article page should include:
<link rel="site.standard.document" href="at://did:plc:.../site.standard.document/..." />
The plugin stores the document AT URI in the sync-state content-type so your frontend can look it up and expose it.
Development#
pnpm install
pnpm test:ts:back
pnpm test:ts:front
pnpm build
Current scope#
- One publication/account per Strapi instance
- Top-level field mapping only
- Publication icon and cover image inputs are URL-based
If you need multi-publication routing, nested/component field mapping, or frontend helpers for exposing verification tags, those can be added on top of the current architecture.