wekan Documentation

The Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at .

21,026 stars JavaScript JavaScript 1 file ยท ~9,561 tokens #docker-image#foss#javascript#kanban#meteor#real-time#sandstorm#snapcraft
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy wekan on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name wekan -p 27017:27017 --restart always ghcr.io/wekan/wekan:latest
Configured Services & Ports
โš™๏ธ ferretdb
image: debian:bookworm-slim
๐Ÿ”Œ Ports: 127.0.0.1:27017:27017
โš™๏ธ wekan
image: ghcr.io/wekan/wekan:latest
๐Ÿ”Œ Ports: 80:8080
docker-compose.yml
yaml (docker-compose.yml)
# This docker-compose.yml uses FerretDB v1 with SQLite from
# https://github.com/wekan/FerretDB by default.
#
# WeKan with FerretDB v1 + embedded SQLite (no PostgreSQL, no MongoDB)
#
# This is the DEFAULT WeKan compose file (docker-compose.yml): a light,
# self-contained database โ€” FerretDB v1 (the wekan/FerretDB fork,
# https://github.com/wekan/FerretDB) with its embedded pure-Go SQLite backend,
# which speaks the MongoDB wire protocol. There is no separate PostgreSQL or
# MongoDB server โ€” FerretDB stores everything in SQLite files on a volume, and
# WeKan stores attachments/avatars on the filesystem.
#
# The other WeKan compose files:
#   docker-compose.yml                           FerretDB v1 + SQLite (default)
#   docker-compose-ferretdb-v1-postgresql.yml    FerretDB v1 + PostgreSQL
#   docker-compose-ferretdb-v1-mysql.yml         FerretDB v1 + MySQL
#   docker-compose-ferretdb-v1-mariadb.yml       FerretDB v1 + MariaDB
#   docker-compose-ferretdb-v1-sap-hana.yml      FerretDB v1 + SAP HANA
#   docker-compose-ferretdb-v2-postgresql.yml    FerretDB 2 + PostgreSQL
#   docker-compose-mongodb-v7.yml                MongoDB 7
#   docker-compose-multitenancy.yml              MongoDB, multitenancy example
#
# The FerretDB binary is downloaded (once, then cached on the volume) from the
# newest wekan/FerretDB release: https://github.com/wekan/FerretDB/releases
# Each platform is a separate ferretdb- asset there; the container downloads
# only the one matching its architecture (amd64, arm64, ppc64le, s390x, riscv64, ...).
#
# Setup (this is the default docker-compose.yml, so no -f is needed):
#   docker compose up -d
#
# To follow the logs:
#   docker compose logs -f
#
# To stop:
#   docker compose down

