Open-source alternative

AppFlowy

The open-source alternative to Notion

AGPL-3.0 Dart Difficulty 3/5 Health 85.0/100

AppFlowy is a free, self-hostable open-source alternative to Notion. Bring projects, wikis, and teams together with AI. AppFlowy is the AI collaborative workspace where you achieve more without losing control of your data. The leading open source Notion alternative. It is licensed under AGPL-3.0 and primarily written in Dart.

76,974
Stars
6,045
Forks
1,031
Open issues
85.0
Health score

Feature comparison

Notion vs AppFlowy

Feature Notion AppFlowy
Notes ✓ ✓
Databases ✓ ✓
Collaboration ✓ ✓
Templates ✓ ✓

Docker Compose blueprint

# Essential services for AppFlowy Cloud

services:
  nginx:
    restart: on-failure
    image: nginx
    ports:
      - ${NGINX_PORT:-80}:80   # Disable this if you are using TLS
      - ${NGINX_TLS_PORT:-443}:443
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl/certificate.crt:/etc/nginx/ssl/certificate.crt
      - ./nginx/ssl/private_key.key:/etc/nginx/ssl/private_key.key

  # You do not need this if you have configured to use your own s3 file storage
  minio:
    restart: on-failure
    image: minio/minio
    environment:
      - MINIO_BROWSER_REDIRECT_URL=${APPFLOWY_BASE_URL?:err}/minio
      - MINIO_ROOT_USER=${APPFLOWY_S3_ACCESS_KEY:-minioadmin}
      - MINIO_ROOT_PASSWORD=${APPFLOWY_S3_SECRET_KEY:-minioadmin}
    command: server /data --console-address ":9001"
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ]
      interval: 30s
      timeout: 20s
      retries: 3
    volumes:
      - minio_data:/data

  postgres:
    restart: on-failure
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_DB=${POSTGRES_DB:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
      - POSTGRES_HOST=${POSTGRES_HOST:-postgres}
      - PGPORT=${POSTGRES_PORT:-5432}
    command: [ "postgres", "-c", "port=${POSTGRES_PORT:-5432}" ]
    healthcheck:
      test: [ "CMD", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "${POSTGRES_DB}", "-p", "${POSTGRES_PORT:-5432}" ]
      interval: 5s
      timeout: 5s
      retries: 12
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    restart: on-failure
    image: redis

  gotrue:
    restart: on-failure
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: "curl --fail http://127.0.0.1:9999/health || exit 1"
      interval: 5s
      timeout: 5s
      retries: 12
      start_period: 40s
    image: appflowyinc/gotrue:${GOTRUE_VERSION:-latest}
    environment:
      # There are a lot of options to configure GoTrue. You can reference the example config:
      # https://github.com/supabase/auth/blob/master/example.env
      # The initial GoTrue Admin user to create, if not already exists.
      - GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL}
      # The initial GoTrue Admin user password to create, if not already exists.
      # If the user already exists, the update will be skipped.
      - GOTRUE_ADMIN_PASSWORD=${GOTRUE_ADMIN_PASSWORD}
      - GOTRUE_DISABLE_SIGNUP=${GOTRUE_DISABLE_SIGNUP:-false}
      - GOTRUE_SITE_URL=appflowy-flutter://                           # redirected to AppFlowy application
      - GOTRUE_URI_ALLOW_LIST=** # adjust restrict if necessary
      - GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET}                        # authentication secret
      - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP}
      # Without this environment variable, the createuser command will create an admin
      # with the `admin` role as opposed to `supabase_admin`
      - GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin
      - GOTRUE_DB_DRIVER=postgres
      - API_EXTERNAL_URL=${API_EXTERNAL_URL}
      - DATABASE_URL=${GOTRUE_DATABASE_URL}
      - PORT=9999
      - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST}                          # e.g. smtp.gmail.com
      - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT}                          # e.g. 465
      - GOTRUE_SMTP_USER=${GOTRUE_SMTP_USER}                          # email sender, e.g. noreply@appflowy.io
      - GOTRUE_SMTP_PASS=${GOTRUE_SMTP_PASS}                          # email password
      - GOTRUE_MAILER_URLPATHS_CONFIRMATION=/gotrue/verify
      - GOTRUE_MAILER_URLPATHS_INVITE=/gotrue/verify
      - GOTRUE_MAILER_URLPATHS_RECOVERY=/gotrue/verify
      - GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/gotrue/verify
      - GOTRUE_MAILER_TEMPLATES_MAGIC_LINK=${GOTRUE_MAILER_TEMPLATES_MAGIC_LINK}
      - GOTRUE_SMTP_ADMIN_EMAIL=${GOTRUE_SMTP_ADMIN_EMAIL}                # email with admin privileges e.g. internal@appflowy.io
      - GOTRUE_SMTP_MAX_FREQUENCY=${GOTRUE_SMTP_MAX_FREQUENCY:-1ns}       # set to 1ns for running tests
      - GOTRUE_RATE_LIMIT_EMAIL_SENT=${GOTRUE_RATE_LIMIT_EMAIL_SENT:-100} # number of email sendable per minute
      - GOTRUE_MAILER_AUTOCONFIRM=${GOTRUE_MAILER_AUTOCONFIRM:-false}     # change this to true to skip email confirmation
      # Google OAuth config
      - GOTRUE_EXTERNAL_GOOGLE_ENABLED=${GOTRUE_EXTERNAL_GOOGLE_ENABLED}
      - GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=${GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID}
      - GOTRUE_EXTERNAL_GOOGLE_SECRET=${GOTRUE_EXTERNAL_GOOGLE_SECRET}
      - GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=${GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI}
      # GITHUB OAuth config
      - GOTRUE_EXTERNAL_GITHUB_ENABLED=${GOTRUE_EXTERNAL_GITHUB_ENABLED}
      - GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=${GOTRUE_EXTERNAL_GITHUB_CLIENT_ID}
      - GOTRUE_EXTERNAL_GITHUB_SECRET=${GOTRUE_EXTERNAL_GITHUB_SECRET}
      - GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=${GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI}
      # Discord OAuth config
      - GOTRUE_EXTERNAL_DISCORD_ENABLED=${GOTRUE_EXTERNAL_DISCORD_ENABLED}
      - GOTRUE_EXTERNAL_DISCORD_CLIENT_ID=${GOTRUE_EXTERNAL_DISCORD_CLIENT_ID}
      - GOTRUE_EXTERNAL_DISCORD_SECRET=${GOTRUE_EXTERNAL_DISCORD_SECRET}
      - GOTRUE_EXTERNAL_DISCORD_REDIRECT_URI=${GOTRUE_EXTERNAL_DISCORD_REDIRECT_URI}
      # SAML 2.0 OAuth config
      - GOTRUE_SAML_ENABLED=${GOTRUE_SAML_ENABLED}
      - GOTRUE_SAML_PRIVATE_KEY=${GOTRUE_SAML_PRIVATE_KEY}

  appflowy_cloud:
    restart: on-failure
    environment:
      - RUST_LOG=${RUST_LOG:-info}
      - APPFLOWY_ENVIRONMENT=production
      - APPFLOWY_DATABASE_URL=${APPFLOWY_DATABASE_URL}
      - APPFLOWY_REDIS_URI=${APPFLOWY_REDIS_URI}
      - APPFLOWY_GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET}
      - APPFLOWY_GOTRUE_BASE_URL=${APPFLOWY_GOTRUE_BASE_URL}
      - APPFLOWY_S3_CREATE_BUCKET=${APPFLOWY_S3_CREATE_BUCKET}
      - APPFLOWY_S3_USE_MINIO=${APPFLOWY_S3_USE_MINIO}
      - APPFLOWY_S3_MINIO_URL=${APPFLOWY_S3_MINIO_URL}
      - APPFLOWY_S3_ACCESS_KEY=${APPFLOWY_S3_ACCESS_KEY}
      - APPFLOWY_S3_SECRET_KEY=${APPFLOWY_S3_SECRET_KEY}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION}
      - APPFLOWY_S3_PRESIGNED_URL_ENDPOINT=${APPFLOWY_S3_PRESIGNED_URL_ENDPOINT}
      - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST}
      - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT}
      - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME}
      - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL}
      - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD}
      - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND}
      - APPFLOWY_ACCESS_CONTROL=${APPFLOWY_ACCESS_CONTROL}
      - APPFLOWY_DATABASE_MAX_CONNECTIONS=${APPFLOWY_DATABASE_MAX_CONNECTIONS}
      - AI_SERVER_HOST=${AI_SERVER_HOST}
      - AI_SERVER_PORT=${AI_SERVER_PORT}
      - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY}
      - APPFLOWY_WEB_URL=${APPFLOWY_WEB_URL}
      - APPFLOWY_BASE_URL=${APPFLOWY_BASE_URL}
      - ASSEMBLYAI_API_KEY=${ASSEMBLYAI_API_KEY}
      - ASSEMBLYAI_API_BASE=${ASSEMBLYAI_API_BASE}
      - ASSEMBLYAI_STREAMING_API_BASE=${ASSEMBLYAI_STREAMING_API_BASE}
      - APPFLOWY_SEARCH_SERVICE_URL=${APPFLOWY_SEARCH_SERVICE_URL:-http://appflowy_search:4002}
      - APPFLOWY_SEARCH_REQUEST_TIMEOUT_SECS=${APPFLOWY_SEARCH_REQUEST_TIMEOUT_SECS:-10}
      - AI_ENABLED=${AI_ENABLED:-true}
      # SIGNUP_WHITELIST_ENABLED=true requires GOTRUE_DISABLE_SIGNUP=false on
      # the gotrue service. The whitelist is enforced by GoTrue's
      # BeforeUserCreated PG hook, which never runs when signup is globally
      # disabled — leaving the whitelist a no-op. Set GOTRUE_DISABLE_SIGNUP=false
      # and rely on the whitelist + invitations to gate who can sign up.
      - SIGNUP_WHITELIST_ENABLED=${SIGNUP_WHITELIST_ENABLED:-false}
      - GUEST_INVITES_REQUIRE_ADMIN_APPROVAL=${GUEST_INVITES_REQUIRE_ADMIN_APPROVAL:-false}
    image: appflowyinc/appflowy_cloud:${APPFLOWY_CLOUD_VERSION:-latest}
    healthcheck:
      test: "curl --fail http://127.0.0.1:8000/api/health || exit 1"
      interval: 5s
      timeout: 5s
      retries: 12
    depends_on:
      gotrue:
        condition: service_healthy

  admin_frontend:
    restart: on-failure
    image: appflowyinc/admin_frontend:${APPFLOWY_ADMIN_FRONTEND_VERSION:-latest}
    environment:
      - APPFLOWY_GOTRUE_BASE_URL=${APPFLOWY_GOTRUE_BASE_URL:-http://gotrue:9999}
      - APPFLOWY_BASE_URL=${APPFLOWY_BASE_URL:-http://appflowy_cloud:8000}

    depends_on:
      gotrue:
        condition: service_healthy
      appflowy_cloud:
        condition: service_healthy

  ai:
    restart: on-failure
    image: appflowyinc/appflowy_ai:${APPFLOWY_AI_VERSION:-latest}
    environment:
      - AI_SERVER_PORT=${AI_SERVER_PORT}
      - OPENAI_API_KEY=${AI_OPENAI_API_KEY}
      - DEFAULT_AI_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account
      - DEFAULT_AI_COMPLETION_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account
      - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
      - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
      - AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
      - APPFLOWY_S3_ACCESS_KEY=${APPFLOWY_S3_ACCESS_KEY}
      - APPFLOWY_S3_SECRET_KEY=${APPFLOWY_S3_SECRET_KEY}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION}
      - AI_DATABASE_URL=${APPFLOWY_DATABASE_URL}
      - AI_REDIS_URL=${APPFLOWY_REDIS_URI}
      - AI_USE_MINIO=${APPFLOWY_S3_USE_MINIO}
      - AI_MINIO_URL=${APPFLOWY_S3_MINIO_URL}
      - AI_APPFLOWY_HOST=${APPFLOWY_BASE_URL}
      - APPFLOWY_GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET}
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:5001/health" ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      postgres:
        condition: service_healthy
      appflowy_cloud:
        condition: service_healthy

  appflowy_worker:
    restart: on-failure
    image: appflowyinc/appflowy_worker:${APPFLOWY_WORKER_VERSION:-latest}
    environment:
      - RUST_LOG=${RUST_LOG:-info}
      - APPFLOWY_ENVIRONMENT=production
      - APPFLOWY_WORKER_REDIS_URL=${APPFLOWY_WORKER_REDIS_URL:-redis://redis:6379}
      - APPFLOWY_WORKER_ENVIRONMENT=production
      - APPFLOWY_WORKER_DATABASE_URL=${APPFLOWY_WORKER_DATABASE_URL}
      - APPFLOWY_WORKER_DATABASE_NAME=${APPFLOWY_WORKER_DATABASE_NAME}
      - APPFLOWY_WORKER_IMPORT_TICK_INTERVAL=30
      - APPFLOWY_S3_USE_MINIO=${APPFLOWY_S3_USE_MINIO}
      - APPFLOWY_S3_MINIO_URL=${APPFLOWY_S3_MINIO_URL}
      - APPFLOWY_S3_ACCESS_KEY=${APPFLOWY_S3_ACCESS_KEY}
      - APPFLOWY_S3_SECRET_KEY=${APPFLOWY_S3_SECRET_KEY}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION}
      - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST}
      - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT}
      - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME}
      - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL}
      - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD}
      - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND}
    depends_on:
      postgres:
        condition: service_healthy
      appflowy_cloud:
        condition: service_healthy

  appflowy_search:
    restart: on-failure
    image: appflowyinc/appflowy_search:${APPFLOWY_SEARCH_VERSION:-latest}
    environment:
      - RUST_LOG=${RUST_LOG:-info}
      - APPFLOWY_SEARCH_HOST=${APPFLOWY_SEARCH_HOST:-[::]}
      - APPFLOWY_SEARCH_PORT=${APPFLOWY_SEARCH_PORT:-4002}
      - APPFLOWY_SEARCH_DATABASE_URL=${APPFLOWY_DATABASE_URL}
      - APPFLOWY_SEARCH_REDIS_URL=${APPFLOWY_REDIS_URI}
      - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY}
      - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
      - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
      - AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION}
      # S3 / MinIO — required for /api/admin/search/.../rebuild to read collab
      # blobs offloaded by appflowy_cloud. Without these the service falls back
      # to MinIO defaults at localhost:9000 and the rebuild API returns
      # "collab store is not configured; cannot rebuild documents".
      - APPFLOWY_S3_USE_MINIO=${APPFLOWY_S3_USE_MINIO}
      - APPFLOWY_S3_MINIO_URL=${APPFLOWY_S3_MINIO_URL}
      - APPFLOWY_S3_ACCESS_KEY=${APPFLOWY_S3_ACCESS_KEY}
      - APPFLOWY_S3_SECRET_KEY=${APPFLOWY_S3_SECRET_KEY}
      - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET}
      - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION}
      - APPFLOWY_BACKGROUND_INDEXER_ENABLED=true
      - APPFLOWY_INDEXER_DATABASE_ENABLED=${APPFLOWY_INDEXER_DATABASE_ENABLED:-false}
      - APPFLOWY_KEYWORD_SEARCH_ENABLED=${APPFLOWY_KEYWORD_SEARCH_ENABLED:-true}
      - APPFLOWY_KEYWORD_WORKER_ENABLED=true
      - APPFLOWY_KEYWORD_INDEX_MAP_SIZE_BYTES=${APPFLOWY_KEYWORD_INDEX_MAP_SIZE_BYTES:-2147483648}
      - APPFLOWY_KEYWORD_INDEX_DIR=${APPFLOWY_KEYWORD_INDEX_DIR:-/var/lib/appflowy/keyword_index}
      - APPFLOWY_GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET}
    volumes:
      - keyword_index_data:/var/lib/appflowy/keyword_index
    healthcheck:
      # The runtime image does not ship curl or wget. Bash's /dev/tcp support
      # keeps this an HTTP-level check without adding an image dependency.
      test:
        - CMD
        - bash
        - -ec
        - |
          exec 3<>/dev/tcp/127.0.0.1/$${APPFLOWY_SEARCH_PORT:-4002}
          printf 'GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
          read -r _ status _ <&3
          [ "$$status" = 200 ]
      interval: 5s
      timeout: 5s
      retries: 12
      start_period: 40s
    depends_on:
      postgres:
        condition: service_healthy

  appflowy_web:
    restart: on-failure
    image: appflowyinc/appflowy_web:${APPFLOWY_WEB_VERSION:-latest}
    depends_on:
      appflowy_cloud:
        condition: service_healthy
    environment:
      - APPFLOWY_BASE_URL=${APPFLOWY_BASE_URL}
      - APPFLOWY_GOTRUE_BASE_URL=${APPFLOWY_BASE_URL}/gotrue
      - APPFLOWY_WS_BASE_URL=${APPFLOWY_WEBSOCKET_BASE_URL}
