Configuration Reference
AFFiNE can be configured through environment variables and a JSON configuration file. This guide covers all available configuration options.
Configuration Methods
AFFiNE supports two configuration methods:
- Environment Variables - Set in
.env file or system environment
- JSON Configuration - Advanced settings in
config.json
Environment variables take precedence over JSON configuration. Use environment variables for sensitive data like API keys and passwords.
Core Server Configuration
Server Settings
Control how the AFFiNE server listens and responds:
# External URL for generating links
AFFINE_SERVER_EXTERNAL_URL=https://affine.yourdomain.com
# Or configure separately:
AFFINE_SERVER_HTTPS=true
AFFINE_SERVER_HOST=affine.yourdomain.com
AFFINE_SERVER_PORT=3010
# Listening address (default: 0.0.0.0)
LISTEN_ADDR=0.0.0.0
# Subpath deployment (e.g., /affine)
AFFINE_SERVER_SUB_PATH=
{
"server": {
"name": "My AFFiNE Server",
"externalUrl": "https://affine.yourdomain.com",
"https": true,
"host": "affine.yourdomain.com",
"port": 3010,
"listenAddr": "0.0.0.0",
"path": ""
}
}
name: Server name shown in AFFiNE Desktop client
externalUrl: Full external URL (overrides host/https)
hosts: Array of additional accepted hostnames
Database Configuration
PostgreSQL
Configure the PostgreSQL database connection:
DATABASE_URL=postgresql://affine:password@postgres:5432/affine
The URL format is:
postgresql://[username]:[password]@[host]:[port]/[database]
The database must have the pgvector extension installed. The official Docker image includes this automatically.
Redis
Configure Redis for caching and sessions:
REDIS_SERVER_HOST=redis
REDIS_SERVER_PORT=6379
REDIS_SERVER_DATABASE=0
REDIS_SERVER_USERNAME=
REDIS_SERVER_PASSWORD=
{
"redis": {
"host": "redis",
"port": 6379,
"db": 0,
"username": "",
"password": ""
}
}
Authentication & Security
Private Key
AFFiNE uses a private key for signing tokens and encrypting data:
AFFINE_PRIVATE_KEY=your_private_key_here
If not provided, a key is generated automatically on first start. Make sure to backup the generated key from ~/.affine/config/private.key.
Authentication Settings
{
"auth": {
"allowSignup": true,
"allowSignupForOauth": true,
"requireEmailVerification": true,
"requireEmailDomainVerification": false,
"passwordRequirements": {
"min": 8,
"max": 32
},
"session": {
"ttl": 1296000,
"ttr": 604800
}
}
}
allowSignup: Allow new user registrations (default: true)
allowSignupForOauth: Allow signup via OAuth providers (default: true)
requireEmailVerification: Require email verification before access (default: true)
requireEmailDomainVerification: Verify email domain records (default: false)
passwordRequirements: Minimum and maximum password length
session.ttl: Session expiration in seconds (default: 15 days)
session.ttr: Session refresh time in seconds (default: 7 days)
OAuth Providers
Configure OAuth authentication providers:
{
"oauth": {
"providers": {
"google": {
"clientId": "your-google-client-id",
"clientSecret": "your-google-client-secret"
}
}
}
}
Storage Configuration
File System Storage (Default)
{
"storages": {
"avatar": {
"publicPath": "/api/avatars/",
"storage": {
"provider": "fs",
"bucket": "avatars",
"config": {
"path": "~/.affine/storage"
}
}
},
"blob": {
"storage": {
"provider": "fs",
"bucket": "blobs",
"config": {
"path": "~/.affine/storage"
}
}
}
}
}
AWS S3 Storage
{
"storages": {
"blob": {
"storage": {
"provider": "aws-s3",
"bucket": "my-affine-bucket",
"config": {
"endpoint": "https://s3.us-east-1.amazonaws.com",
"region": "us-east-1",
"forcePathStyle": false,
"credentials": {
"accessKeyId": "your-access-key",
"secretAccessKey": "your-secret-key"
}
}
}
}
}
}
Cloudflare R2 Storage
{
"storages": {
"blob": {
"storage": {
"provider": "cloudflare-r2",
"bucket": "my-affine-bucket",
"config": {
"endpoint": "https://account-id.r2.cloudflarestorage.com",
"region": "auto",
"accountId": "your-account-id",
"credentials": {
"accessKeyId": "your-r2-access-key",
"secretAccessKey": "your-r2-secret-key"
}
}
}
}
}
}
Both avatar and blob storage can use different providers. This allows you to separate user avatars from document attachments.
Email Configuration
Configure SMTP for sending emails:
MAILER_HOST=smtp.gmail.com
MAILER_PORT=465
MAILER_USER=noreply@example.com
MAILER_PASSWORD=your_app_password
MAILER_SENDER=AFFiNE <noreply@example.com>
MAILER_IGNORE_TLS=false
{
"mailer": {
"SMTP": {
"name": "mail.example.com",
"host": "smtp.gmail.com",
"port": 465,
"username": "noreply@example.com",
"password": "your_app_password",
"sender": "AFFiNE <noreply@example.com>",
"ignoreTLS": false
}
}
}
For Gmail, use an App Password.
Fallback SMTP
Configure a fallback SMTP server for specific domains:
{
"mailer": {
"fallbackDomains": ["example.com", "test.com"],
"fallbackSMTP": {
"host": "smtp.fallback.com",
"port": 587,
"username": "fallback@example.com",
"password": "fallback_password",
"sender": "AFFiNE Fallback <fallback@example.com>"
}
}
}
AI Features (Copilot)
Enable and configure AI features:
{
"copilot": {
"enabled": true,
"providers": {
"openai": {
"apiKey": "sk-...",
"baseURL": "https://api.openai.com/v1"
},
"anthropic": {
"apiKey": "sk-ant-...",
"baseURL": "https://api.anthropic.com/v1"
},
"gemini": {
"apiKey": "...",
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
}
}
}
}
AI features require API keys from the respective providers. These services may incur costs based on usage.
Custom Model Scenarios
{
"copilot": {
"scenarios": {
"override_enabled": true,
"scenarios": {
"chat": "gemini-2.5-flash",
"complex_text_generation": "gpt-5-mini",
"coding": "claude-sonnet-4-5@20250929",
"image": "gpt-image-1",
"embedding": "gemini-embedding-001"
}
}
}
}
Advanced Configuration
Rate Limiting
{
"throttle": {
"enabled": true,
"throttlers": {
"default": {
"ttl": 60,
"limit": 120
},
"strict": {
"ttl": 60,
"limit": 20
}
}
}
}
Job Queues
{
"job": {
"queues": {
"copilot": {
"concurrency": 10
},
"doc": {
"concurrency": 1
},
"indexer": {
"concurrency": 1
},
"notification": {
"concurrency": 10
}
}
}
}
WebSocket Configuration
{
"websocket": {
"transports": ["websocket", "polling"],
"maxHttpBufferSize": 100000000
}
}
Metrics & Monitoring
{
"metrics": {
"enabled": true
}
}
When enabled, metrics are exposed at /metrics endpoint in Prometheus format.
Document History
{
"doc": {
"history": {
"interval": 600000
},
"experimental": {
"yocto": false
}
}
}
history.interval: Minimum time in milliseconds between history snapshots (default: 10 minutes)
experimental.yocto: Use experimental y-octo merge algorithm
Feature Flags
{
"flags": {
"allowGuestDemoWorkspace": true
},
"client": {
"versionControl": {
"enabled": false,
"requiredVersion": ">=0.25.0"
}
}
}
Search & Indexing
Configure the search indexer:
AFFINE_INDEXER_ENABLED=true
AFFINE_INDEXER_SEARCH_PROVIDER=elasticsearch
AFFINE_INDEXER_SEARCH_ENDPOINT=http://elasticsearch:9200
AFFINE_INDEXER_SEARCH_USERNAME=elastic
AFFINE_INDEXER_SEARCH_PASSWORD=changeme
Indexer is disabled by default in self-hosted deployments. Enable it only if you need advanced search capabilities and have Elasticsearch configured.
Complete Configuration Example
Here’s a complete production configuration:
AFFINE_REVISION=stable
PORT=3010
AFFINE_SERVER_EXTERNAL_URL=https://affine.example.com
AFFINE_PRIVATE_KEY=your_generated_private_key
DATABASE_URL=postgresql://affine:secure_password@postgres:5432/affine
REDIS_SERVER_HOST=redis
REDIS_SERVER_PORT=6379
REDIS_SERVER_PASSWORD=redis_password
MAILER_HOST=smtp.gmail.com
MAILER_PORT=465
MAILER_USER=noreply@example.com
MAILER_PASSWORD=gmail_app_password
MAILER_SENDER=AFFiNE <noreply@example.com>
DB_DATA_LOCATION=/var/lib/affine/postgres
UPLOAD_LOCATION=/var/lib/affine/storage
CONFIG_LOCATION=/var/lib/affine/config
Validating Configuration
AFFiNE provides a JSON schema for validating your configuration:
# Download the schema
wget https://github.com/toeverything/affine/releases/latest/download/config.schema.json
# Validate using a JSON schema validator
npx ajv-cli validate -s config.schema.json -d config.json
Reloading Configuration
After changing configuration:
-
Environment variables: Restart the container
docker compose restart affine
-
JSON configuration: Some settings are hot-reloaded, but restart is recommended
docker compose restart affine
Next Steps