services:

  # FerretDB v1 with the embedded SQLite backend (MongoDB-compatible on :27017).
  # A tiny Debian container fetches the wekan/FerretDB v1 release binary for its
  # architecture (cached on the volume and replaced when this required release
  # changes) and runs it.
  # SQLite data lives in /data/sqlite on the ferretdb-data volume.
  ferretdb:
    image: debian:bookworm-slim
    container_name: wekan-ferretdb
    restart: always
    environment:
      DO_NOT_TRACK: "1"
      FERRETDB_TELEMETRY: disable
      # Follow the newest published release. A specific download/vX.Y.Z remains
      # available for rollback; the marker below upgrades an older cached binary.
      FERRETDB_RELEASE: ${FERRETDB_RELEASE:-latest/download}
    command:
      - sh
      - -c
      - |
        set -eu
        BIN=/data/bin/ferretdb
        RELEASE_FILE=/data/bin/ferretdb.release
        if ! command -v curl >/dev/null 2>&1; then
          apt-get update -qq
          apt-get install -y -qq --no-install-recommends curl ca-certificates >/dev/null
        fi
        DOWNLOAD_RELEASE="$$FERRETDB_RELEASE"
        REQUIRED_RELEASE="$$FERRETDB_RELEASE"
        if [ "$$FERRETDB_RELEASE" = latest/download ]; then
          LATEST_URL="$$(curl -fsSIL --retry 5 -o /dev/null -w '%{url_effective}' \
            https://github.com/wekan/FerretDB/releases/latest)"
          REQUIRED_RELEASE="$${LATEST_URL##*/}"
          [ -n "$$REQUIRED_RELEASE" ]
          DOWNLOAD_RELEASE="download/$$REQUIRED_RELEASE"
        fi
        CACHED_RELEASE="$$(cat "$$RELEASE_FILE" 2>/dev/null || true)"
        if [ ! -x "$$BIN" ] || [ "$$CACHED_RELEASE" != "$$REQUIRED_RELEASE" ]; then
          echo "Fetching newest FerretDB v1 $${REQUIRED_RELEASE} from the wekan/FerretDB release ..."
          arch="$$(dpkg --print-architecture)"
          case "$$arch" in ppc64el) arch=ppc64le ;; esac
          mkdir -p /data/bin
          # Download only this arch's binary (per-arch release assets, no ferretdb.zip).
          curl -fSL --retry 5 -o "$$BIN.new" \
            "https://github.com/wekan/FerretDB/releases/$${DOWNLOAD_RELEASE}/ferretdb-$${arch}"
          curl -fSL --retry 5 -o "$$BIN.new.sha256sum" \
            "https://github.com/wekan/FerretDB/releases/$${DOWNLOAD_RELEASE}/ferretdb-$${arch}.sha256sum"
          EXPECTED="$$(awk '{print $$1; exit}' "$$BIN.new.sha256sum")"
          ACTUAL="$$(sha256sum "$$BIN.new" | awk '{print $$1}')"
          [ -n "$$EXPECTED" ] && [ "$$ACTUAL" = "$$EXPECTED" ]
          chmod +x "$$BIN.new"
          mv "$$BIN.new" "$$BIN"
          rm "$$BIN.new.sha256sum"
          printf '%s\n' "$$REQUIRED_RELEASE" > "$$RELEASE_FILE"
          chmod +x "$$BIN"
        fi
        mkdir -p /data/files/db
        # --repl-set-name only makes FerretDB keep local.oplog.rs, which Admin Panel /
        # Attachments / Continuous backup streams the database from (maintainer
        # decision of 2026-10-03). WeKan itself still polls: no MONGO_OPLOG_URL.
        exec "$$BIN" \
          --handler=sqlite \
          --sqlite-url=file:/data/files/db/ \
          --listen-addr=0.0.0.0:27017 \
          --repl-set-name=rs0 \
          --telemetry=disable \
          --log-level=error
    # Healthy only once FerretDB is actually accepting connections on 27017 โ€” i.e.
    # AFTER the first-run binary download (apt-get + curl) has finished and the server
    # is listening. WeKan's depends_on: condition: service_healthy waits for this, so it
    # never starts against a not-yet-ready database (which showed up as the browser
    # getting "Connection reset by peer" on a fresh `docker compose up`). bash + /dev/tcp
    # needs no extra tools in debian-slim. start_period covers a slow first download.
    healthcheck:
      test: ["CMD-SHELL", "bash -c '/dev/null"]
      interval: 5s
      timeout: 3s
      retries: 40
      start_period: 30s
    volumes:
      - ferretdb-data:/data
    networks:
      - wekan-tier
    # Expose only on localhost by default; remove to keep it internal to the network.
    ports:
      - 127.0.0.1:27017:27017

  wekan:
    #-------------------------------------------------------------------------------------
    # ==== WEKAN FROM GITHUB/QUAY/DOCKER HUB ====
    # All of GitHub, Quay and Docker Hub have latest, but because
    # latest tag changes when is newest release,
    # when upgrading would be better to use version tag.
    # a) Using specific version tag is better:
    # image: ghcr.io/wekan/wekan:v6.89
    # image: quay.io/wekan/wekan:v6.89
    # image: wekanteam/wekan:v6.89
    # b) GitHub Container registry.
    # b1) Latest release tag:
    image: ghcr.io/wekan/wekan:latest
    # b2) Newest git commit automatic build:
    #image: ghcr.io/wekan/wekan:main
    # c) Quay:
    #image: quay.io/wekan/wekan:latest
    # d) Docker Hub:
    #image: wekanteam/wekan:latest
    #-------------------------------------------------------------------------------------
    container_name: wekan-app
    # On CentOS 7 there is seccomp issue with glibc 6,
    # so CentOS 7 users shoud use these security_opt seccomp:unconfined
    # settings to get WeKan working. See:
    #   - https://github.com/wekan/wekan/issues/4585
    #   - https://github.com/wekan/wekan/issues/4587
    #security_opt:
    #  - seccomp:unconfined
    restart: always
    networks:
      - wekan-tier
    #-------------------------------------------------------------------------------------
    # ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
    # ==== and use commands: docker compose up -d --build
    #build:
    #  context: .
    #  dockerfile: Dockerfile
    #-------------------------------------------------------------------------------------
    ports:
      # Docker outsideport:insideport. Do not add anything extra here.
      # For example, if you want to have wekan on port 3001,
      # use 3001:8080 . Do not add any extra address etc here, that way it does not work.
      # remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network
      - 80:8080
    environment:
      # FerretDB v1 deployments are polling-only. Use docker-compose-mongodb-v7.yml
      # for MongoDB replica-set/change-stream reactivity.
      - DEFAULT_METEOR_REACTIVITY_ORDER=polling
      - METEOR_REACTIVITY_ORDER=polling
      # uws does not work at s390x. sockjs works.
      # sockjs is the only transport WeKan ships: no bundle or image carries
      # uWebSockets.js, and DDP_TRANSPORT=uws is coerced to sockjs at startup.
      - DDP_TRANSPORT=sockjs
      #-----------------------------------------------------------------
      # ==== WRITEABLE PATH FOR FILE UPLOADS ====
      - WRITABLE_PATH=/data
      #-----------------------------------------------------------------
      # ==== AWS S3 FOR FILES ====
      # Any region. For example:
      #   us-standard,us-west-1,us-west-2,
      #   eu-west-1,eu-central-1,
      #   ap-southeast-1,ap-northeast-1,sa-east-1
      #
      #- S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "region": "xxx"}}'
      #- S3_SECRET_KEY_FILE=/run/secrets/s3_secret_key
      #-----------------------------------------------------------------
      # ==== MONGO_URL โ€” points to FerretDB (MongoDB-compatible) ====
      #- MONGO_URL=mongodb://wekan-ferretdb:27017/wekan?directConnection=true
      # directConnection: FerretDB answers as a one-member replica set whose
      # member is its listen address 0.0.0.0, which is nowhere from this container.
      - MONGO_URL=mongodb://ferretdb:27017/wekan?directConnection=true
      #- MONGO_URL_FILE=/run/secrets/mongo_url
      #---------------------------------------------------------------
      # ==== ROOT_URL SETTING ====
      # Change ROOT_URL to your real Wekan URL, for example:
      # If you have Caddy/Nginx/Apache providing SSL
      #  - https://example.com
      #  - https://boards.example.com
      # This can be problematic with avatars https://github.com/wekan/wekan/issues/1776
      #  - https://example.com/wekan
      # If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them
      #  - http://example.com
      #  - http://boards.example.com
      #  - http://192.168.1.100    <=== using at local LAN
      - ROOT_URL=http://localhost  #   <=== using only at same laptop/desktop where Wekan is installed
      #---------------------------------------------------------------
      # ==== EMAIL SETTINGS ====
      # In Admin Panel / People / Email, enable "Enable below email settings"
      # to reveal additional email sending options below the checkbox.
      #   see https://github.com/wekan/wekan/blob/main/docs/Email/Troubleshooting-Mail.md
      #   For SSL in email, change smtp:// to smtps://
      # NOTE: Special characters need to be url-encoded in MAIL_URL.
      #       You can encode those characters for example at: https://www.urlencoder.org
      #- MAIL_URL=smtp://user:[email protected]:25/
      - MAIL_URL=smtp://:25/?ignoreTLS=true&tls={rejectUnauthorized:false}
      - MAIL_FROM=Wekan Notifications 
      # Currently MAIL_SERVICE is not in use.
      #- MAIL_SERVICE=Outlook365
      #- [email protected]
      #- MAIL_SERVICE_PASSWORD=SecretPassword
      #- MAIL_URL_FILE=/run/secrets/mail_url
      #---------------------------------------------------------------
      # https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
      # Add more Node heap, this is done by default at Dockerfile:
      #   - NODE_OPTIONS="--max_old_space_size=4096"
      # Add more stack, this is done at Dockerfile:
      #   bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
      #---------------------------------------------------------------
      # ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ====
      # https://github.com/edemaine/kadira-compose
      # https://github.com/meteor/meteor-apm-agent
      # https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f
      #- APM_OPTIONS_ENDPOINT=http://:11011
      #- APM_APP_ID=
      #- APM_APP_SECRET=
      #---------------------------------------------------------------
      # ==== OPTIONAL: LOGS AND STATS ====
      # https://github.com/wekan/wekan/blob/main/docs/Features/Logs.md
      #
      # Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
      # https://github.com/wekan/wekan-logstash
      #
      # Statistics Python script for Wekan Dashboard
      # https://github.com/wekan/wekan-stats
      #
      # The legacy Winston/Zulip database-change logger was removed in 2017:
      # replacing Meteor's default console transport could prevent database startup (#1094).
      # Use the current logging integrations documented above instead.
      #
      # There is Feature Request: Logging date and time of all activity with summary reports,
      # and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598
      #---------------------------------------------------------------
      # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ====
      #- RESULTS_PER_PAGE=20
      #---------------------------------------------------------------
      # ==== AFTER OIDC LOGIN, ADD USERS AUTOMATICALLY TO THIS BOARD ID ====
      # https://github.com/wekan/wekan/pull/5098
      #- DEFAULT_BOARD_ID=abcd1234
      #---------------------------------------------------------------
      # ==== WEKAN API AND EXPORT BOARD ====
      # Wekan Export Board works when WITH_API=true.
      # https://github.com/wekan/wekan/blob/main/docs/API/REST-API.md
      # https://github.com/wekan/wekan-gogs
      # If you disable Wekan API with false, Export Board does not work.
      - WITH_API=true
      #---------------------------------------------------------------
      # ==== PASSWORD BRUTE FORCE PROTECTION ====
      #https://atmospherejs.com/lucasantoniassi/accounts-lockout
      #Defaults below. Uncomment to change. wekan/server/accounts-lockout.js
      #- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3
      #- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
      #- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
      #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3
      #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60
      #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15
      #---------------------------------------------------------------
      # ==== ACCOUNT OPTIONS ====
      # https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config
      # Defaults below. Uncomment to change. wekan/server/accounts-common.js
      # - ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90
      #---------------------------------------------------------------
      #---------------------------------------------------------------
      # ==== CARD LOADING: all (default) or lazy ====
      # 'all'  = load every card of a board into the browser (default).
      # 'lazy' = each list loads only the cards currently visible
      #          (infinite scroll) plus a live count, so boards with
      #          thousands of cards use less memory/bandwidth.
      # Also changeable at runtime in Admin Panel / Features.
      - CARDS_LOADING=auto
      #---------------------------------------------------------------
      # ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
      # https://github.com/wekan/wekan/issues/2518
      - CARD_OPENED_WEBHOOK_ENABLED=false
      #---------------------------------------------------------------
      # ==== Allow configuration to validate uploaded attachments ====
      #-ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
      #-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/*
      #-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000
      #---------------------------------------------------------------
      # ==== Allow configuration to validate uploaded avatars ====
      #-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file}
      #-AVATARS_UPLOAD_MIME_TYPES=image/*
      #-AVATARS_UPLOAD_MAX_SIZE=500000
      # DEFAULT_AVATAR_URL : avatar of users who have not set one, a URL with {username},
      # {userId}, {emailMd5} or {emailSha256} replaced. Example: http://192.168.1.200/avatars/{username}.png
      #- DEFAULT_AVATAR_URL=
      #---------------------------------------------------------------
      # ==== Allow to shrink attached/pasted image ====
      # https://github.com/wekan/wekan/pull/2544
      #- MAX_IMAGE_PIXEL=1024
      #- IMAGE_COMPRESS_RATIO=80
      #---------------------------------------------------------------
      # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE =====
      # Number of days after a notification is read before we remove it.
      # Default: 2
      #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2
      #---------------------------------------------------------------
      # ==== BIGEVENTS DUE ETC NOTIFICATIONS =====
      # https://github.com/wekan/wekan/pull/2541
      # Introduced a system env var BIGEVENTS_PATTERN default as "NONE",
      # so any activityType matches the pattern, system will send out
      # notifications to all board members no matter they are watching
      # or tracking the board or not. Owner of the wekan server can
      # disable the feature by setting this variable to "NONE" or
      # change the pattern to any valid regex. i.e. '|' delimited
      # activityType names.
      # a) Example
      #- BIGEVENTS_PATTERN=due
      # b) All
      #- BIGEVENTS_PATTERN=received|start|due|end
      # c) Disabled
      - BIGEVENTS_PATTERN=NONE
      #---------------------------------------------------------------
      # ==== NOTIFY ON ASSIGN =====
      # Notify the user directly when they are added as a card member or
      # assignee. Set false to disable.
      #- NOTIFY_ON_ASSIGN=true
      #---------------------------------------------------------------
      # ==== EMAIL DUE DATE NOTIFICATION =====
      # https://github.com/wekan/wekan/pull/2536
      # System timelines will be showing any user modification for
      # dueat startat endat receivedat, also notification to
      # the watchers and if any card is due, about due or past due.
      #
      # Notify due days, default is None, 2 days before and on the event day
      #- NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0
      #
      # Notify due at hour of day. Default every morning at 8am. Can be 0-23.
      # If env variable has parsing error, use default. Notification sent to watchers.
      #- NOTIFY_DUE_AT_HOUR_OF_DAY=8
      #-----------------------------------------------------------------
      # ==== EMAIL NOTIFICATION TIMEOUT, ms =====
      # Default: 30000 ms = 30s
      #- EMAIL_NOTIFICATION_TIMEOUT=30000
      #-----------------------------------------------------------------
      # ==== CORS =====
      # CORS: Set Access-Control-Allow-Origin header.
      #- CORS=*
      # CORS_ALLOW_HEADERS: Set Access-Control-Allow-Headers header.  "Authorization,Content-Type" is required for cross-origin use of the API.
      #- CORS_ALLOW_HEADERS=Authorization,Content-Type
      # CORS_EXPOSE_HEADERS: Set Access-Control-Expose-Headers header.  This is not needed for typical CORS situations
      #- CORS_EXPOSE_HEADERS=*
      #-----------------------------------------------------------------
      # ==== MATOMO INTEGRATION ====
      # Optional: Integration with Matomo https://matomo.org that is installed to your server
      # The address of the server where Matomo is hosted.
      #- MATOMO_ADDRESS=https://example.com/matomo
      # The value of the site ID given in Matomo server for Wekan
      #- MATOMO_SITE_ID=1
      # The option do not track which enables users to not be tracked by matomo
      #- MATOMO_DO_NOT_TRACK=true
      # The option that allows matomo to retrieve the username:
      #- MATOMO_WITH_USERNAME=true
      #-----------------------------------------------------------------
      # ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
      # Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
      # Setting this to false is not recommended, it also disables all other browser policy protections
      # and allows all iframing etc. See wekan/server/policy.js
      - BROWSER_POLICY_ENABLED=true
      # When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
      #- TRUSTED_URL=https://intra.example.com
      #-----------------------------------------------------------------
      # ==== METRICS ALLOWED IP ADDRESSES ====
      # https://github.com/wekan/wekan/blob/main/docs/Features/Metrics.md
      #- METRICS_ALLOWED_IP_ADDRESSES=192.168.0.100,192.168.0.200
      #-----------------------------------------------------------------
      # ==== OUTGOING WEBHOOKS ====
      # What to send to Outgoing Webhook, or leave out. If commented out the default values will be: cardId,listId,oldListId,boardId,comment,user,card,commentId,swimlaneId,customerField,customFieldValue
      #- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,board,list,swimlane,commentId
      #-----------------------------------------------------------------
      # ==== Debug OIDC OAuth2 etc ====
      #- DEBUG=true
      #---------------------------------------------
      # ==== AUTOLOGIN WITH OIDC/OAUTH2 ====
      # https://github.com/wekan/wekan/blob/main/docs/Login/autologin.md
      #- OIDC_REDIRECTION_ENABLED=true
      # OIDC RP-initiated logout endpoint (end_session_endpoint). When set, the
      # "Log Out" menu redirects to this endpoint so the identity provider session
      # is also ended and the user is returned to Wekan (ROOT_URL) via
      # post_logout_redirect_uri, instead of landing on the provider home page
      # (which errors for non-admin users). See https://github.com/wekan/wekan/issues/6158
      # Keycloak example:
      #- OAUTH2_LOGOUT_ENDPOINT=/realms//protocol/openid-connect/logout
      #-----------------------------------------------------------------
      # ==== OAUTH2 ORACLE on premise identity manager OIM ====
      #- ORACLE_OIM_ENABLED=true
      #-----------------------------------------------------------------
      # ==== OAUTH2 AZURE ====
      # https://github.com/wekan/wekan/blob/main/docs/Login/Azure/Azure.md
      # 1) Register the application with Azure. Make sure you capture
      #    the application ID as well as generate a secret key.
      # 2) Configure the environment variables. This differs slightly
      #     by installation type, but make sure you have the following:
      #- OAUTH2_ENABLED=true
      # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299
      #- OAUTH2_CA_CERT=ABCD1234
      # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting.
      #- OAUTH2_ADFS_ENABLED=false
      # Azure AD B2C. https://github.com/wekan/wekan/issues/5242
      #- OAUTH2_B2C_ENABLED=false
      # SECURITY (GHSA-mp7g-hj5q-gxhq): Link an OIDC login to a pre-existing
      # Wekan account (password/LDAP) with the same email. OFF by default; when
      # false an OIDC login whose email already exists is rejected instead of
      # merged, preventing account takeover via spoofed email claims. Only
      # enable if you fully trust your OIDC provider's email claims, and even
      # then the provider must send email_verified=true for the merge to happen.
      #- OAUTH2_MERGE_EXISTING_USERS=false
      # When false, OAuth2/OIDC login is refused for users that do not already
      # have a Wekan account (matched by verified email).
      #- OAUTH2_AUTO_REGISTRATION=true
      # Comma/space separated OAuth2/OIDC group names whose members become Wekan
      # admins. Empty = off.
      #- OAUTH2_ADMIN_GROUPS=
      # OAuth2 login style: redirect (default) or popup.
      #- OAUTH2_LOGIN_STYLE=redirect
      # Application GUID captured during app registration:
      #- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
      # Secret key generated during app registration:
      #- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
      #- OAUTH2_SERVER_URL=https://login.microsoftonline.com/
      #- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize
      #- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo
      #- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token
      # The claim name you want to map to the unique ID field:
      #- OAUTH2_ID_MAP=email
      # The claim name you want to map to the username field:
      #- OAUTH2_USERNAME_MAP=email
      # The claim name you want to map to the full name field:
      #- OAUTH2_FULLNAME_MAP=name
      # The claim name you want to map to the email field:
      #- OAUTH2_EMAIL_MAP=email
      # Optional exact email domains for generic OAuth2/OIDC login (on-premise supported).
      #- OAUTH2_ALLOWED_EMAIL_DOMAINS=example.internal,other.internal
      # OAUTH2_DEFAULT_ORGANIZATION : an existing organization that every account
      # created by an OAuth2/OIDC login joins (short name, display name or id).
      #- OAUTH2_DEFAULT_ORGANIZATION=Nextcloud users
      #-----------------------------------------------------------------
      # ==== OAUTH2 Nextcloud ====
      # 1) Register the application with Nextcloud: https://your.nextcloud/index.php/settings/admin/security
      #    Make sure you capture the application ID as well as generate a secret key.
      #    Use https://your.wekan/_oauth/oidc for the redirect URI.
      # 2) Configure the environment variables. This differs slightly
      #     by installation type, but make sure you have the following:
      #- OAUTH2_ENABLED=true
      # OAuth2 login style: redirect (default) or popup.
      #- OAUTH2_LOGIN_STYLE=redirect
      # Application GUID captured during app registration:
      #- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
      # Secret key generated during app registration:
      #- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
      #- OAUTH2_SERVER_URL=https://your-nextcloud.tld
      #- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize
      #- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json
      #- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token
      # The claim name you want to map to the unique ID field:
      #- OAUTH2_ID_MAP=id
      # The claim name you want to map to the username field:
      #- OAUTH2_USERNAME_MAP=id
      # The claim name you want to map to the full name field:
      #- OAUTH2_FULLNAME_MAP=display-name
      # The claim name you want to map to the email field:
      #- OAUTH2_EMAIL_MAP=email
      #-----------------------------------------------------------------
      # ==== OAUTH2 KEYCLOAK ====
      # https://github.com/wekan/wekan/blob/main/docs/Login/Keycloak/Keycloak.md  <== MAPPING INFO, REQUIRED
      #- OAUTH2_ENABLED=true
      # OAuth2 login style: redirect (default) or popup.
      #- OAUTH2_LOGIN_STYLE=redirect
      #- OAUTH2_CLIENT_ID=
      #- OAUTH2_SERVER_URL=
      #- OAUTH2_AUTH_ENDPOINT=/realms//protocol/openid-connect/auth
      #- OAUTH2_USERINFO_ENDPOINT=/realms//protocol/openid-connect/userinfo
      #- OAUTH2_TOKEN_ENDPOINT=/realms//protocol/openid-connect/token
      #- OAUTH2_SECRET=
      #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
      #- OAUTH2_ID_MAP=sub
      #- OAUTH2_USERNAME_MAP=preferred_username
      #- OAUTH2_EMAIL_MAP=email
      #- OAUTH2_FULLNAME_MAP=name
      #-----------------------------------------------------------------
      # ==== OAUTH2 AUTHELIA ====
      # https://github.com/wekan/wekan/blob/main/docs/Features/Login-Page/Authelia.md  <== MAPPING INFO, REQUIRED
      # https://www.authelia.com/integration/openid-connect/introduction/
      #- OAUTH2_ENABLED=true
      # OAuth2 login style: redirect (default) or popup.
      #- OAUTH2_LOGIN_STYLE=redirect
      #- OAUTH2_CLIENT_ID=
      #- OAUTH2_SERVER_URL=https://auth.yourserver.com
      #- OAUTH2_AUTH_ENDPOINT=/api/oidc/authorization
      #- OAUTH2_USERINFO_ENDPOINT=/api/oidc/userinfo
      #- OAUTH2_TOKEN_ENDPOINT=/api/oidc/token
      #- OAUTH2_SECRET=
      #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
      #- OAUTH2_ID_MAP=sub
      #- OAUTH2_USERNAME_MAP=preferred_username
      #- OAUTH2_EMAIL_MAP=email
      #- OAUTH2_FULLNAME_MAP=name
      #- OAUTH2_REQUEST_PERMISSIONS=openid profile email
      #-----------------------------------------------------------------
      # ==== OAUTH2 DOORKEEPER ====
      # https://github.com/wekan/wekan/issues/1874
      # https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md2
      # Enable the OAuth2 connection
      #- OAUTH2_ENABLED=true
      # OAuth2 docs: https://github.com/wekan/wekan/blob/main/docs/Login/OAuth2.md
      # OAuth2 login style: redirect (default) or popup.
      #- OAUTH2_LOGIN_STYLE=redirect
      # OAuth2 Client ID.
      #- OAUTH2_CLIENT_ID=abcde12345
      # OAuth2 Secret.
      #- OAUTH2_SECRET=54321abcde
      #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret
      # OAuth2 Server URL.
      #- OAUTH2_SERVER_URL=https://chat.example.com
      # OAuth2 Authorization Endpoint.
      #- OAUTH2_AUTH_ENDPOINT=/oauth/authorize
      # OAuth2 Userinfo Endpoint.
      #- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
      # OAuth2 Token Endpoint.
      #- OAUTH2_TOKEN_ENDPOINT=/oauth/token
      # OAUTH2 ID Token Whitelist Fields.
      #- OAUTH2_ID_TOKEN_WHITELIST_FIELDS=""
      # OAUTH2 Request Permissions.
      #- OAUTH2_REQUEST_PERMISSIONS=openid profile email
      # OAuth2 ID Mapping
      #- OAUTH2_ID_MAP=
      # OAuth2 Username Mapping
      #- OAUTH2_USERNAME_MAP=
      # OAuth2 Fullname Mapping
      #- OAUTH2_FULLNAME_MAP=
      # OAuth2 Email Mapping
      #- OAUTH2_EMAIL_MAP=
      #-----------------------------------------------------------------
      # ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
      # https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
      # For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
      # Most settings work both on Snap and Docker below.
      # Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
      #
      # The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap.
      # (this is set properly in the Admin Panel, changing this item does not remove Password login option)
      #- DEFAULT_AUTHENTICATION_METHOD=ldap
      #
      # Enable or not the connection by the LDAP
      #- LDAP_ENABLE=true
      #
      # The port of the LDAP server
      #- LDAP_PORT=389
      #
      # The host server for the LDAP server
      #- LDAP_HOST=localhost
      #
      #-----------------------------------------------------------------
      # ==== LDAP AD Simple Auth ====
      #
      # Set to true, if you want to connect with Active Directory by Simple Authentication.
      # When using AD Simple Auth, LDAP_BASEDN is not needed.
      #
      # Example:
      #- LDAP_AD_SIMPLE_AUTH=true
      #
      # === LDAP User Authentication ===
      #
      # a) Option to login to the LDAP server with the user's own username and password, instead of
      #    an administrator key. Default: false (use administrator key).
      #
      # b) When using AD Simple Auth, set to true, when login user is used for binding,
      #    and LDAP_BASEDN is not needed.
      #
      # Example:
      #- LDAP_USER_AUTHENTICATION=true
      #
      # Which field is used to find the user for the user authentication. Default: uid.
      #- LDAP_USER_AUTHENTICATION_FIELD=uid
      #
      # === LDAP Default Domain ===
      #
      # a) In case AD SimpleAuth is configured, the default domain is appended to the given
      #    loginname for creating the correct username for the bind request to AD.
      #
      # b) The default domain of the ldap it is used to create email if the field is not map
      #     correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
      #
      # Example :
      #- LDAP_DEFAULT_DOMAIN=mydomain.com
      #
      #-----------------------------------------------------------------
      # ==== LDAP BASEDN Auth ====
      #
      # The base DN for the LDAP Tree
      #- LDAP_BASEDN=ou=user,dc=example,dc=org
      #
      #-----------------------------------------------------------------
      # Fallback on the default authentication method
      #- LDAP_LOGIN_FALLBACK=false
      #
      # Reconnect to the server if the connection is lost
      #- LDAP_RECONNECT=true
      #
      # Overall timeout, in milliseconds
      #- LDAP_TIMEOUT=10000
      #
      # Specifies the timeout for idle LDAP connections in milliseconds
      #- LDAP_IDLE_TIMEOUT=10000
      #
      # Connection timeout, in milliseconds
      #- LDAP_CONNECT_TIMEOUT=10000
      #
      # If the LDAP needs a user account to search
      #- LDAP_AUTHENTIFICATION=true
      #
      # The search user DN - You need quotes when you have spaces in parameters
      # 2 examples:
      #- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan
      #- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com
      #
      # The password for the search user
      #- LDAP_AUTHENTIFICATION_PASSWORD=pwd
      #- LDAP_AUTHENTIFICATION_PASSWORD_FILE=/run/secrets/ldap_auth_password
      #
      # Enable logs for the module
      #- LDAP_LOG_ENABLED=true
      #
      # If the sync of the users should be done in the background
      #- LDAP_BACKGROUND_SYNC=false
      #
      # LDAP_BACKGROUND_SYNC_INTERVAL : How often the background task syncs: milliseconds (3600000 = hourly) or text
      # Text is later.js text, quotes allowed, as specified in:
      # https://bunkat.github.io/later/parsers.html#text
      #- LDAP_BACKGROUND_SYNC_INTERVAL=every 1 hours
      # Unset or empty uses the default (hourly). A value it cannot read is logged
      # and the default is used; it no longer stops WeKan (#2354).
      # https://github.com/wekan/wekan/issues/2354#issuecomment-515305722
      - LDAP_BACKGROUND_SYNC_INTERVAL=''
      #
      #- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
      #
      #- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
      #
      # LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS : When true, the LDAP
      # background sync disables users no longer present in LDAP (and re-enables
      # them when they reappear).
      #- LDAP_BACKGROUND_SYNC_DISABLE_NONEXISTANT_USERS=false
      #
      # If using LDAPS: LDAP_ENCRYPTION=ssl
      #- LDAP_ENCRYPTION=false
      #
      # The certification for the LDAPS server. Certificate needs to be included in this docker compose.yml file.
      #- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
      #
      # Reject Unauthorized Certificate
      #- LDAP_REJECT_UNAUTHORIZED=false
      #
      # Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
      #- LDAP_USER_SEARCH_FILTER=
      #
      # base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
      #- LDAP_USER_SEARCH_SCOPE=one
      #
      # Which field is used to find the user, like uid / sAMAccountName
      #- LDAP_USER_SEARCH_FIELD=sAMAccountName
      #
      # Used for pagination (0=unlimited)
      #- LDAP_SEARCH_PAGE_SIZE=0
      #
      # The limit number of entries (0=unlimited)
      #- LDAP_SEARCH_SIZE_LIMIT=0
      #
      # Enable the login restriction group filter. Note the authenticated ldap user must be able to query all relevant group data with own login data from ldap.
      # When true, only members of LDAP_GROUP_FILTER_GROUP_NAME are allowed to log in.
      # NOTE: This flag ONLY controls the login restriction. Admin status sync
      # (LDAP_SYNC_ADMIN_STATUS / LDAP_SYNC_ADMIN_GROUPS) and group->role sync
      # (LDAP_SYNC_GROUP_ROLES) query LDAP groups independently and do NOT require
      # this flag to be true. The group filter metadata below
      # (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
      # LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
      # must still be configured for any group search to work.
      #- LDAP_GROUP_FILTER_ENABLE=false
      #
      # Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
      # unset when users and groups share one subtree: the group searches then
      # use LDAP_BASEDN, exactly as they always have. Set it when the directory
      # keeps them apart - ou=groups beside ou=people is a common layout - or
      # every group search runs under the USER base, finds nothing, and
      # LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
      #- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
      #
      # The object class for filtering. Example: group
      #- LDAP_GROUP_FILTER_OBJECTCLASS=
      #
      # The attribute of a group identifying it. Example: cn
      #- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
      #
      # The attribute inside a group object listing its members. Example: member
      #- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
      #
      # The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn is saved as value into the attribute.
      #- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
      #
      # Active Directory nested groups (#6744). When true, a user who is a member of
      # LDAP_GROUP_FILTER_GROUP_NAME through another group (team group -> access
      # group) is a member too: the group searches use AD's
      # LDAP_MATCHING_RULE_IN_CHAIN (1.2.840.113556.1.4.1941). Needs a DN-valued
      # member attribute (member, with LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=dn).
      # Also applies to admin status, group->role and org/team sync. Default: false.
      #- LDAP_GROUP_FILTER_NESTED=true
      #
      # Let users moved from local password login to LDAP still log in with their old local password (true). Default: false
      #- LDAP_MIGRATION_ALLOW_PASSWORD_LOGIN=
      #
      # Comma separated LDAP attributes to read for a user. Empty: all attributes. Example: uid,cn,mail
      #- LDAP_USER_ATTRIBUTES=
      #
      # The OIDC user info claim that holds the avatar URL. Default: picture
      #- OAUTH2_AVATAR_MAP=
      #
      # Update groups, admin status, e-mail, full name and username from the OIDC provider at every login (true). Default: false
      #- PROPAGATE_OIDC_DATA=
      #
      # Sign in with Apple: path to the private key (.p8) that signs the client secret JWT. Empty: OAUTH2_SECRET is used
      #- OAUTH2_SECRET_JWT_KEY_PATH=
      #
      # Sign in with Apple: the Team ID, issuer of the client secret JWT
      #- OAUTH2_SECRET_JWT_ISSUER=
      #
      # Sign in with Apple: the Key ID of the private key
      #- OAUTH2_SECRET_JWT_KEY_ID=
      #
      # Audience of the client secret JWT. Default: https://appleid.apple.com
      #- OAUTH2_SECRET_JWT_AUDIENCE=
      #
      # Subject of the client secret JWT. Default: OAUTH2_CLIENT_ID
      #- OAUTH2_SECRET_JWT_SUBJECT=
      #
      # Lifetime of the client secret JWT in seconds. Default: 300
      #- OAUTH2_SECRET_JWT_EXPIRES_IN=
      #
      # Let a CAS login take over an existing local account with the same username (true). Default: false
      #- CAS_MERGE_EXISTING_USERS=
      #
      # The group name (id) that matches all users.
      #- LDAP_GROUP_FILTER_GROUP_NAME=
      #
      # LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
      #- LDAP_UNIQUE_IDENTIFIER_FIELD=
      #
      # LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
      #- LDAP_UTF8_NAMES_SLUGIFY=true
      #
      # LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName
      #- LDAP_USERNAME_FIELD=sAMAccountName
      #
      # LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName
      #- LDAP_FULLNAME_FIELD=fullname
      #
      #- LDAP_MERGE_EXISTING_USERS=false
      #
      # Allow existing account matching by e-mail address when username does not match
      #- LDAP_EMAIL_MATCH_ENABLE=true
      #
      # LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match
      #- LDAP_EMAIL_MATCH_REQUIRE=true
      #
      # LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching
      #- LDAP_EMAIL_MATCH_VERIFIED=true
      #
      # LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address
      #- LDAP_EMAIL_FIELD=mail
      #-----------------------------------------------------------------
      #- LDAP_SYNC_USER_DATA=false
      #
      #- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
      #
      #- LDAP_SYNC_GROUP_ROLES=
      #
      # The default domain of the ldap it is used to create email if the field is not map correctly
      # with the LDAP_SYNC_USER_DATA_FIELDMAP is defined in setting LDAP_DEFAULT_DOMAIN above.
      #
      # Enable/Disable syncing of admin status based on ldap groups:
      # NOTE: Admin status sync and group->role sync (LDAP_SYNC_GROUP_ROLES) query
      # LDAP groups on their own. They no longer require LDAP_GROUP_FILTER_ENABLE=true,
      # which only controls the login restriction filter. The group filter metadata
      # (LDAP_GROUP_FILTER_OBJECTCLASS, LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE,
      # LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT, LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE)
      # must still be configured for the group search to work.
      #- LDAP_SYNC_ADMIN_STATUS=true
      #
      # Comma separated list of admin group names to sync.
      #- LDAP_SYNC_ADMIN_GROUPS=group1,group2
      #
      # When true, sync a user's LDAP groups as Wekan Organizations.
      #- LDAP_SYNC_ORGANIZATIONS=false
      #
      # Comma separated allowlist of LDAP group names/patterns to sync as Organizations.
      # Wildcards supported: * (any chars), ? (single char). Empty = all of the user's groups.
      #- LDAP_SYNC_ORGANIZATIONS_GROUPS=
      #
      # When true, sync a user's LDAP groups as Wekan Teams.
      #- LDAP_SYNC_TEAMS=false
      #
      # Comma separated allowlist of LDAP group names/patterns to sync as Teams.
      # Wildcards supported: * (any chars), ? (single char). Empty = all of the user's groups.
      #- LDAP_SYNC_TEAMS_GROUPS=
      #---------------------------------------------------------------------
      # Login to LDAP automatically with HTTP header.
      # In below example for siteminder, at right side of = is header name.
      #- HEADER_LOGIN_ID=HEADERUID
      #- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME
      #- HEADER_LOGIN_LASTNAME=HEADERLASTNAME
      #- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS
      # SECURITY (GHSA-jggc-qvfc-jr6x): comma-separated allowlist of source IPs
      # permitted to use header login. The source IP is the real TCP peer of the
      # connection (your reverse proxy), NOT the spoofable X-Forwarded-For header.
      # REQUIRED when header login is enabled: if empty/unset, header login fails
      # CLOSED and authenticates no one.
      #- HEADER_LOGIN_TRUSTED_IPS=127.0.0.1,10.0.0.2
      # Optional: if WeKan sits behind MULTIPLE proxy hops, list the intermediate
      # proxy IPs here. X-Forwarded-For is then honored ONLY when the immediate
      # TCP peer is one of these trusted proxies, and the right-most hop that is
      # not itself a trusted proxy (the real client) is matched against
      # HEADER_LOGIN_TRUSTED_IPS above.
      #- HEADER_LOGIN_TRUSTED_PROXIES=10.0.0.1,10.0.0.2
      #---------------------------------------------------------------------
      # ==== LOGOUT TIMER, probably does not work yet ====
      # LOGOUT_WITH_TIMER : Enables or not the option logout with timer
      # example : LOGOUT_WITH_TIMER=true
      #- LOGOUT_WITH_TIMER=
      #
      # LOGOUT_IN : The number of days
      # example : LOGOUT_IN=1
      #- LOGOUT_IN=
      #
      # LOGOUT_ON_HOURS : The number of hours
      # example : LOGOUT_ON_HOURS=9
      #- LOGOUT_ON_HOURS=
      #
      # LOGOUT_ON_MINUTES : The number of minutes
      # example : LOGOUT_ON_MINUTES=55
      #- LOGOUT_ON_MINUTES=
      #---------------------------------------------------------------
      # USERS_DISABLE_INACTIVE_AFTER_DAYS : once a night, disable accounts that
      # have not used WeKan for this many days (Admin Panel > People > Last
      # active). Admins are never disabled. Accounts with no recorded activity
      # yet are counted from the first run. Re-enabling an account restarts its
      # count. Unset or 0 = off (default). Admin Panel / People / Login can set
      # it too, and its value there wins.
      #- USERS_DISABLE_INACTIVE_AFTER_DAYS=180
      #-------------------------------------------------------------------
      # Hide password login form
      # - PASSWORD_LOGIN_ENABLED=true
      #-------------------------------------------------------------------
      #- CAS_ENABLED=true
      #- CAS_BASE_URL=https://cas.example.com/cas
      #- CAS_LOGIN_URL=https://cas.example.com/login
      #- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate
      #---------------------------------------------------------------------
      # SAML 2.0 (SP-initiated) login, see docs/Features/Login-Page/SAML.md.
      # SAML_ENABLED : turn the "Sign In with SAML" login button on.
      #- SAML_ENABLED=true
      # SAML_PROVIDER : short name for the identity provider, used to build
      # the ACS/callback URL and shown in Meteor.settings.public.
      #- SAML_PROVIDER=
      # SAML_ENTRYPOINT : the identity provider's SSO redirect endpoint.
      #- SAML_ENTRYPOINT=
      # SAML_ISSUER : this WeKan instance's SAML issuer/entity ID.
      #- SAML_ISSUER=
      # SAML_CERT : the identity provider's signing certificate (PEM, no
      # header/footer needed), used to verify the SAML response signature.
      #- SAML_CERT=
      #- SAML_IDPSLO_REDIRECTURL=
      # SAML_PRIVATE_KEYFILE / SAML_PUBLIC_CERTFILE : optional, paths (under
      # private/) to this WeKan instance's own key/cert, only needed if the
      # identity provider requires signed AuthnRequests.
      #- SAML_PRIVATE_KEYFILE=
      #- SAML_PUBLIC_CERTFILE=
      # SAML_IDENTIFIER_FORMAT : NameID format requested from the IdP.
      # Defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress.
      #- SAML_IDENTIFIER_FORMAT=
      # SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE : assertion attribute whose value becomes
      # the WeKan username (e.g. username). Empty = the email / NameID. With
      # SAML_MERGE_EXISTING_USERS=true an existing account is found by that
      # username, or - when the value is the assertion's email - by a verified
      # email, so an LDAP account with its own short username can be linked.
      # Linking an existing account ALSO requires the assertion to carry the
      # attribute email_verified=true (SAML has no standard one: add it at the
      # IdP only if the IdP verifies addresses) and that email to be verified on
      # the local account. A refused link logs its reason in the server log.
      # docs/Features/Login-Page/SAML.md
      #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE=
      # SAML_ATTRIBUTES : not used by login at the moment; attributes are read
      # from the validated assertion as named (email, username, displayName).
      #- SAML_ATTRIBUTES=
      #- SAML_MERGE_EXISTING_USERS=false
      #- SAML_IDP_PROFILE=standard
      #- SAML_WANT_RESPONSE_SIGNED=
      #- SAML_WANT_ASSERTIONS_SIGNED=
      #- SAML_LOGIN_FLOW=
      #---------------------------------------------------------------------
      # Login with Meteor accounts providers, see docs/Features/Login-Page/OAuth-Providers.md
      # and docs/Features/Login-Page/Passwordless.md. Every one of these can be overridden
      # in Admin Panel / People / Login; a value set there wins over the environment
      # variable. Register this callback URL at the provider:
      #   /_oauth/  where service is google, github, facebook,
      #   twitter, meteor-developer, weibo or meetup.
      # ==== Google ==== create the app at https://console.cloud.google.com/apis/credentials
      # callback URL: /_oauth/google
      #- OAUTH_GOOGLE_ENABLED=true
      #- OAUTH_GOOGLE_CLIENT_ID=
      #- OAUTH_GOOGLE_SECRET=
      # OAUTH_GOOGLE_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_GOOGLE_SECRET_FILE=/run/secrets/oauth_google_secret
      # ==== GitHub ==== create the app at https://github.com/settings/developers
      # callback URL: /_oauth/github
      #- OAUTH_GITHUB_ENABLED=true
      #- OAUTH_GITHUB_CLIENT_ID=
      #- OAUTH_GITHUB_SECRET=
      # OAUTH_GITHUB_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_GITHUB_SECRET_FILE=/run/secrets/oauth_github_secret
      # ==== Facebook ==== create the app at https://developers.facebook.com/apps/
      # callback URL: /_oauth/facebook
      #- OAUTH_FACEBOOK_ENABLED=true
      #- OAUTH_FACEBOOK_APP_ID=
      #- OAUTH_FACEBOOK_SECRET=
      # OAUTH_FACEBOOK_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_FACEBOOK_SECRET_FILE=/run/secrets/oauth_facebook_secret
      # ==== Twitter / X ==== create the app at https://developer.x.com/en/portal/dashboard
      # callback URL: /_oauth/twitter
      #- OAUTH_TWITTER_ENABLED=true
      #- OAUTH_TWITTER_CONSUMER_KEY=
      #- OAUTH_TWITTER_SECRET=
      # OAUTH_TWITTER_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_TWITTER_SECRET_FILE=/run/secrets/oauth_twitter_secret
      # ==== Meteor Developer ==== create the app at https://www.meteor.com/
      # callback URL: /_oauth/meteor-developer
      #- OAUTH_METEOR_DEVELOPER_ENABLED=true
      #- OAUTH_METEOR_DEVELOPER_CLIENT_ID=
      #- OAUTH_METEOR_DEVELOPER_SECRET=
      # OAUTH_METEOR_DEVELOPER_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_METEOR_DEVELOPER_SECRET_FILE=/run/secrets/oauth_meteor_developer_secret
      # ==== Weibo ==== create the app at https://open.weibo.com/
      # callback URL: /_oauth/weibo
      #- OAUTH_WEIBO_ENABLED=true
      #- OAUTH_WEIBO_CLIENT_ID=
      #- OAUTH_WEIBO_SECRET=
      # OAUTH_WEIBO_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_WEIBO_SECRET_FILE=/run/secrets/oauth_weibo_secret
      # ==== Meetup ==== create the app at https://www.meetup.com/api/oauth/list/
      # callback URL: /_oauth/meetup
      #- OAUTH_MEETUP_ENABLED=true
      #- OAUTH_MEETUP_CLIENT_ID=
      #- OAUTH_MEETUP_SECRET=
      # OAUTH_MEETUP_SECRET_FILE : read the secret from a file (Docker secrets) instead
      #- OAUTH_MEETUP_SECRET_FILE=/run/secrets/oauth_meetup_secret
      # OAUTH_PROVIDERS_LOGIN_STYLE : redirect or popup, for all providers above. Default: redirect
      #- OAUTH_PROVIDERS_LOGIN_STYLE=redirect
      # OAUTH_PROVIDERS_MERGE_EXISTING_USERS : link a provider login to an existing WeKan
      # account with the same email. Default: false = the login is refused, not merged,
      # like OAUTH2_MERGE_EXISTING_USERS. Enable only if you trust the provider's emails.
      #- OAUTH_PROVIDERS_MERGE_EXISTING_USERS=false
      # OAUTH_PROVIDERS_ALLOWED_EMAIL_DOMAINS : only these email domains may sign in with
      # the providers above, comma separated, like OAUTH2_ALLOWED_EMAIL_DOMAINS. Empty: all.
      #- OAUTH_PROVIDERS_ALLOWED_EMAIL_DOMAINS=example.com
      # PASSWORDLESS_ENABLED : email a one-time sign-in code instead of asking a
      # password. Needs MAIL_URL. Also switchable in Admin Panel / People / Login.
      #- PASSWORDLESS_ENABLED=true
      #---------------------------------------------------------------------
      # Wait spinner to use
      # - WAIT_SPINNER=Bounce
      #---------------------------------------------------------------------
    # Wait for MongoDB to be healthy (replica-set primary elected) before
    # starting WeKan, so index creation does not crash with "Topology is closed".
    # If you use FerretDB instead, comment out the wekandb block below and
    # uncomment the wekan-ferretdb block.
    depends_on:
      ferretdb:
        condition: service_healthy
    #  wekan-migrate:
    #    condition: service_completed_successfully
    #  wekan-ferretdb:
    #    condition: service_healthy
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - wekan-files:/data:rw

