Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
e8b286d
improvement(self-host): enterprise features enabling
icecrasher321 Jul 28, 2026
212f1de
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 28, 2026
3abf172
chore(helm): bump chart to 1.3.0 for the enterprise self-host values
icecrasher321 Jul 28, 2026
49981f5
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 28, 2026
a0ca8cb
fix(self-host): address review findings on instance org and org delete
icecrasher321 Jul 28, 2026
c55e5f1
fix(admin): block org delete on any live subscription, not just entit…
icecrasher321 Jul 29, 2026
a548903
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 29, 2026
d12c871
fix(self-host): resolve SSO and access-control in the UI, not the raw…
icecrasher321 Jul 29, 2026
1b80ae2
fix(admin): validate retention workspace targets on the Admin API too
icecrasher321 Jul 29, 2026
fb98a44
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 29, 2026
81b1849
fix(self-host): close three review findings on admin routes and cleanup
icecrasher321 Jul 29, 2026
7d55f38
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 29, 2026
29ab3d0
fix(admin): gate whitelabel on entitlement and emit detach audits pos…
icecrasher321 Jul 29, 2026
c4278bf
Merge remote-tracking branch 'origin/staging' into improvement/enterp…
icecrasher321 Jul 29, 2026
3390208
fix(self-host): refuse instance-org resolution when the slug is ambig…
icecrasher321 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions apps/docs/content/docs/en/platform/enterprise/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,17 @@ Clone a workspace into a linked child, then push or pull **deployed** workflow c

## Self-hosted setup

Self-hosted deployments enable enterprise features via environment variables instead of billing.

| Variable | Description |
|----------|-------------|
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Team and organization management |
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups |
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | SAML and OIDC sign-in |
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding |
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging |
| `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | Data retention configuration |
| `DATA_DRAINS_ENABLED`, `NEXT_PUBLIC_DATA_DRAINS_ENABLED` | Data drains |
| `FORKING_ENABLED`, `NEXT_PUBLIC_FORKING_ENABLED` | Workspace forking |
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox |
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Disable invitations; manage membership via Admin API |

Once enabled, each feature is configured through the same Settings UI as Sim Cloud. When invitations are disabled, use the Admin API (`x-admin-key` header) to manage organization membership and workspace access. Internal members join the organization; external workspace members only receive access to a specific workspace.
Self-hosted deployments unlock enterprise features through environment configuration instead of billing. One switch turns on the whole set:

```bash
ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
```

Each feature also keeps its own flag, so you can enable them one at a time or switch a single feature back off.

Most of these features read their settings from the organization that owns a workspace, so a deployment also needs an organization model — either one instance-wide organization that every user joins automatically, or organizations you provision yourself through the Admin API.

See the [self-hosted enterprise guide](/platform/enterprise/self-hosted) for the full variable list, both organization patterns, the Admin API reference, and troubleshooting.

Once enabled, each feature is configured through the same Settings UI as Sim Cloud. When invitations are disabled (`DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS`), use the Admin API (`x-admin-key` header) to manage organization membership and workspace access. Internal members join the organization; external workspace members only receive access to a specific workspace.
1 change: 1 addition & 0 deletions apps/docs/content/docs/en/platform/enterprise/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"title": "Enterprise",
"pages": [
"index",
"self-hosted",
"sso",
"verified-domains",
"session-policies",
Expand Down
211 changes: 211 additions & 0 deletions apps/docs/content/docs/en/platform/enterprise/self-hosted.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
---
title: Self-hosted Enterprise
description: Run the full enterprise feature set on a self-hosted deployment without billing
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'

On Sim Cloud, enterprise features are unlocked by an Enterprise subscription. Self-hosted deployments have no subscription, so they are unlocked by environment configuration instead.

There are two parts to getting this right, and skipping the second is the most common reason features appear to do nothing:

1. **Enable the features** with `ENTERPRISE_ENABLED`.
2. **Give them an organization to apply to.** Whitelabeling, PII redaction, permission groups, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from.

## Enable the feature set

Set the master switch and its client twin. Both are required — the server value decides access, and the `NEXT_PUBLIC_` value decides what the settings UI shows.

```bash
ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
```

That turns on organizations, permission groups, SSO, whitelabeling, audit logs, session policies, data retention, data drains, workspace forks, and the inbox.

### Turning one feature off

Every feature keeps its own flag, and an explicitly set flag always wins over the master switch. To run the suite without data drains:

```bash
ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
DATA_DRAINS_ENABLED=false
NEXT_PUBLIC_DATA_DRAINS_ENABLED=false
```

The individual flags also work on their own if you would rather opt in one at a time and leave the master switch unset.

| Feature | Server variable | Client variable |
|---------|-----------------|-----------------|
| Everything below | `ENTERPRISE_ENABLED` | `NEXT_PUBLIC_ENTERPRISE_ENABLED` |
| Organizations | `ORGANIZATIONS_ENABLED` | `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` |
| Permission groups | `ACCESS_CONTROL_ENABLED` | `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` |
| SAML and OIDC sign-in | `SSO_ENABLED` | `NEXT_PUBLIC_SSO_ENABLED` |
| Custom branding | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` |
| Audit logs | `AUDIT_LOGS_ENABLED` | `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` |
| Session policies | `SESSION_POLICIES_ENABLED` | `NEXT_PUBLIC_SESSION_POLICIES_ENABLED` |
| Data retention deletion | `DATA_RETENTION_ENABLED` | `NEXT_PUBLIC_DATA_RETENTION_ENABLED` |
| Data drains | `DATA_DRAINS_ENABLED` | `NEXT_PUBLIC_DATA_DRAINS_ENABLED` |
| Workspace forks | `FORKING_ENABLED` | — |
| Sim Mailer inbox | `INBOX_ENABLED` | `NEXT_PUBLIC_INBOX_ENABLED` |

