Skip to main content

Best Open Source Alternatives to Notion 2026

·OSSAlt Team
notionappflowyaffineoutlineself-hostingdocker2026

TL;DR

Notion charges $10/user/month. The best open source alternatives: AppFlowy (AGPL 3.0, ~60K GitHub stars, Rust/Flutter) for a full Notion-like experience with databases, kanban, and docs; AFFiNE (MIT, ~45K stars, TypeScript) for a whiteboard+doc hybrid; and Outline (BSL, ~28K stars, TypeScript/React) for team wikis. All self-host via Docker and store your data on your server.

Key Takeaways

  • AppFlowy: AGPL 3.0, ~60K stars — Rust backend + Flutter frontend, databases, kanban, docs, grids
  • AFFiNE: MIT, ~45K stars — doc + whiteboard + database in one, local-first with optional sync server
  • Outline: BSL, ~28K stars — focused team wiki/knowledge base, excellent Slack/Notion import
  • SiYuan: AGPL 3.0, ~30K stars — local-first block editor, full offline, strong search
  • Logseq: AGPL 3.0, ~33K stars — outliner/graph notes, plain Markdown files, no database backend
  • Pick by use case: Team wiki → Outline; Full Notion replacement → AppFlowy; Whiteboard hybrid → AFFiNE

Comparison Table

FeatureAppFlowyAFFiNEOutlineSiYuan
LicenseAGPL 3.0MITBSL 1.1AGPL 3.0
GitHub Stars~60K~45K~28K~30K
BackendRustNode.jsNode.jsGo
FrontendFlutterTypeScriptReactTypeScript
Databases/gridsYesYesNoYes
Kanban boardsYesYesNoYes
WhiteboardNoYesNoNo
Team wikiYesYesYesNo
Notion importYesYesYesNo
Local-first optionYesYesNoYes
Self-host DockerYesYesYesYes
Mobile appsYesYesLimitedYes
Real-time collabYesYesYesNo

Option 1: AppFlowy

AppFlowy is the closest 1:1 Notion replacement. Databases with multiple views (grid, board, calendar, gallery), rich text docs, nested pages — all in a Flutter desktop/mobile app or web.

# docker-compose.yml
services:
  appflowy_cloud:
    image: appflowyinc/appflowy_cloud:latest
    container_name: appflowy_cloud
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      RUST_LOG: info
      APPFLOWY_DATABASE_URL: "postgres://appflowy:${POSTGRES_PASSWORD}@postgres:5432/appflowy"
      APPFLOWY_REDIS_URI: "redis://redis:6379"
      APPFLOWY_GOTRUE_JWT_SECRET: "${JWT_SECRET}"
      APPFLOWY_GOTRUE_JWT_EXP: 7200
      APPFLOWY_GOTRUE_BASE_URL: "http://gotrue:9999"
      APPFLOWY_GOTRUE_EXT_URL: "https://appflowy.yourdomain.com"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  gotrue:
    image: appflowyinc/gotrue:latest
    container_name: appflowy_gotrue
    restart: unless-stopped
    environment:
      GOTRUE_SITE_URL: "https://appflowy.yourdomain.com"
      GOTRUE_JWT_SECRET: "${JWT_SECRET}"
      GOTRUE_DB_DRIVER: "postgres"
      DATABASE_URL: "postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@postgres:5432/supabase_auth"
      GOTRUE_SMTP_HOST: "smtp.yourdomain.com"
      GOTRUE_SMTP_PORT: 587
      GOTRUE_SMTP_USER: "${SMTP_USER}"
      GOTRUE_SMTP_PASS: "${SMTP_PASS}"
      GOTRUE_MAILER_AUTOCONFIRM: "false"
      API_EXTERNAL_URL: "https://appflowy.yourdomain.com"
    depends_on:
      - postgres

  postgres:
    image: pgvector/pgvector:pg16
    container_name: appflowy_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: appflowy
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: appflowy
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appflowy"]
      interval: 10s
      start_period: 20s

  redis:
    image: redis:7-alpine
    container_name: appflowy_redis
    restart: unless-stopped
    volumes:
      - redis_data:/data

  minio:
    image: minio/minio:latest
    container_name: appflowy_minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: "${MINIO_USER}"
      MINIO_ROOT_PASSWORD: "${MINIO_PASSWORD}"
    volumes:
      - minio_data:/data