volumes:
  wekan-files:
    driver: local
  ferretdb-data:
    driver: local

networks:
  wekan-tier:
    driver: bridge

File: README.md

WeKan ยฎ - Open Source kanban

Downloads

https://wekan.fi/install/

Screenshot

About WeKan ยฎ

WeKan ยฎ is a FLOSS collaborative kanban board application with MIT license.

Whether youโ€™re maintaining a personal todo list, planning your holidays with some friends,
or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool
to keep your things organized. They give you a visual overview of the current state of your project,
and make you productive by allowing you to focus on the few items that matter the most.

Since WeKan ยฎ is a free software, you donโ€™t have to trust us with your data and can
install Wekan on your own computer or server. In fact we encourage you to do
that by providing one-click installation on various platforms.

  • WeKan ยฎ is used in most countries of the world.
  • WeKan ยฎ largest user has 30k users using WeKan ยฎ in their company.
  • WeKan ยฎ has been translated to 234 languages,
    167 of them essentially complete.
  • Features:
    • Real-time user interface
    • All Boards page, Drag drop reorder with one or Multi-Selection:
      • Board icons at Remaining, (Sub)Workspaces, Archive
      • Shared Templates: Boards, Lists, Cards
      • Add Board / Import:
        • WeKan JSON/.zip, CSV/TSV, Excel, Markdown, Leo, todo.txt
        • Trello, Jira, Trello, Asana, Zenkit, Focalboard
        • Kanboard, Nextcloud Deck, OpenProject, Taskwarrior
        • GitHub, GitLab, Gitea, Forgejo
    • Change Color: Board theme and background image, Swimlane, List, Card
      • Overrides: 1 Admin Panel, 2 Board Settings, 3 Member Settings
    • Drag drop reorder with one or Multi-Selection:
      • Board icons to Workspaces at All Boards page
      • Swimlane, List, Minicard
  • Board
    • Top header: Mobile/Desktop view, Show/Hide Drag Handles, Starred Boards Bookmarks, Sort Boards, Filter, Search, Notifications, Member Settings
    • Member Settings:
      • Show My Dependencies, Show Board Dependencies, Import/Export JSON/SVG
        • Dependencies are for SAFe PI Plannings, drawing colored lines between cards, like Kendis or Scaled Agile Framework. Import Export as JSON and SVG.
      • My Cards, Due Cards, My Attachments, Search All Boards, Date Settings,
        Change color - override theme, Nofications Settings (Tray, Email, etc),
        Font change and Font size change, Change Avatar,
        Change Language (250+ languages)
      • Change Settings: Show desktop drag handles, Submit editors with Enter,
        Open many cards at once, Play a sound when a checklist item is completed,
        Show cards count if list contains more than ... cards,
        Show rescue dialoque before closing for unsaved card descriptions
    • Board Views:
    • Swimlanes, Lists
      • Minicard: At list there is card, that has not been not opened yet
      • Card: Opened card popup of minicard
        • Attachments viewer for PDF and Office documents
      • Drag drop reorder one or Multi-Selection: Swimlanes, Lists, Minicards, Checklists, Checklist Items
      • Collapse/Uncollapse: Menus, Swimlanes, Lists, Cards
    • Table, Calendar, Multi Board Calendar, Time, Timeline
    • Statistics, Group by Assignee
    • Gantt, Frappe Gantt, DHTMLX Gantt
    • Scrum: Product Backlog, Sprints, Sprint Report, Velocity, Roadmap, Dashboard, Bigboard
    • Burndown, Burnup, Cumulative Flow, Control, Cycle Time, Flow Efficiency,
      Lead Time, Throughtput Histogram, WIP Run, Pulse, Aging WIP,
      Blocker Analysis, Monte Carlo Forecasts, Process Behavior (XmR),
      Work Item Size vs Cycle Time, Map
    • Right Sidebar / Board Settings:
    • Rules: like IFTTT like Trello Butler
      • Lists view for Rules
      • Workflow view: like Jira Workflows
      • Blocks: like Scratch or Jira AutoBlocks, shows same IFTTT Rules
      • Import / Export IFTTT Rules
    • Export board :
      • Select what to include: Card details, Board, Activities, Labels, People (Creator, Owner, Members, Assignees), Board Info (Board, List, Swimlane), Dates (Created, Received, Start, Due, End), Description, Custom Fields, Checklists, Subtasks, Comments, Attachments, Votin, Plannin Poker, Stickers, Location (link to map based on coordinates), Dependencies, Sort, Scrum Settings
    • Export to: PDF, Excel, HTML, Calendar feed (iCal), Dependencies JSON/SVG,
      CSV , ; TSV, JSON with/without attachmeents, .zip (Attachments),
      Kanboard, Markdown, Leo, todo.txt, Taskwarrior, Focalboard,
      Trello, Jira, NextCloud Deck, OpenProject, GitHub, GitLab,
      Gitea, Forgejo, Asana, Zenkit
    • Scrum Settings
    • Change color theme, Change Background Image, Date settings
    • Settings:
      • Board View: Show/Hide and drag drop reorder what is shown atat Public Board and Private Board
      • Swimlane: Scrum settings, Lock resize swimlane height, WIP Limit Groups
      • Board View: Show/Hide and drag drop reorder what is shown at Minicard and Card
  • Admin Panel
    • Settings
      • Version
      • Visibility: What is shown/hidden, Whitelabeling
      • Announcement
      • Accessibility
      • Translation: Add custom translation strings
      • PWA
      • Global Webhooks
  • People
    • Email, Notifications, Domains, Organizations, Teams, People
    • Locked Users, Roles, Shared Templates
    • Login, SAML, LDAP, OAuth2 login providers, Passwordless login
  • Attachments
    • Scheduled Backups, Continuous realtime backups, Move Attachments, Default Save Storage, Limits
    • Storage: MongoDB GridFS, Filesystem, S3/MinIO, Azure Blob, Google Cloud
    • Database migration
  • Problems
    • Summary
    • Security
    • Delete: Optional enable permanent delete.
      • History Undo/Redo available at Board, List, Card, etc menus at Swimlanes and List views
    • Notifications
    • Reports
      • Security: shows any attempted attacks based on names from https://wekan.fi/hall-of-fame/ like built-in SIEM with country flag and city based of location of IP address based on location headers like CloudFlare etc
      • Impersonation
      • Performance, Speed, Tests, CPU usage, Instrumentation, Broken Cards, Files, Rules, Boards, Recovery, Offices, API, Database problems, Filesystem integrity
  • Platforms:
    • Windows/Mac/Linux bundle .zip
    • Linux Snap: Automatic updates
    • Linux AppImage
    • Docker, Kubernetes
    • SaaS: Free with App or Paid with Admin Panel