<Callout type="warning">
Data retention is the one feature that deletes data. Its flag controls the cleanup pass, not the settings screen — retention windows are always configurable. Nothing is ever deleted until you enable it, and even then only against windows you configured explicitly. Sim never applies the hosted plan defaults to a self-hosted deployment.
</Callout>

## Choose an organization model

### Pattern 1: one organization for the whole instance

Best when everyone on the deployment belongs to the same company. Set a name and every user joins that organization automatically at signup, with their workspaces created org-owned.

```bash
INSTANCE_ORG_NAME="Acme Inc"
```

Optionally pin the slug and the owner:

```bash
INSTANCE_ORG_SLUG=acme-inc
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com
```

The organization is created the first time a user signs up. If `INSTANCE_ORG_OWNER_EMAIL` is not set, or names a user who does not exist yet, the first user to sign up becomes the owner; move ownership later with the Admin API. Provisioning is idempotent and safe across multiple replicas.

<Callout type="info">
Instance-organization mode only applies when billing is disabled. With billing enabled, organizations are created through the normal subscription flow and these variables are ignored.
</Callout>

#### Existing deployments

Users and workspaces created before you set `INSTANCE_ORG_NAME` stay where they are. Move them across once with the backfill script, which adds every user to the organization and attaches their workspaces:

```bash
# Preview
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
bun run apps/sim/scripts/consolidate-users-into-organization.ts

# Apply
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
bun run apps/sim/scripts/consolidate-users-into-organization.ts --apply
```

It is a dry run unless you pass `--apply`, and it is safe to re-run. Users who already belong to a different organization are reported and skipped, since a user can only belong to one.

### Pattern 2: many organizations you manage yourself

Best when one deployment serves several teams that should not see each other's data. Leave `INSTANCE_ORG_NAME` unset and provision organizations through the Admin API.

Set an admin key first:

```bash
ADMIN_API_KEY=$(openssl rand -hex 32)
```

<Steps>
<Step>
### Create an organization

The owner must not already belong to another organization.

```bash
curl -X POST https://sim.example.com/api/v1/admin/organizations \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Inc", "ownerId": "user_123", "slug": "acme-inc"}'
```
</Step>

<Step>
### Add members

```bash
curl -X POST https://sim.example.com/api/v1/admin/organizations/$ORG_ID/members \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userId": "user_456", "role": "member"}'
```
</Step>

<Step>
### Move a workspace into the organization

Organization-scoped features only apply to workspaces the organization owns.

```bash
curl -X POST https://sim.example.com/api/v1/admin/dashboard/workspaces/$WORKSPACE_ID/move \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"destinationOrganizationId\": \"$ORG_ID\"}"
```
</Step>

<Step>
### Configure organization settings

Branding, retention, and session policies can be set from the API instead of the UI.