volumes:
  postgres_data:
  redis_data:
  minio_data:
# .env
POSTGRES_PASSWORD=your-secure-db-password
JWT_SECRET=your-32-char-secret
SMTP_USER=you@yourdomain.com
SMTP_PASS=smtp-password
MINIO_USER=appflowy
MINIO_PASSWORD=your-minio-password

docker compose up -d

AppFlowy Caddy Config

appflowy.yourdomain.com {
    reverse_proxy localhost:8000
}

Connect AppFlowy Desktop

  1. Download AppFlowy Desktop
  2. Click Sign InSelf-hosted server
  3. Enter: https://appflowy.yourdomain.com
  4. Create account / sign in

AppFlowy Database Views

AppFlowy databases support multiple views of the same data:

  • Grid: Spreadsheet-style with filtered/sorted rows
  • Board: Kanban grouped by any property
  • Calendar: Date-based view
  • Gallery: Card layout for visual content
Database: Project Tasks
├── Grid view: All tasks with status, priority, assignee
├── Board view: Kanban grouped by "Status" (Todo/In Progress/Done)
└── Calendar view: Grouped by "Due Date"

Option 2: AFFiNE

AFFiNE combines a block-based doc editor with an infinite whiteboard — useful for design thinking, architecture diagrams, and visual note-taking alongside written docs.

# docker-compose.yml
services:
  affine:
    image: ghcr.io/toeverything/affine-graphql:stable
    container_name: affine
    restart: unless-stopped
    ports:
      - "3010:3010"
    volumes:
      - affine_config:/root/.affine/config
      - affine_storage:/root/.affine/storage
    environment:
      NODE_OPTIONS: "--import=./scripts/register.js"
      AFFINE_CONFIG_PATH: "/root/.affine/config"
      REDIS_SERVER_HOST: redis
      DATABASE_URL: "postgresql://affine:${POSTGRES_PASSWORD}@postgres:5432/affine"
      NEXTAUTH_URL: "https://affine.yourdomain.com"
      AFFINE_SERVER_HTTPS: "true"
      AFFINE_SERVER_HOST: "affine.yourdomain.com"
      AFFINE_SERVER_PORT: "3010"
      AFFINE_ADMIN_EMAIL: "${ADMIN_EMAIL}"
      AFFINE_ADMIN_PASSWORD: "${ADMIN_PASSWORD}"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  redis:
    image: redis:7-alpine
    container_name: affine_redis
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    container_name: affine_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: affine
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: affine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U affine"]
      interval: 10s
      start_period: 30s

volumes:
  affine_config:
  affine_storage:
  postgres_data:
affine.yourdomain.com {
    reverse_proxy localhost:3010
}

AFFiNE Local-First Mode

AFFiNE also works entirely offline — no server needed:

  1. Download AFFiNE Desktop
  2. Data stored in ~/.affine/ as SQLite
  3. Self-host only needed for team sync

Option 3: Outline (Team Wiki)

Outline is purpose-built for team knowledge bases — clean editor, nested collections, Slack integration, full-text search. Less of a database tool, more of a clean wiki.