Docker Containers

text
image: ghcr.io/wekan/wekan:latest
text
image: wekanteam/wekan:latest
text
image: quay.io/wekan/wekan:latest

docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.yml

Standards

Code stats

Translate WeKan ยฎ at Transifex

Translations to non-English languages are accepted only at Transifex using webbrowser.
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json .

WeKan ยฎ feature requests and bugs

Please add most of your questions as GitHub issue: WeKan ยฎ Feature Requests and Bugs.
It's better than at chat where details get lost when chat scrolls up.

Discussions

IRC

FAQ

NOTE:

  • Please read the FAQ first
  • Please don't feed the trolls and spammers that are mentioned in the FAQ :)

Requirements

  • 1 GB RAM minimum free for WeKan ยฎ. Production server should have minimum total 4 GB RAM.
    For thousands of users, for example with Docker: 3 frontend servers,
    each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
  • Enough disk space and alerts about low disk space. If you run out of disk space, MongoDB database gets corrupted.
  • SECURITY: Updating to newest WeKan ยฎ version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
    Old versions have security issues because of old versions Node.js etc. Only newest WeKan ยฎ is supported.
    WeKan ยฎ on Sandstorm is not usually affected by any Standalone WeKan ยฎ (Snap/Docker/Source) security issues.
  • Reporting all new bugs immediately.
    New features and fixes are added to WeKan ยฎ many times a day.
  • Backups of WeKan ยฎ database once a day minimum.
    Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
    Some bugs can cause WeKan ยฎ board to not load at all, requiring manual fixing of database content.