<Tabs items={['Whitelabel', 'Data retention', 'Session policy']}>
<Tab value="Whitelabel">
```bash
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/whitelabel \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"brandName": "Acme AI", "hidePoweredBySim": true}'
```
</Tab>
<Tab value="Data retention">
```bash
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/data-retention \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"logRetentionHours": 2160}'
```
</Tab>
<Tab value="Session policy">
```bash
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/session-policy \
-H "x-admin-key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"maxSessionHours": 168, "idleTimeoutHours": 48}'
```
</Tab>
</Tabs>
</Step>
</Steps>

Deleting an organization requires echoing its slug, because the delete cascades to members, invitations, and permission groups, and detaches its workspaces:

```bash
curl -X DELETE "https://sim.example.com/api/v1/admin/organizations/$ORG_ID?confirmSlug=acme-inc" \
-H "x-admin-key: $ADMIN_API_KEY"
```

## Verifying it worked

If a feature is enabled but nothing appears, check these in order.

**The settings section is missing.** The `NEXT_PUBLIC_` twin is not set, or the app was not restarted after adding it. Client variables are read at build and boot.

**The section appears but the API returns 403.** The server-side variable is missing while its client twin is set. Set both.

**The feature is on but has no effect inside a workspace.** The workspace is not owned by an organization. Check `workspace_mode` and `organization_id`:

```sql
SELECT id, name, workspace_mode, organization_id FROM workspace;
```

A workspace showing `personal` or a null `organization_id` will not pick up branding, PII redaction, permission groups, or drains. Use the backfill script or the workspace move endpoint.

**Retention is configured but nothing is deleted.** `DATA_RETENTION_ENABLED` is unset. Configuring windows and running the cleanup pass are separate switches by design.

## Related

- [Environment variables](/platform/self-hosting/environment-variables)
- [Single Sign-On](/platform/enterprise/sso)
- [Access control](/platform/enterprise/access-control)
- [Roles and permissions](/platform/permissions)
- [Workspaces](/platform/workspaces)
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ import { Callout } from 'fumadocs-ui/components/callout'
| `ALLOWED_LOGIN_EMAILS` | Restrict signups to specific emails (comma-separated) |
| `DISABLE_REGISTRATION` | Set to `true` to disable new user signups |

## Enterprise Features

Enterprise features are unlocked by configuration rather than billing on self-hosted deployments. One switch turns on the full set; per-feature flags below it override the switch either way.

| Variable | Description |
|----------|-------------|
| `ENTERPRISE_ENABLED`, `NEXT_PUBLIC_ENTERPRISE_ENABLED` | Enable the whole enterprise feature set |
| `INSTANCE_ORG_NAME` | Name of the organization every user joins automatically at signup |
| `INSTANCE_ORG_SLUG` | Slug for that organization (derived from the name when omitted) |
| `INSTANCE_ORG_OWNER_EMAIL` | Owner of that organization (defaults to the first user to sign up) |

Most enterprise features read their settings from the organization that owns a workspace, so enabling the flags alone is not enough — the deployment also needs an organization model. See the [self-hosted enterprise guide](/platform/enterprise/self-hosted) for the per-feature flags, both organization patterns, and the Admin API.

## File Storage