# docker-compose.yml
services:
  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      SECRET_KEY: "${SECRET_KEY}"
      UTILS_SECRET: "${UTILS_SECRET}"
      DATABASE_URL: "postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline"
      REDIS_URL: "redis://redis:6379"
      URL: "https://wiki.yourdomain.com"
      PORT: 3000
      # Auth (choose one):
      OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}"
      OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}"
      OIDC_AUTH_URI: "${OIDC_AUTH_URI}"
      OIDC_TOKEN_URI: "${OIDC_TOKEN_URI}"
      OIDC_USERINFO_URI: "${OIDC_USERINFO_URI}"
      # Or Google auth:
      # GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID}"
      # GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET}"
      # Storage (S3 or local):
      FILE_STORAGE: "local"
      FILE_STORAGE_LOCAL_ROOT_DIR: "/var/lib/outline/data"
      # Email:
      SMTP_HOST: "smtp.yourdomain.com"
      SMTP_PORT: 587
      SMTP_USERNAME: "${SMTP_USER}"
      SMTP_PASSWORD: "${SMTP_PASS}"
      SMTP_FROM_EMAIL: "outline@yourdomain.com"
    volumes:
      - outline_data:/var/lib/outline/data
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: outline
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: outline
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U outline"]
      interval: 10s
      start_period: 20s

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  outline_data:
  postgres_data:
wiki.yourdomain.com {
    reverse_proxy localhost:3000
}

Outline Auth: Using Authentik (OIDC)

If you're running Authentik for SSO:

  1. Authentik Admin → Applications → Providers → Create → OAuth2/OpenID Provider
  2. Name: Outline, Redirect URI: https://wiki.yourdomain.com/auth/oidc.callback
  3. Copy Client ID, Secret, and OIDC endpoints into Outline env vars
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_AUTH_URI=https://auth.yourdomain.com/application/o/outline/authorize/
OIDC_TOKEN_URI=https://auth.yourdomain.com/application/o/outline/token/
OIDC_USERINFO_URI=https://auth.yourdomain.com/application/o/outline/userinfo/
OIDC_DISPLAY_NAME="Your SSO"

Import from Notion

Outline has a Notion importer:

  1. In Notion: Settings → Export → Markdown & CSV (export all workspace)
  2. In Outline: Settings → Import → Notion → upload the ZIP

Option 4: SiYuan (Local-First, Offline)

For individuals who want everything local with optional sync:

services:
  siyuan:
    image: b3log/siyuan:latest
    container_name: siyuan
    restart: unless-stopped
    ports:
      - "6806:6806"
    volumes:
      - siyuan_workspace:/siyuan/workspace
    environment:
      TZ: America/Los_Angeles
    command: ["--workspace=/siyuan/workspace", "--accessAuthCode=${ACCESS_CODE}"]

volumes:
  siyuan_workspace:

Visit http://your-server:6806 with your access code.


Importing from Notion

All four tools import Notion exports:

# 1. Export from Notion:
# Settings → Export → Markdown & CSV → Export All

# 2. For AppFlowy:
# Settings → Import → Notion → upload .zip

# 3. For AFFiNE:
# New workspace → Import → Notion

# 4. For Outline:
# Settings → Import → Notion

Decision Guide

Choose AppFlowy if:

  • You need a full Notion replacement (databases + docs + kanban + calendar views)
  • Your team is 1-50 people
  • You want a native desktop app (Flutter-based, fast)

Choose AFFiNE if:

  • You need a whiteboard + doc hybrid
  • You like local-first with optional sync
  • Design thinking and visual collaboration matter

Choose Outline if:

  • Primary use case is team knowledge base / wiki
  • You want the cleanest writing experience
  • Slack integration is important

Choose SiYuan if:

  • You want everything stored locally (single user or small team)
  • Offline-first is mandatory
  • You don't want a cloud sync component at all

Maintenance

# Update AppFlowy:
docker compose pull
docker compose up -d

# Backup AppFlowy PostgreSQL:
docker exec appflowy_postgres pg_dump -U appflowy appflowy \
  | gzip > appflowy-backup-$(date +%Y%m%d).sql.gz

# Backup AFFiNE:
docker exec affine_postgres pg_dump -U affine affine \
  | gzip > affine-backup-$(date +%Y%m%d).sql.gz
tar -czf affine-storage-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect affine_affine_storage --format '{{.Mountpoint}}')

# Backup Outline:
docker exec outline-postgres-1 pg_dump -U outline outline \
  | gzip > outline-backup-$(date +%Y%m%d).sql.gz

See all open source productivity and collaboration tools at OSSAlt.com/categories/productivity.

Comments