Skip to main content

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:
  1. Environment Variables - Set in .env file or system environment
  2. 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:
.env
# 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=
config.json
{
  "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:
.env
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:
.env
REDIS_SERVER_HOST=redis
REDIS_SERVER_PORT=6379
REDIS_SERVER_DATABASE=0
REDIS_SERVER_USERNAME=
REDIS_SERVER_PASSWORD=
config.json
{
  "redis": {
    "host": "redis",
    "port": 6379,
    "db": 0,
    "username": "",
    "password": ""
  }
}

Authentication & Security

Private Key

AFFiNE uses a private key for signing tokens and encrypting data:
.env
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

config.json
{
  "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)

config.json
{
  "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

config.json
{
  "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

config.json
{
  "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:
.env
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
config.json
{
  "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:
config.json
{
  "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:
config.json
{
  "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

config.json
{
  "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

config.json
{
  "throttle": {
    "enabled": true,
    "throttlers": {
      "default": {
        "ttl": 60,
        "limit": 120
      },
      "strict": {
        "ttl": 60,
        "limit": 20
      }
    }
  }
}

Job Queues

config.json
{
  "job": {
    "queues": {
      "copilot": {
        "concurrency": 10
      },
      "doc": {
        "concurrency": 1
      },
      "indexer": {
        "concurrency": 1
      },
      "notification": {
        "concurrency": 10
      }
    }
  }
}

WebSocket Configuration

config.json
{
  "websocket": {
    "transports": ["websocket", "polling"],
    "maxHttpBufferSize": 100000000
  }
}

Metrics & Monitoring

config.json
{
  "metrics": {
    "enabled": true
  }
}
When enabled, metrics are exposed at /metrics endpoint in Prometheus format.

Document History

config.json
{
  "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

config.json
{
  "flags": {
    "allowGuestDemoWorkspace": true
  },
  "client": {
    "versionControl": {
      "enabled": false,
      "requiredVersion": ">=0.25.0"
    }
  }
}

Search & Indexing

Configure the search indexer:
.env
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:
  1. Environment variables: Restart the container
    docker compose restart affine
    
  2. JSON configuration: Some settings are hot-reloaded, but restart is recommended
    docker compose restart affine
    

Next Steps