By default Sim writes uploads to local disk. For production, point it at AWS S3, Azure Blob, or Google Cloud Storage. See [Object Storage](/platform/self-hosting/object-storage) for the full setup, bucket layout, and IAM policy.
Expand Down
13 changes: 13 additions & 0 deletions apps/docs/content/docs/en/platform/self-hosting/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ Open [http://localhost:3000](http://localhost:3000)
</Card>
</Cards>

## Enterprise Features

Organizations, SSO, permission groups, audit logs, whitelabeling, session policies, data retention, and data drains all run on a self-hosted deployment — no billing or subscription required. One switch turns on the set:

```bash
ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
```

Most of these features read their settings from the organization that owns a workspace, so enabling the flags is only half of it — your deployment also needs an organization model. Set `INSTANCE_ORG_NAME` to put every user in one shared organization automatically, or provision organizations yourself through the Admin API.

See the [self-hosted enterprise guide](/platform/enterprise/self-hosted) for both patterns, the per-feature flags, and troubleshooting.

## Architecture

| Component | Port | Description |
Expand Down
28 changes: 28 additions & 0 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,34 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
# Usage: curl -H "x-admin-key: your_key" https://your-instance/api/v1/admin/workspaces

# Enterprise Features (Optional - self-hosted). One switch enables organizations, SSO,
# permission groups, audit logs, whitelabeling, session policies, data retention, data
# drains, forks, and the inbox. Set both — the server value grants access, the
# NEXT_PUBLIC_ value decides what the settings UI shows.
# Docs: https://docs.sim.ai/platform/enterprise/self-hosted
# ENTERPRISE_ENABLED=true
# NEXT_PUBLIC_ENTERPRISE_ENABLED=true

# Per-feature overrides. An explicitly set flag wins over ENTERPRISE_ENABLED, so use these
# to enable one feature on its own or to switch a single feature back off.
# ACCESS_CONTROL_ENABLED= / NEXT_PUBLIC_ACCESS_CONTROL_ENABLED= # Permission groups
# SSO_ENABLED= / NEXT_PUBLIC_SSO_ENABLED= # SAML and OIDC sign-in
# WHITELABELING_ENABLED= / NEXT_PUBLIC_WHITELABELING_ENABLED= # Custom branding
# AUDIT_LOGS_ENABLED= / NEXT_PUBLIC_AUDIT_LOGS_ENABLED= # Audit logging
# SESSION_POLICIES_ENABLED= / NEXT_PUBLIC_SESSION_POLICIES_ENABLED=
# DATA_RETENTION_ENABLED= / NEXT_PUBLIC_DATA_RETENTION_ENABLED= # Runs retention deletion — off by default
# DATA_DRAINS_ENABLED= / NEXT_PUBLIC_DATA_DRAINS_ENABLED= # Export streams
# FORKING_ENABLED= # Workspace forks
# ORGANIZATIONS_ENABLED= / NEXT_PUBLIC_ORGANIZATIONS_ENABLED= # Organizations only

# Instance organization (Optional). Most enterprise features read their settings from the
# organization that owns a workspace, so a deployment needs an organization for them to
# apply. Setting a name puts every user in one shared org at signup and makes their
# workspaces org-owned. Leave unset to manage organizations yourself via the Admin API.
# INSTANCE_ORG_NAME=Acme Inc
# INSTANCE_ORG_SLUG=acme-inc # Optional — derived from the name when unset
# INSTANCE_ORG_OWNER_EMAIL=admin@acme.com # Optional — defaults to the first user to sign up

# Limits (Optional - self-hosted). With billing disabled (BILLING_ENABLED unset), no plan
# limits are enforced. Explicitly setting a free-tier variable below opts that specific
# limit back in at the configured value.
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(auth)/components/sso-login-button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'
import { Chip, cn } from '@sim/emcn'
import { useRouter } from 'next/navigation'
import { getEnv, isTruthy } from '@/lib/core/config/env'
import { isSsoEnabled } from '@/lib/core/config/env-flags'
import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'

interface SSOLoginButtonProps {
Expand All @@ -17,7 +17,7 @@ export function SSOLoginButton({
}: SSOLoginButtonProps) {
const router = useRouter()

if (!isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))) {
if (!isSsoEnabled) {
return null
}

Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/(auth)/login/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { useRouter, useSearchParams } from 'next/navigation'
import { requestJson } from '@/lib/api/client/request'
import { forgetPasswordContract } from '@/lib/api/contracts'
import { client } from '@/lib/auth/auth-client'
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
import { getEnv, isFalsy } from '@/lib/core/config/env'
import { isSsoEnabled } from '@/lib/core/config/env-flags'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
Expand Down Expand Up @@ -343,7 +344,7 @@ export default function LoginPage({
}
}

const ssoEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))
const ssoEnabled = isSsoEnabled
const emailEnabled = !isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED'))
const hasSocial = githubAvailable || googleAvailable || microsoftAvailable
const hasOnlySSO = ssoEnabled && !emailEnabled && !hasSocial
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/(auth)/signup/signup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { createLogger } from '@sim/logger'
import { useRouter, useSearchParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { client, useSession } from '@/lib/auth/auth-client'
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
import { getEnv, isFalsy } from '@/lib/core/config/env'
import { isSsoEnabled } from '@/lib/core/config/env-flags'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { captureClientEvent, captureEvent } from '@/lib/posthog/client'
Expand Down Expand Up @@ -360,7 +361,7 @@ function SignupFormContent({
}
}

const ssoEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))
const ssoEnabled = isSsoEnabled
const emailEnabled =
!isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED')) && emailSignupEnabled
const hasSocial = githubAvailable || googleAvailable || microsoftAvailable
Expand Down
Loading
Loading