Skip to main content
This guide walks you through setting up a complete development environment for building AT Protocol applications, from initial installation to running tests.

Prerequisites

1

Node.js

Install Node.js 18 or later. We recommend using nvm for managing Node.js versions.
# Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# Install Node.js 18
nvm install 18
nvm use 18
2

Package manager

Choose your preferred package manager:
# npm comes with Node.js
npm --version
3

Git

Ensure Git is installed:
git --version

Project Setup

Creating a New Project

1

Initialize your project

mkdir my-atproto-app
cd my-atproto-app
npm init -y
2

Install dependencies

npm install @atproto/api
npm install --save-dev typescript @types/node
3

Configure TypeScript

Create a tsconfig.json file:
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2021",
    "module": "commonjs",
    "lib": ["ES2021"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
4

Add scripts to package.json

package.json
{
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "start": "node dist/index.js",
    "test": "jest"
  }
}

Directory Structure

Organize your project:
my-atproto-app/
├── src/
│   ├── index.ts          # Entry point
│   ├── auth/
│   │   └── oauth.ts      # OAuth configuration
│   ├── api/
│   │   └── client.ts     # API client setup
│   └── utils/
│       └── richtext.ts   # Rich text helpers
├── tests/
│   └── api.test.ts
├── .env                  # Environment variables (don't commit!)
├── .gitignore
├── package.json
└── tsconfig.json

Environment Configuration

Create .env File

.env
# AT Protocol Configuration
ATPROTO_SERVICE=https://bsky.social
ATPROTO_IDENTIFIER=your.handle.bsky.social
ATPROTO_PASSWORD=your-app-password

# OAuth Configuration (for production)
OAUTH_CLIENT_ID=https://your-app.com/client-metadata.json
OAUTH_REDIRECT_URI=https://your-app.com/callback

# Development
NODE_ENV=development
PORT=3000

Add .gitignore

.gitignore
# Dependencies
node_modules/

# Build output
dist/
*.js
*.js.map
*.d.ts

# Environment
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs/
*.log
npm-debug.log*

# Testing
coverage/
.nyc_output/

Load Environment Variables

Install and configure dotenv:
npm install dotenv
src/index.ts
import dotenv from 'dotenv'

// Load environment variables
dotenv.config()

// Validate required variables
if (!process.env.ATPROTO_SERVICE) {
  throw new Error('ATPROTO_SERVICE is required')
}

Basic Application Setup

Create API Client

src/api/client.ts
import { Agent, CredentialSession } from '@atproto/api'

export async function createAgent(): Promise<Agent> {
  const serviceUrl = process.env.ATPROTO_SERVICE!
  const identifier = process.env.ATPROTO_IDENTIFIER!
  const password = process.env.ATPROTO_PASSWORD!

  const session = new CredentialSession(new URL(serviceUrl))
  await session.login({ identifier, password })

  return new Agent(session)
}

Main Application Entry

src/index.ts
import dotenv from 'dotenv'
import { createAgent } from './api/client'

dotenv.config()

async function main() {
  try {
    const agent = await createAgent()
    
    console.log('Connected as:', agent.accountDid)
    
    // Your application logic here
    const profile = await agent.getProfile({ actor: agent.accountDid })
    console.log('Profile:', profile.data.displayName)
    
  } catch (error) {
    console.error('Error:', error)
    process.exit(1)
  }
}

main()

Testing Setup

Install Testing Dependencies

npm install --save-dev jest @types/jest ts-jest

Configure Jest

jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/tests'],
  testMatch: ['**/*.test.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts'
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
}

Write Tests

tests/api.test.ts
import { Agent } from '@atproto/api'
import { createAgent } from '../src/api/client'

describe('API Client', () => {
  let agent: Agent

  beforeAll(async () => {
    agent = await createAgent()
  })

  test('should authenticate successfully', () => {
    expect(agent.did).toBeDefined()
    expect(agent.did).toMatch(/^did:/)
  })

  test('should fetch user profile', async () => {
    const { data } = await agent.getProfile({ actor: agent.accountDid })
    
    expect(data).toBeDefined()
    expect(data.did).toBe(agent.accountDid)
  })

  test('should create and delete a post', async () => {
    // Create post
    const { uri } = await agent.post({
      text: 'Test post from automated tests',
      createdAt: new Date().toISOString()
    })

    expect(uri).toBeDefined()

    // Delete post
    await agent.deletePost(uri)

    // Verify deletion
    await expect(
      agent.getPost({ uri })
    ).rejects.toThrow()
  })
})

Development Workflow

Running in Development

# Build and run
npm run build
npm start

# Watch mode
npm run dev

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm test -- --watch

# Run tests with coverage
npm test -- --coverage

Web Application Setup

For building web applications:

Next.js Setup

npx create-next-app@latest my-atproto-app --typescript
cd my-atproto-app
npm install @atproto/api @atproto/oauth-client-browser

Vite Setup

npm create vite@latest my-atproto-app -- --template react-ts
cd my-atproto-app
npm install
npm install @atproto/api @atproto/oauth-client-browser

Debugging

VS Code Configuration

Create .vscode/launch.json:
.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Application",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/src/index.ts",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "envFile": "${workspaceFolder}/.env"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "envFile": "${workspaceFolder}/.env"
    }
  ]
}

Enable Debug Logging

src/index.ts
// Enable verbose logging
if (process.env.NODE_ENV === 'development') {
  process.env.DEBUG = '@atproto/*'
}

Linting and Formatting

ESLint Setup

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npx eslint --init
.eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  parserOptions: {
    ecmaVersion: 2021,
    sourceType: 'module'
  },
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn'
  }
}

Prettier Setup

npm install --save-dev prettier
.prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}
Add scripts to package.json:
{
  "scripts": {
    "lint": "eslint src/**/*.ts",
    "format": "prettier --write src/**/*.ts"
  }
}

Common Development Tasks

Creating App Passwords

For development, create an app password at:
Never commit app passwords to version control. Always use environment variables.

Testing with Local PDS

Run a local PDS for development:
# Clone the atproto repository
git clone https://github.com/bluesky-social/atproto.git
cd atproto

# Install dependencies
pnpm install

# Run dev environment
make run-dev-env
Update your .env:
ATPROTO_SERVICE=http://localhost:2583

Best Practices

1

Use TypeScript

TypeScript provides type safety and better IDE support.
2

Version control your code

Always use Git and commit regularly.
git init
git add .
git commit -m "Initial commit"
3

Never commit secrets

Use .gitignore to exclude .env files and other sensitive data.
4

Write tests

Test your code to catch bugs early and ensure reliability.
5

Use environment-specific configs

Create separate configs for development, staging, and production.
.env.development
.env.production

Troubleshooting

Common Issues

Ensure dependencies are installed:
rm -rf node_modules package-lock.json
npm install
Check your tsconfig.json and ensure all dependencies are properly typed:
npm install --save-dev @types/node
Verify your credentials in .env:
  • Check that your handle is correct
  • Ensure you’re using an app password, not your main password
  • Verify the service URL is correct
For browser applications, ensure you’re using OAuth, not credential-based auth.

Next Steps

Using the API

Learn how to use the Agent API

OAuth Authentication

Set up OAuth for production

Rich Text

Work with mentions and links

Quickstart

Build your first app quickly

Build docs developers (and LLMs) love