Roadmap and Demo

Roadmap - Public read-only board at WeKan ยฎ demo.

Developer Documentation

We also welcome sponsors for features and bugfixes.
By working directly with WeKan ยฎ you get the benefit of active maintenance and new features added by growing WeKan ยฎ developer community.

Getting Started with Development

The main branch uses Meteor 3.x with Node.js 26.x.
See CHANGELOG.md for the latest runtime updates.

To contribute, create a fork and run ./build.sh (or ./build.bat on Windows) as detailed here. Once you're ready, please test your code and submit a pull request (PR).

Please refer to the developer documentation for more information.

First-Time Setup for Development

Prerequisites

Before building WeKan from source, ensure you have:

  • Git - for cloning the repository
  • Node.js 26.x - WeKan requires Node.js 26.x
  • Meteor - the JavaScript framework WeKan is built with

Building WeKan

The build.sh script shows a two-level menu. The top level groups options into categories:

text
1) Setup   2) Dev server   3) Tests   4) Docker   5) Tools   6) Quit

You pick a category number, then the item number inside it (each submenu also has a Back entry). Building from source is a three-stage process:

  1. Setup -> Install dependencies - Downloads all required Meteor packages and npm modules
  2. Setup -> Build WeKan - Compiles the application
  3. Dev server -> localhost:3000 - Starts the development server at http://localhost:3000