volumes:
  postgres_data:
  minio_data:
  keyword_index_data:

One-click deploy

Spin up AppFlowy on a managed platform.

Why choose AppFlowy over Notion?

AppFlowy is a community-driven open-source project that offers a self-hostable alternative to Notion. With a health score of 85.0/100, an AGPL-3.0 license, and full control over your data, it is built for teams that value privacy and ownership.

  • ✓ Self-hostable — run it on your own servers or cloud.
  • ✓ Open license — audit, fork, and customize freely.
  • ✓ No vendor lock-in — export and migrate your data.
  • ✓ Active community — issues and PRs on GitHub.

Self-hosting guide

  1. 1

    Clone or use the Docker blueprint

    Use the Compose file above, or clone the repo from GitHub and follow upstream docs.

  2. 2

    Configure environment variables

    Set database, secrets, and app URL. Never commit production secrets to git.

  3. 3

    Add reverse proxy & SSL

    Put Nginx or Caddy in front for HTTPS, rate limiting, and static caching.

  4. 4

    Backups & updates

    Schedule volume/database backups and watch GitHub releases for security patches.

Self-host difficulty for AppFlowy is rated 3/5.

Frequently asked questions

Yes. It is released under the AGPL-3.0 license and can be self-hosted without paying for the software itself.
Difficulty is rated 3/5. Basic Docker knowledge is recommended for a stable production setup.
AppFlowy is a self-hostable open-source alternative to Notion.
Visit the GitHub repository to open issues or submit pull requests.

We use cookies for essential site functions and, if enabled, advertising (e.g. Google AdSense). See our Privacy Policy.