A zero-dependency AT Protocol Personal Data Server written in JavaScript
atproto pds

pds.js#

A zero-dependency AT Protocol Personal Data Server written in JavaScript, running on Cloudflare Workers with Durable Objects. Let's see how far we can get with just Web APIs.

⚠️ Work in progress - This is experimental. You probably shouldn't use this yet.

Status#

  • Repo operations (createRecord, getRecord, putRecord, deleteRecord, applyWrites, listRecords)
  • Sync endpoints (getRepo, getRecord, subscribeRepos, listRepos, getLatestCommit)
  • Auth (createSession, getSession, refreshSession)
  • Handle resolution (resolveHandle)
  • AppView proxy (app.bsky.* forwarding with service auth)
  • Relay notification (requestCrawl)
  • Single or multi-user (each DID gets isolated storage, no self-service signup yet)
  • Blob storage (uploadBlob, getBlob, listBlobs)
  • OAuth 2.0 (PAR, authorization code + PKCE, DPoP-bound tokens, refresh, revoke)
  • deleteSession (logout)
  • updateHandle
  • importRepo
  • Account management (createAccount, deleteAccount)
  • Email verification
  • Invite codes
  • Admin/moderation
  • Rate limiting

See endpoint comparison for detailed coverage vs the official atproto PDS.

Prerequisites#

  • Node.js 18+

Quick Start#

npm install

# Create local dev config
cp .env.example .dev.vars
# Edit .dev.vars with your values

# Run locally
npm run dev

Configuration#

For local development, create .dev.vars:

PDS_PASSWORD=your-password  # Used for legacy auth and OAuth consent
JWT_SECRET=your-secret
RELAY_HOST=https://bsky.network  # optional

For production, use Cloudflare secrets:

wrangler secret put PDS_PASSWORD
wrangler secret put JWT_SECRET
wrangler secret put RELAY_HOST  # optional

Blob Storage#

Blobs (images, videos) are stored in Cloudflare R2. Create the bucket before deploying:

npx wrangler r2 bucket create pds-blobs

The binding is already configured in wrangler.toml. Supported formats: JPEG, PNG, GIF, WebP, MP4. Max size: 50MB. Orphaned blobs are automatically cleaned up after 24 hours.

Testing#

npm test          # Unit tests
npm run test:e2e  # E2E tests (starts local server)

Deploy#

wrangler deploy

Initialize#

After deployment, run the setup script to register with PLC and initialize:

npm run setup -- --pds https://your-pds.workers.dev

This generates keys, registers your DID with the PLC directory, initializes the PDS, and saves credentials. Handle defaults to the worker hostname.