bash
# Clone your fork
git clone [email protected]:YOUR_USERNAME/wekan.git
cd wekan

# Make the script executable
chmod +x build.sh

# Step 1: Install dependencies (Setup -> Install dependencies)
./build.sh
# Press 1 (Setup) and Enter, then 1 (Install dependencies) and Enter

# Step 2: Build WeKan (Setup -> Build WeKan, after dependencies complete)
./build.sh
# Press 1 (Setup) and Enter, then 2 (Build WeKan) and Enter

# Step 3: Run WeKan in development mode (Dev server -> localhost:3000)
./build.sh
# Press 2 (Dev server) and Enter, then 1 (localhost:3000) and Enter

If a dev server is already running on that port, the Dev server options stop it automatically and start a fresh server on the same port.

WSL Users

WSL users can use Snap. See install docs.

License

WeKan ยฎ is released under the very permissive MIT license, and made
with Meteor.


File: docs/API/New-card-with-Python3-and-REST-API.md

Wekan provides a python script to ease the call of the REST API from command line interface.

Context

Install

You need python3.

Windows

text
choco install python3
# REBOOT
pip3 install pip --upgrade
pip3 install json
python3 wekan.py

Debian/Ubuntu

text
sudo apt-get -y install python3 python3-pip python3-simplejson
sudo pip3 install pip --upgrade
chmod +x wekan.py
./wekan.py

