> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup

# Configuration Management

> [!WARNING]
>
> **First time installing?** Follow the [Docker Compose installation guide](/developers/self-host/capabilities/docker-compose) to get Twenty running, then return here for configuration.

Twenty offers **two configuration modes** to suit different deployment needs:

**Admin panel access:** Only users with admin privileges (`canAccessFullAdminPanel: true`) can access the configuration interface.

## 1. Admin Panel Configuration (Default)

```bash theme={null}
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true  # default
```

**Most configuration happens through the UI** after installation:

1. Access your Twenty instance (usually `http://localhost:3000`)
2. Go to **Settings / Admin Panel / Configuration Variables**
3. Configure integrations, email, storage, and more
4. Changes take effect immediately (within 15 seconds for multi-container deployments)

> [!WARNING]
>
> **Multi-Container Deployments:** When using database configuration (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`), both server and worker containers read from the same database. Admin panel changes affect both automatically, eliminating the need to duplicate environment variables between containers (except for infrastructure variables).

**What you can configure through the admin panel:**

* **Authentication** - Google/Microsoft OAuth, password settings
* **Email** - SMTP settings, templates, verification
* **Storage** - S3 configuration, local storage paths
* **Integrations** - Gmail, Google Calendar, Microsoft services
* **Workflow & Rate Limiting** - Execution limits, API throttling
* **And much more...**

![Admin Panel Configuration Variables](https://mintcdn.com/twenty/GMeQVDsw5ST_LXpE/images/user-guide/setup/admin-panel-config-variables.png?fit=max\&auto=format\&n=GMeQVDsw5ST_LXpE\&q=85\&s=90c73bf43dbdef9a4f78285354247c13)

> [!WARNING]
>
> Each variable is documented with descriptions in your admin panel at **Settings → Admin Panel → Configuration Variables**.
> Some infrastructure settings like database connections (`PG_DATABASE_URL`), server URLs (`SERVER_URL`), and secrets (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) can only be configured via `.env` file.
>
> [Complete technical reference →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)

## Encryption keys

Twenty uses two env-only encryption keys:

| Variable | Purpose | Required |
| - | - | - |
| `ENCRYPTION_KEY` | Primary key used to encrypt secrets at rest (OAuth tokens, application variables, signing-key private keys, TOTP secrets, sensitive config values). | Yes for new installs (legacy installs may instead rely on `APP_SECRET` — see below) |
| `FALLBACK_ENCRYPTION_KEY` | Verification-only key. Set during a rotation to the *previous* `ENCRYPTION_KEY` so existing rows remain decryptable. | Only during rotation |

For backward compatibility, if `ENCRYPTION_KEY` is unset, Twenty falls back to `APP_SECRET` for at-rest encryption — matching the legacy behaviour for older deployments. New installs should always set a dedicated `ENCRYPTION_KEY`.

Generate values with `openssl rand -base64 32` and store them somewhere safe (a secrets manager, sealed config, etc.). Losing `ENCRYPTION_KEY` means losing access to every secret stored in the database.

To rotate `ENCRYPTION_KEY` without downtime, see the [Key rotation guide](/developers/self-host/capabilities/key-rotation).

## 2. Environment-Only Configuration

```bash theme={null}
IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
```

**All configuration managed through `.env` files:**

1. Set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` in your `.env` file
2. Add all configuration variables to your `.env` file
3. Restart containers for changes to take effect
4. Admin panel will show current values but cannot modify them

## Multi-Workspace Mode

By default, Twenty runs in **single-workspace mode** — ideal for most self-hosted deployments where you need one CRM instance for your organization.

### Single-Workspace Mode (Default)

```bash theme={null}
IS_MULTIWORKSPACE_ENABLED=false  # default
```

* One workspace per Twenty instance
* First user automatically becomes admin with full privileges (`canImpersonate` and `canAccessFullAdminPanel`)
* New signups are disabled after the first workspace is created
* Simple URL structure: `https://your-domain.com`

### Enabling Multi-Workspace Mode

```bash theme={null}
IS_MULTIWORKSPACE_ENABLED=true
DEFAULT_SUBDOMAIN=app  # default value
```

Enable multi-workspace mode for SaaS-like deployments where multiple independent teams need their own workspaces on the same Twenty instance.

**Key differences from single-workspace mode:**

* Multiple workspaces can be created on the same instance
* Each workspace gets its own subdomain (e.g., `sales.your-domain.com`, `marketing.your-domain.com`)
* Users sign up and log in at `{DEFAULT_SUBDOMAIN}.your-domain.com` (e.g., `app.your-domain.com`)
* No automatic admin privileges — first user in each workspace is a regular user
* Workspace-specific settings like subdomain and custom domain become available in workspace settings

> [!WARNING]
>
> **Environment-only setting:** `IS_MULTIWORKSPACE_ENABLED` can only be configured via `.env` file and requires a restart. It cannot be changed through the admin panel.

### DNS Configuration for Multi-Workspace

When using multi-workspace mode, configure your DNS with a wildcard record to allow dynamic subdomain creation:

```
*.your-domain.com -> your-server-ip
```

This enables automatic subdomain routing for new workspaces without manual DNS configuration.

### Restricting Workspace Creation

In multi-workspace mode, you may want to limit who can create new workspaces:

```bash theme={null}
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
```

When enabled, only users with `canAccessFullAdminPanel` can create additional workspaces. Users can still create their first workspace during initial signup.

## Gmail & Google Calendar Integration

### Create Google Cloud Project

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing one
3. Enable these APIs:

* [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)
* [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)
* [People API](https://console.cloud.google.com/apis/library/people.googleapis.com)

### Configure OAuth

1. Go to [Credentials](https://console.cloud.google.com/apis/credentials)
2. Create OAuth 2.0 Client ID
3. Add these redirect URIs:
   * `https://{your-domain}/auth/google/redirect` (for SSO)
   * `https://{your-domain}/auth/google-apis/get-access-token` (for integrations)

### Configure in Twenty

1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Google Auth** section
3. Set these variables:
   * `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
   * `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
   * `AUTH_GOOGLE_CLIENT_ID={client-id}`
   * `AUTH_GOOGLE_CLIENT_SECRET={client-secret}`
   * `AUTH_GOOGLE_CALLBACK_URL=https://{your-domain}/auth/google/redirect`
   * `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`

> [!WARNING]
>
> **Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.

**Required scopes** (automatically configured):
[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)

* `https://www.googleapis.com/auth/calendar.events`
* `https://www.googleapis.com/auth/gmail.readonly`
* `https://www.googleapis.com/auth/profile.emails.read`

### If your app is in test mode

If your app is in test mode, you will need to add test users to your project.

Under [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent), add your test users to the "Test users" section.

## Microsoft 365 Integration

> [!WARNING]
>
> Users must have a [Microsoft 365 Licence](https://admin.microsoft.com/Adminportal/Home) to be able to use the Calendar and Messaging API. They will not be able to sync their account on Twenty without one.

### Create a project in Microsoft Azure

You will need to create a project in [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) and get the credentials.

### Enable APIs

On Microsoft Azure Console enable the following APIs in "Permissions":

* Microsoft Graph: Mail.ReadWrite
* Microsoft Graph: Mail.Send
* Microsoft Graph: Calendars.ReadWrite
* Microsoft Graph: User.Read
* Microsoft Graph: openid
* Microsoft Graph: email
* Microsoft Graph: profile
* Microsoft Graph: offline\_access

> [!NOTE]
>
> All of these permissions are required, even if you only want to sync emails and calendar events. Twenty always requests "Mail.ReadWrite", "Mail.Send" and "Calendars.ReadWrite" when a user connects their account, so read-only permissions like "Mail.Read" or "Calendars.Read" are not enough.

> [!NOTE]
>
> If your tenant does not let regular users consent to these permissions, they will see error AADSTS90094 when connecting their account. An administrator can fix this by clicking "Grant admin consent for {your-tenant}" in the app registration's "API permissions" page.

### Authorized redirect URIs

You need to add the following redirect URIs to your project:

* `https://{your-domain}/auth/microsoft/redirect` if you want to use Microsoft SSO
* `https://{your-domain}/auth/microsoft-apis/get-access-token`

### Configure in Twenty

1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Microsoft Auth** section
3. Set these variables:
   * `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
   * `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
   * `AUTH_MICROSOFT_ENABLED=true`
   * `AUTH_MICROSOFT_CLIENT_ID={client-id}`
   * `AUTH_MICROSOFT_CLIENT_SECRET={client-secret}`
   * `AUTH_MICROSOFT_CALLBACK_URL=https://{your-domain}/auth/microsoft/redirect`
   * `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`

> [!WARNING]
>
> **Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.

### Configure scopes

[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L11)

* 'openid'
* 'email'
* 'profile'
* 'offline\_access'
* 'Mail.ReadWrite'
* 'Mail.Send'
* 'Calendars.ReadWrite'
* 'User.Read'

### If your app is in test mode

If your app is in test mode, you will need to add test users to your project.

Add your test users to the "Users and groups" section.

## Background Jobs for Calendar & Messaging

After configuring Gmail, Google Calendar, or Microsoft 365 integrations, you need to start the background jobs that sync data.

Register the following recurring jobs in your worker container:

```bash theme={null}
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
yarn command:prod cron:calendar:calendar-events-import
yarn command:prod cron:messaging:ongoing-stale
yarn command:prod cron:calendar:ongoing-stale
yarn command:prod cron:workflow:automated-cron-trigger
```

## Outbound Connections to Internal Hosts

By default, Twenty refuses outbound connections to private/internal IP addresses (SSRF protection). This covers HTTP workflow actions, webhooks, SSO identity providers and IMAP/SMTP/CalDAV connections. If your identity provider, mail or calendar server runs on a private network, list its hostname or IP literal:

```bash theme={null}
OUTBOUND_HTTP_ALLOWED_INTERNAL_HOSTS=keycloak,mail.internal,192.168.1.10
```

Entries are matched on the hostname only, without wildcards: for an OIDC issuer such as `http://keycloak:8080/realms/twenty`, list `keycloak`. Every other private address stays blocked, and even an allowed host can never reach the cloud metadata address range. Set the variable to `*` to disable the protection entirely, which is only appropriate on trusted, isolated networks.

`OUTBOUND_HTTP_SAFE_MODE_ENABLED` is deprecated. While it is set to `false`, the protection stays fully off and `OUTBOUND_HTTP_ALLOWED_INTERNAL_HOSTS` is ignored, so remove it when you migrate to the list.

This list does not cover AI file attachments: images and PDFs reach the model as links built from `SERVER_URL`, fetched either by the AI provider or by the AI SDK in the worker, which refuses private addresses. For attachments to work, `SERVER_URL` must be public; with a `localhost` or private `SERVER_URL`, AI chats and agents only work with text.

## Email Configuration

1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Email** section
3. Configure your SMTP settings:

**Gmail**

You will need to provision an [App Password](https://support.google.com/accounts/answer/185833).

* EMAIL\_DRIVER=smtp
* EMAIL\_SMTP\_HOST=smtp.gmail.com
* EMAIL\_SMTP\_PORT=465
* EMAIL\_SMTP\_USER=gmail\_email\_address
* EMAIL\_SMTP\_PASSWORD='gmail\_app\_password'

**Office365**

Keep in mind that if you have 2FA enabled, you will need to provision an [App Password](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).

* EMAIL\_DRIVER=smtp
* EMAIL\_SMTP\_HOST=smtp.office365.com
* EMAIL\_SMTP\_PORT=587
* EMAIL\_SMTP\_USER=office365\_email\_address
* EMAIL\_SMTP\_PASSWORD='office365\_password'

**smtp4dev**

**smtp4dev** is a fake SMTP email server for development and testing.

* Run the smtp4dev image: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* Access the smtp4dev ui here: [http://localhost:8090](http://localhost:8090)
* Set the following variables:
  * EMAIL\_DRIVER=smtp
  * EMAIL\_SMTP\_HOST=localhost
  * EMAIL\_SMTP\_PORT=2525

> [!WARNING]
>
> **Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.

## S3 Storage

> [!WARNING]
>
> By default, Twenty stores uploaded files on the local filesystem. For production deployments, use S3 or an S3-compatible service (MinIO, DigitalOcean Spaces, etc.) to ensure files persist across container restarts and scale across multiple server instances.

Set `STORAGE_TYPE=S_3` and configure the `STORAGE_S3_*` variables through the admin panel or `.env`.

| Variable | Required | Description |
| - | - | - |
| `STORAGE_S3_REGION` | Yes | Region of the bucket: an AWS region such as `eu-west-3`, or a provider-specific slug such as `fr-par` for Scaleway. |
| `STORAGE_S3_NAME` | Yes | Name of the bucket. |
| `STORAGE_S3_ACCESS_KEY_ID` | No | Access key of an IAM user. Leave unset to use the AWS default credential chain (see [Credentials](#credentials)). |
| `STORAGE_S3_SECRET_ACCESS_KEY` | No | Secret for the access key above. Required when `STORAGE_S3_ACCESS_KEY_ID` is set. |
| `STORAGE_S3_ENDPOINT` | No | Custom endpoint URL. Only for S3-compatible services such as `http://minio:9000`, or for an AWS VPC interface endpoint. Leave unset for native AWS S3, where the SDK derives the endpoint from the region. |
| `STORAGE_S3_PRESIGNED_URL_ENABLED` | No | When `true`, downloads redirect to presigned URLs and browsers upload straight to the bucket instead of going through the server. Defaults to `false`. |
| `STORAGE_S3_PRESIGNED_URL_BASE` | No | Public endpoint to use in presigned URLs when `STORAGE_S3_ENDPOINT` is an internal address browsers cannot reach, for example MinIO inside Docker. |
| `STORAGE_S3_PRESIGNED_URL_EXPIRES_IN` | No | Lifetime of presigned URLs in seconds. Defaults to `900`. |

A minimal `.env` for AWS S3 with an IAM user:

```ini theme={null}
STORAGE_TYPE=S_3
STORAGE_S3_REGION=eu-west-3
STORAGE_S3_NAME=my-twenty-bucket
STORAGE_S3_ACCESS_KEY_ID=AKIA...
STORAGE_S3_SECRET_ACCESS_KEY=...
```

> [!NOTE]
>
> The Docker Compose file forwards `STORAGE_TYPE`, `STORAGE_S3_REGION`, `STORAGE_S3_NAME`, `STORAGE_S3_ENDPOINT` and the two credential variables from `.env`. Set the presigned URL variables in the admin panel, or add them to the `environment` block of the `server` and `worker` services.

### Credentials

When `STORAGE_S3_ACCESS_KEY_ID` is unset, the server resolves credentials through the AWS SDK default chain: the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables, a shared credentials file, an ECS task role, an EKS service account role (IRSA), or an EC2 instance profile.

> [!WARNING]
>
> **EC2 instance profiles and Docker:** the instance profile is served by the instance metadata service, and a container on Docker's default bridge network is one network hop further from it than the host. With the default IMDSv2 hop limit of 1, the container never receives credentials even though the AWS CLI works on the host. Raise the limit to 2 on the instance, or use static credentials.

```bash theme={null}
aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 --http-put-response-hop-limit 2
```

When no credentials can be found, the server logs `CredentialsProviderError: Could not load credentials from any providers` on the first upload, while the UI only shows a generic error.

### Bucket permissions

An upload is first written to a pending path, validated, then copied to its final path, and the pending object is deleted. The policy therefore needs more than `s3:PutObject`:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-twenty-bucket"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::my-twenty-bucket/*"
    }
  ]
}
```

`s3:ListBucket` is used to delete folders, for example when a workspace is deleted. Without it, S3 answers requests for missing objects with access denied instead of not found, so the server cannot tell a missing file from a permissions problem.

### Endpoint and addressing

Twenty always uses path-style addressing, so requests go to `https://endpoint/bucket/key`. For native AWS S3 leave `STORAGE_S3_ENDPOINT` unset: the SDK builds `https://s3.{region}.amazonaws.com` from `STORAGE_S3_REGION`, which is why a mistyped region surfaces as a DNS error rather than an S3 error. Set the endpoint only for S3-compatible services, or when the server reaches S3 through a VPC interface endpoint.

### CORS

When `STORAGE_S3_PRESIGNED_URL_ENABLED` is `true`, browsers talk to the bucket directly. Downloads are `GET` requests, and uploads are `PUT` requests that carry a `Content-Type` header, which makes the browser send a preflight request first. The bucket's CORS policy must therefore allow your Twenty origin, both methods and that header:

```json theme={null}
[
  {
    "AllowedOrigins": ["https://crm.example.com"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedHeaders": ["Content-Type"],
    "MaxAgeSeconds": 3000
  }
]
```

Without presigned URLs, all file traffic goes through the server and the bucket needs no CORS configuration.

### Troubleshooting

Upload failures reach the UI as a generic error. The underlying S3 error is in the server logs (`docker compose logs server`) and in the GraphQL response visible in the browser's network tab.

## Logic Functions & Code Interpreter

Twenty supports logic functions for workflows and the code interpreter for AI data analysis. Both run user-provided code and require explicit configuration for security.

### Security Defaults

**In production (NODE\_ENV=production):** Both logic functions and code interpreter default to **Disabled**. You must explicitly enable them with `LOGIC_FUNCTION_TYPE` and `CODE_INTERPRETER_TYPE` if you need these features.

**In development (NODE\_ENV=development):** Both default to **LOCAL** for convenience when running locally.

> [!WARNING]
>
> **Security Notice:** The local driver (`LOGIC_FUNCTION_TYPE=LOCAL` or `CODE_INTERPRETER_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, use `LOGIC_FUNCTION_TYPE=LAMBDA` or `CODE_INTERPRETER_TYPE=E2B` (with sandboxing), or keep them disabled.

### Logic Functions - Available Drivers

| Driver | Environment Variable | Use Case | Security Level |
| - | - | - | - |
| Disabled | `LOGIC_FUNCTION_TYPE=DISABLED` | Disable logic functions entirely | N/A |
| Local | `LOGIC_FUNCTION_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |

### Logic Functions - Recommended Configuration

**For development:**

```bash theme={null}
LOGIC_FUNCTION_TYPE=LOCAL  # default when NODE_ENV=development
```

**For production (AWS):**

```bash theme={null}
LOGIC_FUNCTION_TYPE=LAMBDA
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```

**To disable logic functions:**

```bash theme={null}
LOGIC_FUNCTION_TYPE=DISABLED  # default when NODE_ENV=production
```

### Code Interpreter - Available Drivers

| Driver | Environment Variable | Use Case | Security Level |
| - | - | - | - |
| Disabled | `CODE_INTERPRETER_TYPE=DISABLED` | Disable AI code execution | N/A |
| Local | `CODE_INTERPRETER_TYPE=LOCAL` | Development only | Low (no sandboxing) |
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Production with sandboxed execution | High (isolated sandbox) |

> [!NOTE]
>
> When using `LOGIC_FUNCTION_TYPE=DISABLED` or `CODE_INTERPRETER_TYPE=DISABLED`, any attempt to execute will return an error. This is useful if you want to run Twenty without these capabilities.

## API and front-end on separate hosts

Only relevant if you do not let the back-end serve the front-end. Same-origin
deployments need nothing here.

Declare every browser origin that talks to the API, comma separated. `SERVER_URL`
and `FRONTEND_URL` are already trusted. The server logs a warning when a browser
calls it from an undeclared origin.

```bash theme={null}
AUTH_COOKIE_ALLOWED_ORIGINS=https://app.example.com
```

> [!WARNING]
>
> The front-end sends credentials on every request, and browsers reject a
> credentialed response from an undeclared origin, so the app fails to load until
> you set this.

If the two hosts are on different registrable domains (not just different
subdomains), also set `AUTH_COOKIE_SAME_SITE=none`, which requires HTTPS.

With multi-workspace enabled, do not list every workspace subdomain here.
Instead, route API paths (`/graphql`, `/metadata`, `/rest`, `/auth/`,
`/client-config`, `/file`) to the back-end on each workspace hostname, so every
workspace stays same-origin — this is how Twenty Cloud runs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.