Skip to main content

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.

Overview

The @atproto/identity package provides TypeScript utilities for resolving and managing decentralized identities in the AT Protocol. It supports DID resolution (did:plc and did:web) and handle resolution via DNS and HTTP methods.

Installation

npm install @atproto/identity

Core Concepts

The identity package handles two main types of identifiers:
  • DIDs (Decentralized Identifiers): Persistent, cryptographically-verifiable identifiers
  • Handles: Human-readable domain names that resolve to DIDs
This package provides resolvers for both identifier types and utilities for extracting AT Protocol-specific data from DID documents.

Main Exports

Classes

IdResolver

The main class that combines both DID and handle resolution.
import { IdResolver } from '@atproto/identity'

const resolver = new IdResolver({
  timeout: 3000,
  plcUrl: 'https://plc.directory',
  backupNameservers: ['8.8.8.8', '1.1.1.1']
})

// Resolve a handle to a DID
const did = await resolver.handle.resolve('alice.bsky.social')
// => 'did:plc:...'

// Resolve a DID to its document
const doc = await resolver.did.resolve(did)

// Get AT Protocol-specific data
const data = await resolver.did.resolveAtprotoData(did)
console.log(data.handle, data.pds, data.signingKey)
options
IdentityResolverOpts
Configuration options for the resolver
Properties:
  • handle: HandleResolver - Handle resolver instance
  • did: DidResolver - DID resolver instance

DidResolver

Resolves DIDs to their DID documents. Supports did:plc and did:web methods.
import { DidResolver } from '@atproto/identity'

const resolver = new DidResolver({
  timeout: 3000,
  plcUrl: 'https://plc.directory'
})

const doc = await resolver.resolve('did:plc:ewvi7nxzyoun6zhxrhs64oiz')
options
DidResolverOpts
Configuration options
Methods:
resolve
Promise<DidDocument>
async resolve(did: string, forceRefresh?: boolean): Promise<DidDocument>
Resolves a DID to its document. Uses cache unless forceRefresh is true.
did
string
required
The DID to resolve
forceRefresh
boolean
default:"false"
Skip cache and force fresh resolution
resolveAtprotoData
Promise<AtprotoData>
async resolveAtprotoData(did: string, forceRefresh?: boolean): Promise<AtprotoData>
Resolves a DID and extracts AT Protocol-specific data.Returns an object with:
  • did: string - The DID
  • signingKey: string - The signing key in did:key format
  • handle: string - The associated handle
  • pds: string - The PDS endpoint URL

HandleResolver

Resolves handles to DIDs using DNS TXT records and HTTP well-known endpoints.
import { HandleResolver } from '@atproto/identity'

const resolver = new HandleResolver({
  timeout: 3000,
  backupNameservers: ['8.8.8.8']
})

const did = await resolver.resolve('alice.bsky.social')
// => 'did:plc:...'
options
HandleResolverOpts
Configuration options
Methods:
resolve
Promise<string | undefined>
async resolve(handle: string): Promise<string | undefined>
Resolves a handle to a DID. Tries DNS first, then HTTP, then backup DNS.
handle
string
required
The handle to resolve (e.g., ‘alice.bsky.social’)
resolveDns
Promise<string | undefined>
async resolveDns(handle: string): Promise<string | undefined>
Resolves via DNS TXT record at _atproto.{handle}
resolveHttp
Promise<string | undefined>
async resolveHttp(handle: string, signal?: AbortSignal): Promise<string | undefined>
Resolves via HTTPS at https://{handle}/.well-known/atproto-did

Utility Functions

DID Document Parsing

Extract AT Protocol-specific data from DID documents.
import { 
  parseToAtprotoDocument, 
  ensureAtpDocument,
  getKey,
  getHandle,
  getPds
} from '@atproto/identity'

const doc = await resolver.did.resolve('did:plc:...')

// Parse with partial data (may have missing fields)
const partial = parseToAtprotoDocument(doc)
if (partial.handle && partial.pds) {
  console.log(`Handle: ${partial.handle}, PDS: ${partial.pds}`)
}

// Ensure all required fields are present
try {
  const complete = ensureAtpDocument(doc)
  console.log(complete.did, complete.handle, complete.pds, complete.signingKey)
} catch (err) {
  console.error('Missing required fields')
}

// Extract individual fields
const signingKey = getKey(doc)
const handle = getHandle(doc)
const pdsUrl = getPds(doc)
parseToAtprotoDocument
Partial<AtprotoData>
function parseToAtprotoDocument(doc: DidDocument): Partial<AtprotoData>
Extracts AT Protocol data from a DID document. Returns partial data - some fields may be undefined.
ensureAtpDocument
AtprotoData
function ensureAtpDocument(doc: DidDocument): AtprotoData
Ensures all required AT Protocol fields are present. Throws if any are missing.
getKey
string | undefined
function getKey(doc: DidDocument): string | undefined
Extracts the signing key in did:key format.
getHandle
string | undefined
function getHandle(doc: DidDocument): string | undefined
Extracts the handle from the DID document.
getPds
string | undefined
function getPds(doc: DidDocument): string | undefined
Extracts the PDS endpoint URL.

Types

AtprotoData

type AtprotoData = {
  did: string
  signingKey: string
  handle: string
  pds: string
}
Complete AT Protocol-specific data extracted from a DID document.

DidDocument

W3C DID Document structure. Re-exported from @atproto/common-web.

DidCache

Interface for implementing custom DID document caching.
interface DidCache {
  cacheDid(
    did: string,
    doc: DidDocument,
    prevResult?: CacheResult
  ): Promise<void>
  
  checkCache(did: string): Promise<CacheResult | null>
  
  refreshCache(
    did: string,
    getDoc: () => Promise<DidDocument | null>,
    prevResult?: CacheResult
  ): Promise<void>
  
  clearEntry(did: string): Promise<void>
  clear(): Promise<void>
}

Errors

The package exports several error classes:
import {
  DidNotFoundError,
  PoorlyFormattedDidError,
  UnsupportedDidMethodError,
  PoorlyFormattedDidDocumentError,
  UnsupportedDidWebPathError
} from '@atproto/identity'
  • DidNotFoundError - DID could not be resolved
  • PoorlyFormattedDidError - DID syntax is invalid
  • UnsupportedDidMethodError - DID method is not supported (only did:plc and did:web are supported)
  • PoorlyFormattedDidDocumentError - DID document structure is invalid
  • UnsupportedDidWebPathError - did:web path is not supported

Complete Example

import { IdResolver } from '@atproto/identity'

const resolver = new IdResolver({
  timeout: 3000,
  plcUrl: 'https://plc.directory',
  backupNameservers: ['8.8.8.8']
})

// Resolve handle to DID
const handle = 'alice.bsky.social'
const did = await resolver.handle.resolve(handle)

if (!did) {
  throw new Error('Handle resolution failed')
}

console.log(`Resolved ${handle} to ${did}`)

// Get full DID document
const doc = await resolver.did.resolve(did)

// Extract AT Protocol data
const data = await resolver.did.resolveAtprotoData(did)

// Verify handle matches
if (data.handle !== handle) {
  throw new Error('Handle mismatch - DID document does not match')
}

console.log('Identity verified:', {
  did: data.did,
  handle: data.handle,
  pds: data.pds,
  signingKey: data.signingKey
})

// Subsequent resolutions use cache
const cachedDoc = await resolver.did.resolve(did) // Fast, from cache

// Force fresh resolution
const freshDoc = await resolver.did.resolve(did, true) // Bypasses cache

Additional Resources

Build docs developers (and LLMs) love