Documentation Index
Fetch the complete documentation index at: https://mintlify.com/bluesky-social/atproto/llms.txt
Use this file to discover all available pages before exploring further.
@atproto/pds
TypeScript reference implementation of an atproto Personal Data Server (PDS). The PDS is responsible for hosting user data, managing identities, and coordinating with the wider AT Protocol network.
If you are interested in self-hosting a PDS for production use, see the official PDS distribution repository which includes deployment configuration, documentation, and Docker support.
Installation
Overview
The PDS package provides the core server implementation for hosting user repositories, managing authentication, handling blob storage, and synchronizing data with AppViews and other services in the AT Protocol network.
Key responsibilities:
- User account management and authentication
- Repository hosting and data storage
- Blob (media) storage and management
- Event sequencing and synchronization
- Identity resolution (DIDs and handles)
- OAuth provider implementation
- Email verification and invites
Main Exports
PDS Class
The main server class that orchestrates all PDS functionality.
import { PDS, ServerConfig, ServerSecrets } from '@atproto/pds'
const config: ServerConfig = {
service: {
port: 2583,
hostname: 'pds.example.com',
publicUrl: 'https://pds.example.com',
did: 'did:web:pds.example.com',
devMode: false,
blobUploadLimit: 5 * 1024 * 1024, // 5MB
acceptingImports: true,
},
// ... additional configuration
}
const secrets: ServerSecrets = {
jwtSecret: 'your-jwt-secret',
plcRotationKey: 'your-plc-rotation-key',
}
const pds = await PDS.create(config, secrets)
await pds.start()
Methods
static async create(config: ServerConfig, secrets: ServerSecrets, overrides?: Partial<AppContextOptions>): Promise<PDS>
Creates a new PDS instance with the provided configuration.
async start(): Promise<http.Server>
Starts the PDS server and begins listening for requests.
async destroy(): Promise<void>
Gracefully shuts down the server and cleans up resources.
AppContext
The application context contains all shared services and dependencies.
import { AppContext } from '@atproto/pds'
// Access from PDS instance
const ctx = pds.ctx
// Available services:
ctx.db // Database access
ctx.accountManager // User account management
ctx.actorStore // Repository storage
ctx.sequencer // Event sequencing
ctx.blobstore // Blob storage
ctx.idResolver // Identity resolution
Database
Database interface for PDS data access.
import { Database } from '@atproto/pds'
const db = ctx.db
// Transaction support
await db.transaction(async (dbTxn) => {
// Perform database operations
})
DiskBlobStore
Disk-based blob storage implementation.
import { DiskBlobStore } from '@atproto/pds'
const blobstore = new DiskBlobStore({
location: '/path/to/blobs',
tempLocation: '/path/to/tmp',
})
Configuration
ServerConfig
Complete server configuration interface.
interface ServerConfig {
service: ServiceConfig
db: DatabaseConfig
actorStore: ActorStoreConfig
blobstore: S3BlobstoreConfig | DiskBlobstoreConfig
identity: IdentityConfig
entryway: EntrywayConfig | null
invites: InvitesConfig
email: EmailConfig | null
moderationEmail: EmailConfig | null
subscription: SubscriptionConfig
bskyAppView: BksyAppViewConfig | null
modService: ModServiceConfig | null
reportService: ReportServiceConfig | null
redis: RedisScratchConfig | null
rateLimits: RateLimitsConfig
crawlers: string[]
fetch: FetchConfig
proxy: ProxyConfig
oauth: OAuthConfig
lexicon: LexiconResolverConfig
}
ServiceConfig
Core service configuration.
interface ServiceConfig {
port: number // Server port (default: 2583)
hostname: string // Public hostname
publicUrl: string // Full public URL
did: string // Service DID
version?: string // Service version
privacyPolicyUrl?: string // Privacy policy URL
termsOfServiceUrl?: string // Terms of service URL
acceptingImports: boolean // Accept repo imports
maxImportSize?: number // Max import size in bytes
blobUploadLimit: number // Max blob upload size
contactEmailAddress?: string // Contact email
devMode: boolean // Development mode
}
DatabaseConfig
SQLite database configuration.
interface DatabaseConfig {
accountDbLoc: string // Account database path
sequencerDbLoc: string // Sequencer database path
didCacheDbLoc: string // DID cache database path
disableWalAutoCheckpoint: boolean // Disable WAL auto-checkpoint
}
Blobstore Configuration
S3 Blobstore
interface S3BlobstoreConfig {
provider: 's3'
bucket: string
region?: string
endpoint?: string
forcePathStyle?: boolean
uploadTimeoutMs?: number
credentials?: {
accessKeyId: string
secretAccessKey: string
}
}
Disk Blobstore
interface DiskBlobstoreConfig {
provider: 'disk'
location: string // Blob storage directory
tempLocation?: string // Temporary upload directory
}
IdentityConfig
Identity resolution configuration.
interface IdentityConfig {
plcUrl: string // PLC directory URL
resolverTimeout: number // Resolution timeout (ms)
cacheStaleTTL: number // Cache stale TTL (ms)
cacheMaxTTL: number // Cache max TTL (ms)
recoveryDidKey: string | null // Recovery DID key
serviceHandleDomains: string[] // Allowed handle domains
handleBackupNameservers?: string[] // DNS nameservers
enableDidDocWithSession: boolean // Include DID doc in session
}
RateLimitsConfig
Rate limiting configuration.
type RateLimitsConfig =
| {
enabled: true
bypassKey?: string // Bypass header value
bypassIps?: string[] // Bypass IP addresses
}
| { enabled: false }
OAuthConfig
OAuth provider configuration.
interface OAuthConfig {
issuer: string // OAuth issuer URL
provider?: {
hcaptcha?: HcaptchaConfig
branding: BrandingInput
trustedClients?: string[]
}
}
Environment Variables
The PDS reads configuration from environment variables:
Service
PDS_PORT=2583
PDS_HOSTNAME=pds.example.com
PDS_SERVICE_DID=did:web:pds.example.com
PDS_DEV_MODE=false
PDS_BLOB_UPLOAD_LIMIT=5242880 # 5MB in bytes
Database
PDS_DATA_DIRECTORY=/pds/data
PDS_ACCOUNT_DB_LOCATION=/pds/data/account.sqlite
PDS_SEQUENCER_DB_LOCATION=/pds/data/sequencer.sqlite
PDS_DID_CACHE_DB_LOCATION=/pds/data/did_cache.sqlite
Blobstore (S3)
PDS_BLOBSTORE_S3_BUCKET=my-pds-blobs
PDS_BLOBSTORE_S3_REGION=us-west-2
PDS_BLOBSTORE_S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
PDS_BLOBSTORE_S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Blobstore (Disk)
PDS_BLOBSTORE_DISK_LOCATION=/pds/blobs
PDS_BLOBSTORE_DISK_TMP_LOCATION=/pds/tmp
Identity
PDS_DID_PLC_URL=https://plc.directory
PDS_SERVICE_HANDLE_DOMAINS=.pds.example.com
PDS_DID_CACHE_STALE_TTL=3600000 # 1 hour
PDS_DID_CACHE_MAX_TTL=86400000 # 1 day
Email
PDS_EMAIL_SMTP_URL=smtps://user:pass@smtp.example.com
PDS_EMAIL_FROM_ADDRESS=noreply@pds.example.com
Entryway (Optional)
PDS_ENTRYWAY_URL=https://entryway.example.com
PDS_ENTRYWAY_DID=did:web:entryway.example.com
PDS_ENTRYWAY_JWT_VERIFY_KEY_K256_PUBLIC_KEY_HEX=...
PDS_ENTRYWAY_PLC_ROTATION_KEY=...
Rate Limits
PDS_RATE_LIMITS_ENABLED=true
PDS_RATE_LIMIT_BYPASS_KEY=secret-bypass-key
PDS_RATE_LIMIT_BYPASS_IPS=127.0.0.1,10.0.0.0/8
Redis (Optional)
PDS_REDIS_SCRATCH_ADDRESS=redis://localhost:6379
PDS_REDIS_SCRATCH_PASSWORD=redis-password
Setup Example
Complete setup example:
import { PDS, envToCfg, readEnv } from '@atproto/pds'
import { Secp256k1Keypair } from '@atproto/crypto'
// Load configuration from environment
const env = readEnv()
const config = envToCfg(env)
// Generate or load signing key
const signingKey = await Secp256k1Keypair.create()
// Create secrets
const secrets = {
jwtSecret: process.env.PDS_JWT_SECRET,
plcRotationKey: process.env.PDS_PLC_ROTATION_KEY,
}
// Create and start PDS
const pds = await PDS.create(config, secrets)
const server = await pds.start()
console.log(`PDS listening on port ${config.service.port}`)
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('Shutting down PDS...')
await pds.destroy()
process.exit(0)
})
Repository Operations
The PDS manages user repositories:
import { prepareCreate, prepareUpdate, prepareDelete } from '@atproto/pds/repo'
// Prepare a record creation
const write = await prepareCreate({
did: 'did:plc:abc123',
collection: 'app.bsky.feed.post',
rkey: 'self',
record: {
text: 'Hello, AT Protocol!',
createdAt: new Date().toISOString(),
},
repo: actorStore.repo,
})
// Apply the write
await actorStore.applyWrites([write])
Sequencer
The sequencer handles event streaming:
import { Sequencer } from '@atproto/pds/sequencer'
// Access from context
const sequencer = pds.ctx.sequencer
// Start sequencing
await sequencer.start()
// Sequence an event
await sequencer.sequenceCommit(
did,
commitData,
ops
)
Authentication
The PDS includes OAuth provider and session management:
// Access account manager
const accountManager = pds.ctx.accountManager
// Create account
const account = await accountManager.createAccount({
email: 'user@example.com',
handle: 'user.pds.example.com',
password: 'secure-password',
inviteCode: 'optional-invite',
})
// Verify email
await accountManager.verifyEmail({
did: account.did,
token: emailToken,
})
Lexicon Server
Create and configure the XRPC server:
import { createLexiconServer } from '@atproto/pds'
const server = createLexiconServer({
validateResponse: false,
payload: {
jsonLimit: 150 * 1024,
textLimit: 100 * 1024,
blobLimit: 5 * 1024 * 1024,
},
rateLimits: {
creator: (opts) => new MemoryRateLimiter(opts),
global: [
{
name: 'global-ip',
durationMs: 5 * 60 * 1000,
points: 3000,
},
],
},
})
Resources