Abstract


  • An open-source CRM (Customer Relationship Management) platform that can be self-hosted with Docker Compose
  • Composed of four services: server (Node.js API + frontend), worker (background jobs), db (PostgreSQL 16), and redis (event streams and caching)

TwentyConfigService Precedence


  • Twenty reads configuration through TwentyConfigService, which follows a three-tier override chain:
    1. Env-only config vars are always read from environment variables
    2. Database values (stored in core."keyValuePair" table) override environment variables
    3. Environment variables are the fallback if no database entry exists

Database overrides env vars silently

If a config value exists in the core."keyValuePair" table, the environment variable in env_file or environment is completely ignored. This can cause confusing bugs where changing the .env file has no effect.

Check database config when env changes don't take effect

SELECT key, value FROM core."keyValuePair" WHERE key LIKE '%YOUR_CONFIG%';

Update with:

UPDATE core."keyValuePair" SET value='"new_value"' WHERE key='YOUR_CONFIG_KEY';

Note the double quoting: the value column stores JSON, so strings need '"value"'.

Google OAuth Setup


Env varEndpointFlow
AUTH_GOOGLE_CALLBACK_URL/auth/google/redirectLogin SSO (authenticate user into Twenty)
AUTH_GOOGLE_APIS_CALLBACK_URL/auth/google-apis/get-access-tokenData integration (Gmail + Calendar sync)
  • Both URIs must be registered in Google Cloud Console > APIs & Services > Credentials > OAuth 2.0 Client IDs > Authorized redirect URIs for the same client

Debugging the No Payload Error


  • Symptom: clicking “Sign in with Google” on Twenty’s login page produces a cryptic “No payload” error in the frontend and a JWT decode failure server-side
  • Root cause: the two AUTH_GOOGLE_*_CALLBACK_URL values are swapped. Twenty sends users to Google with the wrong redirect_uri, Google returns the auth code to the wrong endpoint, and the handler tries to exchange a code meant for a different flow

The swap can live in the database, NOT just .env

Because of TwentyConfigService Precedence, DB values in core."keyValuePair" silently override environment variables. Editing .env and restarting will have no effect if the DB rows still hold the swapped values. Always fix the DB directly.

Step 1: Verify the Runtime Redirect URI

  • Check what Twenty is actually sending to Google
curl -sk -o /dev/null -w '%{redirect_url}\n' \
  'https://your-twenty-domain/auth/google'
  • URL-decode the redirect_uri parameter in the returned Google URL. It MUST point to /auth/google/redirect. If it points to /auth/google-apis/get-access-token, the values are swapped

Step 2: Check the Database

SELECT key, value FROM core."keyValuePair"
WHERE key IN ('AUTH_GOOGLE_CALLBACK_URL', 'AUTH_GOOGLE_APIS_CALLBACK_URL');

Step 3: Fix the Swap in the Database

BEGIN;
UPDATE core."keyValuePair"
  SET value = '"https://your-twenty-domain/auth/google/redirect"'
  WHERE key = 'AUTH_GOOGLE_CALLBACK_URL';
UPDATE core."keyValuePair"
  SET value = '"https://your-twenty-domain/auth/google-apis/get-access-token"'
  WHERE key = 'AUTH_GOOGLE_APIS_CALLBACK_URL';
COMMIT;
  • Note the double quoting: the value column stores JSON, so string values need '"..."'

Step 4: Restart the Server

docker compose restart server
  • Twenty reads config into memory on startup, so a restart is required for the fix to take effect. The worker container does NOT need to restart for the login flow

Deployment Behind Traefik


  • Twenty’s server exposes port 3000 internally and is routed via Traefik using Docker labels
  • If the Twenty instance does its own OAuth 2.0 login, do not apply Traefik’s forward-auth middleware to its router, as the two auth flows will conflict

References