Usage

Copy the api.py script to you machine. Newest Wekan Python CLI api.py here.

Then, in this script, look for and change:

Keep in mind your Wekan credentials are potentially accessible in this file.

Then call it without any argument to see if everything is all right. You should just get usage examples.


File: docs/API/REST-API.md

REST API is not complete yet, please add missing functionality with pull requests to devel branch.

If you are in a hurry, you can use these to have more functionality:

For workflows see If-this-then-that issue than mentions Huginn, Flogo etc.

Wekan REST API

The REST API allows you to control and extend Wekan with ease.

If you are an end-user and not a dev or a tester, create an issue to request new APIs.

All API calls in the documentation are made using curl. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.

Production Security Concerns

When calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:

  • Only call via HTTPS
  • Implement a timed authorization token expiration strategy
  • Ensure the calling user only has permissions for what they are calling and no more

Summary

Authentication

HTTP Method Url Short Description
POST /users/login Authenticate with the REST API.

Users

HTTP Method Url Short Description
POST /users/register Register a new user.
POST /api/users Create a new user.
PUT /api/users/:id Disable an existing user.
PUT /api/users/:id Enable an existing user.
PUT /api/users/:id Admin takes the ownership.
DELETE /api/users/:id Delete an existing user. (Warning)
GET /api/users/:id Gets a user's information.
GET /api/users All of the users.
GET /api/user Gets a logged-in user.

Cards

HTTP Method Url Short Description
POST /api/boards/:boardId/lists/:listId/cards Add a card to a list, board, and swimlane.
PUT /api/boards/:boardId/lists/:fromListId/cards/:cardId Update a card.
DELETE /api/boards/:boardId/lists/:listId/cards/:cardId Delete a card.

Rules (Board Automation / IFTTT)

