ATProto Starter

Generate working Cloudflare Worker apps for ATProto in 60 seconds
1
Template
2
Configure
3
Output
4
Tips

Read-Only Indexer

Crawls ATProto records from configured PDSes, stores in D1, and serves a dashboard. Runs on a cron schedule.
D1 Cron Dashboard
Like: Inlay Observatory, ATProto Coordination

Read-Write App

Full CRUD with ATProto session auth or password auth, D1 storage, and two-step write pattern (PDS first, then D1 sync).
D1 Auth CRUD
Like: Agora, Chorus

Simple SPA

Static interactive page that reads from the ATProto public API. No database, no auth. Includes CORS proxy for browser access.
Static CORS Proxy
Like: Lexicon Map
Used for Worker name and D1 database name. Lowercase, hyphens only.
The ATProto collection namespace. e.g., site.filae.agent.task
OAuth DPoP Response Mode
When implementing ATProto OAuth, use responseMode: 'query' not 'fragment'. Fragment mode silently fails because Cloudflare Workers cannot read URL fragments from the client.
Cloudflare Error 1042
If you get error 1042 (Worker exceeded CPU time limit) when accessing D1, it often means your D1 binding is misconfigured. Use shared D1 bindings across workers in the same account. Double-check the database_id in wrangler.toml matches your actual D1 database.
Two-Step Write Pattern
When writing data: always update the PDS first via putRecord, then sync to your D1 index. If PDS fails, don't write to D1. If D1 fails after PDS succeeds, your cron crawler will eventually catch it. Never write to D1 alone -- the PDS is the source of truth.
Lexicon DNS Discovery
To make your custom lexicon discoverable, add a _lexicon.{authority} TXT record to your DNS pointing to your DID. For example: _lexicon.filae.site TXT "did:plc:dcb6ifdsru63appkbffy3foy"
Wrangler Deploy Hangs
wrangler deploy sometimes hangs on CI. As a fallback, use the Cloudflare REST API: upload the Worker script directly to https://api.cloudflare.com/client/v4/accounts/{id}/workers/scripts/{name}. The GitHub Actions workflow uses wrangler-action@v3 which handles timeouts better.
CORS: Proxy Through Worker
Browser JavaScript cannot call bsky.social/xrpc directly due to CORS. Always proxy ATProto API calls through your Worker using a /api/proxy route. The SPA template includes this pattern.
D1 Batch Init
Use db.batch() for multi-statement D1 initialization (CREATE TABLE, CREATE INDEX). Individual db.exec() calls with multiple statements separated by semicolons can fail silently. The db.batch() approach also runs in a single round-trip.