HTTP Method Url Short Description
GET /api/boards/:boardId/rules List all automation rules of a board.
GET /api/boards/:boardId/rules/:ruleId Get one rule with its trigger and action.
POST /api/boards/:boardId/rules Add an automation rule, e.g. add/remove a member when a card is moved to/from a list (#2674).
PUT /api/boards/:boardId/rules/:ruleId Edit a rule's title, trigger and/or action.
DELETE /api/boards/:boardId/rules/:ruleId Remove a rule and its trigger and action.

Board Domain Sharing

HTTP Method Url Short Description
GET /api/boards/:boardId/domains List the email domains a board is shared with.
POST /api/boards/:boardId/domains Share a board with an email domain.
DELETE /api/boards/:boardId/domains/:domain Stop sharing a board with an email domain.

GlobalAdmin Settings

HTTP Method Url Short Description
GET /api/settings Read the Admin Panel global settings.
PUT /api/settings Update the Admin Panel global settings.

Attachments

HTTP Method Url Short Description
GET /api/boards/:boardId/attachments List the live attachments of a board.
GET /api/boards/:boardId/attachments/deleted List the soft-deleted attachments of a board.
DELETE /api/boards/:boardId/attachments/:attachmentId Soft-delete an attachment.
POST /api/boards/:boardId/attachments/:attachmentId/restore Restore a soft-deleted attachment.

Board Settings: card field order

HTTP Method Url Short Description
GET /api/boards/:boardId/cardFieldOrder Get the order of the opened card's sections.
PUT /api/boards/:boardId/cardFieldOrder Set the order of the opened card's sections.

Board Settings: Board View

HTTP Method Url Short Description
GET /api/boards/:boardId/boardViewSettings Get which Board View menu entries a board offers, its defaults and their order.
PUT /api/boards/:boardId/boardViewSettings Set which views are offered, which is the default, and the menu order.

Admin Panel: Problems

HTTP Method Url Short Description
GET /api/admin/problems The Problems status overview and new-problem counts.
GET /api/admin/problems/:stream One page of a problem stream.
POST /api/admin/problems/:stream/acknowledge Acknowledge a stream's new problems.

Admin Panel: OAuth login providers and passwordless

HTTP Method Url Short Description
GET /api/admin/oauth-providers Which source is in effect for every provider setting.
PUT /api/admin/oauth-providers/:providerKey Update one provider's Admin Panel settings.
PUT /api/admin/passwordless Turn passwordless login on or off.

Login

URL Requires Auth HTTP Method
/users/login no POST

Payload

Authentication with username

Argument Example Required Description
username myusername Required Your username
password my$up3erP@ssw0rd Required Your password

Authentication with email

Argument Example Required Description
email [email protected] Required Your email
password my$up3erP@ssw0rd Required Your password
  • Notes:
  • You will need to provide the token for any of the authenticated methods.

Example Call - As Form Data

DOES NOT WORK ! Please use As JSON example below !
https://github.com/wekan/wekan/issues/4807

bash
curl http://localhost:3000/users/login \
     -d "username=myusername&password=mypassword"
bash
curl http://localhost:3000/users/login \
     -d "[email protected]&password=mypassword"

Example Call - As JSON

THIS WORKS !! Alternatively, look at api.py example at https://github.com/wekan/wekan

NOTE: Username and password is case sensitive. So type BIG and small letters correctly.

bash
curl -H "Content-type:application/json" \
      http://localhost:3000/users/login \
      -d '{ "username": "myusername", "password": "mypassword" }'
bash
curl -H "Content-type:application/json" \
      http://localhost:3000/users/login \
      -d '{ "email": "[email protected]", "password": "mypassword" }'

Result

json
{
  "id": "user id",
  "token": "string",
  "tokenExpires": "ISO encoded date string"
}

Result example

json
{
  "id": "XQMZgynx9M79qTtQc",
  "token": "ExMp2s9ML1JNp_l11sIfINPT3wykZ1SsVwg-cnxKdc8",
  "tokenExpires": "2017-12-15T00:47:26.303Z"
}

Board Domain Sharing

Boards can be shared with every user on an email domain (for example everyone
with an @example.com address). These endpoints list, add and remove the domains a
board is shared with.

A board admin (or a site admin) is required to add or remove domains. Domains are
validated: they are stored lowercase, must contain a ., and must not contain @
or whitespace.

The matching api.py commands are boarddomains, addboarddomain and
removeboarddomain.

List board domains

URL Requires Auth HTTP Method
/api/boards/:boardId/domains yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/boards/abcd1234/domains

Add a board domain

URL Requires Auth (board admin) HTTP Method
/api/boards/:boardId/domains yes POST
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X POST \
     http://localhost:3000/api/boards/abcd1234/domains \
     -d '{ "domain": "example.com" }'

Remove a board domain

URL Requires Auth (board admin) HTTP Method
/api/boards/:boardId/domains/:domain yes DELETE
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -X DELETE \
     http://localhost:3000/api/boards/abcd1234/domains/example.com

GlobalAdmin Settings

These endpoints read and update the Admin Panel global settings (registration,
product name, logos, custom head / manifest, accessibility and support pages, and
so on). They are global-admin only.

Updates are applied through a field whitelist, so only the supported settings
fields can be changed. For security, mailServer / SMTP credentials are never
returned
by GET and are never writable by PUT.

The matching api.py commands are getsettings and editsettings <field> <value>.

Get settings

URL Requires Auth (global admin) HTTP Method
/api/settings yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/settings

Update settings

URL Requires Auth (global admin) HTTP Method
/api/settings yes PUT
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X PUT \
     http://localhost:3000/api/settings \
     -d '{ "productName": "My WeKan", "disableRegistration": true }'

Attachments

Deleting an attachment from a card is a soft delete (see
History.md ยง12): the file is kept,
the card and the minicard badge hide it, and the card history - or this API -
restores it. There is no hard delete of a single attachment over the API;
the one hard delete is deleting an archived board with Admin Panel / Problems /
Delete enabled. All three endpoints go through the same server methods the
card and the card history use, so the permission check, the cover unset and
the history row are the ones the UI gets.

List deleted attachments

URL Requires Auth (board access) HTTP Method
/api/boards/:boardId/attachments/deleted yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/boards/BOARDID/attachments/deleted

Result:

json
[
  {
    "attachmentId": "sKHd9a2vLQ7Wq3xPz",
    "attachmentName": "spec.pdf",
    "attachmentType": "application/pdf",
    "boardId": "BOARDID",
    "swimlaneId": "yhY4RJmoZ2P7hFjQm",
    "listId": "vFnjPvQHnKf2EzRfE",
    "cardId": "a2oXL5HGdVTZuTjxi",
    "deletedAt": "2026-09-11T10:12:41.203Z",
    "deletedBy": "USERID",
    "deleteBatchId": "attachment-1789107161203-USERID"
  }
]

The live attachments are listed by GET /api/boards/:boardId/attachments.

Soft-delete an attachment

URL Requires Auth (may edit the card) HTTP Method
/api/boards/:boardId/attachments/:attachmentId yes DELETE
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -X DELETE \
     http://localhost:3000/api/boards/BOARDID/attachments/ATTACHMENTID

Result: { "deleted": true, "batchId": "attachment-1789107161203-USERID" }.
Idempotent: an attachment that is already deleted is left as it is. If the
attachment was the card's cover, the cover is unset.

Restore an attachment

URL Requires Auth (may edit the card) HTTP Method
/api/boards/:boardId/attachments/:attachmentId/restore yes POST
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -X POST \
     http://localhost:3000/api/boards/BOARDID/attachments/ATTACHMENTID/restore

Result: { "restored": true } (false when the attachment was not deleted).
The cover is never re-set by a restore.


Board Settings: card field order

The opened card draws its Labels, Dates, Members, Custom Fields and Description
sections in the order Board Settings sets. Whatever is stored, the order in
effect always contains each of the five keys exactly once: unknown keys are
dropped, duplicates keep their first position, and a missing key is appended in
its default position.

Get the card field order

URL Requires Auth (board access) HTTP Method
/api/boards/:boardId/cardFieldOrder yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/boards/BOARDID/cardFieldOrder

Result:

json
{
  "cardFieldOrder": ["labels", "dates", "members", "customFields", "description"],
  "keys": ["labels", "dates", "members", "customFields", "description"]
}

Set the card field order

URL Requires Auth (board admin) HTTP Method
/api/boards/:boardId/cardFieldOrder yes PUT
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X PUT \
     http://localhost:3000/api/boards/BOARDID/cardFieldOrder \
     -d '{ "cardFieldOrder": ["description", "customFields", "labels", "dates", "members"] }'

Returns the order in effect, in the same shape as GET.


Board Settings: Board View

Which entries of the Board View menu a board offers and which one it
opens in, separately for when the board is public and when it is private, and
the order the menu lists them in - the Board View settings
popup over the API. Both endpoints answer the state in effect, the way the
menu renders it: every view key exactly once with both showOnPublic and
showOnPrivate explicit (a board that never opened the popup shows every
view), the two defaults resolved (missing or unknown reads as
board-view-swimlanes), and boardViewOrder made whole (unknown keys
dropped, missing views appended in default order). keys lists the known
view keys.

Get the Board View settings

URL Requires Auth (board access) HTTP Method
/api/boards/:boardId/boardViewSettings yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/boards/BOARDID/boardViewSettings

Result (shortened):

json
{
  "boardViewSettings": {
    "board-view-swimlanes": { "showOnPublic": true, "showOnPrivate": true },
    "board-view-lists": { "showOnPublic": true, "showOnPrivate": true },
    "board-view-table": { "showOnPublic": false, "showOnPrivate": true },
    "...": {}
  },
  "defaultPublicBoardView": "board-view-swimlanes",
  "defaultPrivateBoardView": "board-view-swimlanes",
  "boardViewOrder": ["board-view-swimlanes", "board-view-lists", "board-view-table", "..."],
  "keys": ["board-view-swimlanes", "board-view-lists", "board-view-table", "..."]
}

Set the Board View settings

URL Requires Auth (board admin) HTTP Method
/api/boards/:boardId/boardViewSettings yes PUT

The body is any subset of boardViewSettings, defaultPublicBoardView,
defaultPrivateBoardView and boardViewOrder; keys not sent are left as
they are. The rules are the popup's:

  • a default is always shown on its side: setting a default also ticks its
    Show box, and hiding a side's current default is refused with 400 -
    set another default first (or in the same request: defaults are applied
    before the show flags, so one request can move the default and hide the
    old one);
  • an unknown view key anywhere in the body is a 400, and nothing of that
    request is applied;
  • boardViewOrder is normalised so the menu lists every view exactly once:
    the keys sent come first in the order given, the rest follow in default
    order;
  • a body with none of the four keys is a 400.
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X PUT \
     http://localhost:3000/api/boards/BOARDID/boardViewSettings \
     -d '{
       "defaultPublicBoardView": "board-view-roadmap",
       "boardViewSettings": {
         "board-view-swimlanes": { "showOnPublic": false },
         "board-view-table": { "showOnPublic": false, "showOnPrivate": true }
       },
       "boardViewOrder": ["board-view-roadmap", "board-view-gantt"]
     }'

Returns the settings in effect, in the same shape as GET. A refusal:

json
{ "error": "boardViewSettings.board-view-swimlanes.showOnPublic: cannot hide the public default view; set defaultPublicBoardView to another view first" }

Admin Panel: Problems

The Admin Panel โ†’ Problems pages over the API, global-admin only and
read-only apart from acknowledging a stream. Each row of a stream is a
summary that accumulates (count, firstAt..at, actors), never a row per
event - see Problems.

Problems overview

URL Requires Auth (global admin) HTTP Method
/api/admin/problems yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/admin/problems

Result:

json
{
  "inProgress": [{ "kind": "board-repair", "active": true, "message": "Board data-repair โ€” 12/146 boards" }],
  "problems": [{ "id": "broken-cards", "severity": "warning", "count": 3, "title": "Broken cards", "detail": "..." }],
  "newProblems": [{ "stream": "security", "count": 2 }, { "stream": "database", "count": 1 }],
  "streams": ["security", "speed", "tests", "cpu", "database", "integrity"]
}

Problem stream

URL Requires Auth (global admin) HTTP Method
/api/admin/problems/:stream yes GET

:stream is one of security, speed, tests, cpu, database,
integrity or api (the REST API usage report). Query parameters: limit
(1..200, default 50), skip (default 0) and search (case-insensitive text
over the columns the page shows).

bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     "http://localhost:3000/api/admin/problems/database?limit=20&search=disk"

Result: { "stream": "database", "total": 1, "limit": 20, "skip": 0, "rows": [ ... ] }.

Acknowledge a stream

URL Requires Auth (global admin) HTTP Method
/api/admin/problems/:stream/acknowledge yes POST
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -X POST \
     http://localhost:3000/api/admin/problems/security/acknowledge

Result: { "acknowledged": "security" }. The overview then reports zero new
problems for the stream until the next one is recorded.


Admin Panel: OAuth login providers and passwordless

The Admin Panel / People / Login section for Meteor's own accounts-*
login services (Google, GitHub, Facebook, X/Twitter, Meteor Developer, Weibo,
Meetup) and passwordless email codes - see
OAuth Providers and
Passwordless. Global-admin only. A
value saved here wins over the OAUTH_* / PASSWORDLESS_ENABLED environment
variable and takes effect without a restart. A provider's secret is never
returned
: it is reported only as { "source", "hasValue" }.

Get OAuth provider settings

URL Requires Auth (global admin) HTTP Method
/api/admin/oauth-providers yes GET
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     http://localhost:3000/api/admin/oauth-providers

Result (source is env, admin or unset):

json
{
  "providers": {
    "google": {
      "enabled": { "source": "admin", "value": true },
      "id": { "source": "env", "value": "1234.apps.googleusercontent.com" },
      "secret": { "source": "env", "hasValue": true }
    },
    "github": { "enabled": { "source": "unset", "value": null }, "id": { "source": "unset", "value": null }, "secret": { "source": "unset", "hasValue": false } }
  },
  "loginStyle": { "source": "env", "value": "popup" },
  "mergeExistingUsers": { "source": "unset", "value": null },
  "passwordless": { "source": "admin", "value": false }
}

Update an OAuth provider

URL Requires Auth (global admin) HTTP Method
/api/admin/oauth-providers/:providerKey yes PUT

:providerKey is one of google, github, facebook, twitter,
meteor-developer, weibo, meetup. Body fields, all optional: enabled,
id, secret (an empty or missing secret keeps the stored one), loginStyle
(popup or redirect), and the two settings shared by every provider,
globalLoginStyle and mergeExistingUsers.

bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X PUT \
     http://localhost:3000/api/admin/oauth-providers/github \
     -d '{ "enabled": true, "id": "Iv1.abc123", "secret": "...", "loginStyle": "redirect" }'

Returns the same source report as GET - without the secret.

Passwordless

URL Requires Auth (global admin) HTTP Method
/api/admin/passwordless yes PUT
bash
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
     -H "Content-type:application/json" \
     -X PUT \
     http://localhost:3000/api/admin/passwordless \
     -d '{ "enabled": true }'

Result: { "passwordless": { "source": "admin", "value": true } }.