Skip to content

Commit ed59ca8

Browse files
merge: staging into feat/table-views
Route-count baseline conflict: staging moved to 984 on its own; merged truth with this branch's 2 view routes is 986, confirmed by running the audit.
2 parents b49a061 + c809845 commit ed59ca8

60 files changed

Lines changed: 3446 additions & 309 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/en/platform/enterprise/index.mdx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,17 @@ Clone a workspace into a linked child, then push or pull **deployed** workflow c
8181

8282
## Self-hosted setup
8383

84-
Self-hosted deployments enable enterprise features via environment variables instead of billing.
85-
86-
| Variable | Description |
87-
|----------|-------------|
88-
| `ORGANIZATIONS_ENABLED`, `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` | Team and organization management |
89-
| `ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` | Permission groups |
90-
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | SAML and OIDC sign-in |
91-
| `WHITELABELING_ENABLED`, `NEXT_PUBLIC_WHITELABELING_ENABLED` | Custom branding |
92-
| `AUDIT_LOGS_ENABLED`, `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | Audit logging |
93-
| `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | Data retention configuration |
94-
| `DATA_DRAINS_ENABLED`, `NEXT_PUBLIC_DATA_DRAINS_ENABLED` | Data drains |
95-
| `FORKING_ENABLED`, `NEXT_PUBLIC_FORKING_ENABLED` | Workspace forking |
96-
| `INBOX_ENABLED`, `NEXT_PUBLIC_INBOX_ENABLED` | Sim Mailer inbox |
97-
| `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Disable invitations; manage membership via Admin API |
98-
99-
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.
84+
Self-hosted deployments unlock enterprise features through environment configuration instead of billing. One switch turns on the whole set:
85+
86+
```bash
87+
ENTERPRISE_ENABLED=true
88+
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
89+
```
90+
91+
Each feature also keeps its own flag, so you can enable them one at a time or switch a single feature back off.
92+
93+
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.
94+
95+
See the [self-hosted enterprise guide](/platform/enterprise/self-hosted) for the full variable list, both organization patterns, the Admin API reference, and troubleshooting.
96+
97+
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.

apps/docs/content/docs/en/platform/enterprise/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"title": "Enterprise",
33
"pages": [
44
"index",
5+
"self-hosted",
56
"sso",
67
"verified-domains",
78
"session-policies",
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
---
2+
title: Self-hosted Enterprise
3+
description: Run the full enterprise feature set on a self-hosted deployment without billing
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { Step, Steps } from 'fumadocs-ui/components/steps'
8+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
9+
10+
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.
11+
12+
There are two parts to getting this right, and skipping the second is the most common reason features appear to do nothing:
13+
14+
1. **Enable the features** with `ENTERPRISE_ENABLED`.
15+
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.
16+
17+
## Enable the feature set
18+
19+
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.
20+
21+
```bash
22+
ENTERPRISE_ENABLED=true
23+
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
24+
```
25+
26+
That turns on organizations, permission groups, SSO, whitelabeling, audit logs, session policies, data retention, data drains, workspace forks, and the inbox.
27+
28+
### Turning one feature off
29+
30+
Every feature keeps its own flag, and an explicitly set flag always wins over the master switch. To run the suite without data drains:
31+
32+
```bash
33+
ENTERPRISE_ENABLED=true
34+
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
35+
DATA_DRAINS_ENABLED=false
36+
NEXT_PUBLIC_DATA_DRAINS_ENABLED=false
37+
```
38+
39+
The individual flags also work on their own if you would rather opt in one at a time and leave the master switch unset.
40+
41+
| Feature | Server variable | Client variable |
42+
|---------|-----------------|-----------------|
43+
| Everything below | `ENTERPRISE_ENABLED` | `NEXT_PUBLIC_ENTERPRISE_ENABLED` |
44+
| Organizations | `ORGANIZATIONS_ENABLED` | `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` |
45+
| Permission groups | `ACCESS_CONTROL_ENABLED` | `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` |
46+
| SAML and OIDC sign-in | `SSO_ENABLED` | `NEXT_PUBLIC_SSO_ENABLED` |
47+
| Custom branding | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` |
48+
| Audit logs | `AUDIT_LOGS_ENABLED` | `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` |
49+
| Session policies | `SESSION_POLICIES_ENABLED` | `NEXT_PUBLIC_SESSION_POLICIES_ENABLED` |
50+
| Data retention deletion | `DATA_RETENTION_ENABLED` | `NEXT_PUBLIC_DATA_RETENTION_ENABLED` |
51+
| Data drains | `DATA_DRAINS_ENABLED` | `NEXT_PUBLIC_DATA_DRAINS_ENABLED` |
52+
| Workspace forks | `FORKING_ENABLED` ||
53+
| Sim Mailer inbox | `INBOX_ENABLED` | `NEXT_PUBLIC_INBOX_ENABLED` |
54+
55+
<Callout type="warning">
56+
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.
57+
</Callout>
58+
59+
## Choose an organization model
60+
61+
### Pattern 1: one organization for the whole instance
62+
63+
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.
64+
65+
```bash
66+
INSTANCE_ORG_NAME="Acme Inc"
67+
```
68+
69+
Optionally pin the slug and the owner:
70+
71+
```bash
72+
INSTANCE_ORG_SLUG=acme-inc
73+
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com
74+
```
75+
76+
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.
77+
78+
<Callout type="info">
79+
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.
80+
</Callout>
81+
82+
#### Existing deployments
83+
84+
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:
85+
86+
```bash
87+
# Preview
88+
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
89+
bun run apps/sim/scripts/consolidate-users-into-organization.ts
90+
91+
# Apply
92+
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
93+
bun run apps/sim/scripts/consolidate-users-into-organization.ts --apply
94+
```
95+
96+
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.
97+
98+
### Pattern 2: many organizations you manage yourself
99+
100+
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.
101+
102+
Set an admin key first:
103+
104+
```bash
105+
ADMIN_API_KEY=$(openssl rand -hex 32)
106+
```
107+
108+
<Steps>
109+
<Step>
110+
### Create an organization
111+
112+
The owner must not already belong to another organization.
113+
114+
```bash
115+
curl -X POST https://sim.example.com/api/v1/admin/organizations \
116+
-H "x-admin-key: $ADMIN_API_KEY" \
117+
-H "Content-Type: application/json" \
118+
-d '{"name": "Acme Inc", "ownerId": "user_123", "slug": "acme-inc"}'
119+
```
120+
</Step>
121+
122+
<Step>
123+
### Add members
124+
125+
```bash
126+
curl -X POST https://sim.example.com/api/v1/admin/organizations/$ORG_ID/members \
127+
-H "x-admin-key: $ADMIN_API_KEY" \
128+
-H "Content-Type: application/json" \
129+
-d '{"userId": "user_456", "role": "member"}'
130+
```
131+
</Step>
132+
133+
<Step>
134+
### Move a workspace into the organization
135+
136+
Organization-scoped features only apply to workspaces the organization owns.
137+
138+
```bash
139+
curl -X POST https://sim.example.com/api/v1/admin/dashboard/workspaces/$WORKSPACE_ID/move \
140+
-H "x-admin-key: $ADMIN_API_KEY" \
141+
-H "Content-Type: application/json" \
142+
-d "{\"destinationOrganizationId\": \"$ORG_ID\"}"
143+
```
144+
</Step>
145+
146+
<Step>
147+
### Configure organization settings
148+
149+
Branding, retention, and session policies can be set from the API instead of the UI.
150+
151+
<Tabs items={['Whitelabel', 'Data retention', 'Session policy']}>
152+
<Tab value="Whitelabel">
153+
```bash
154+
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/whitelabel \
155+
-H "x-admin-key: $ADMIN_API_KEY" \
156+
-H "Content-Type: application/json" \
157+
-d '{"brandName": "Acme AI", "hidePoweredBySim": true}'
158+
```
159+
</Tab>
160+
<Tab value="Data retention">
161+
```bash
162+
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/data-retention \
163+
-H "x-admin-key: $ADMIN_API_KEY" \
164+
-H "Content-Type: application/json" \
165+
-d '{"logRetentionHours": 2160}'
166+
```
167+
</Tab>
168+
<Tab value="Session policy">
169+
```bash
170+
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/session-policy \
171+
-H "x-admin-key: $ADMIN_API_KEY" \
172+
-H "Content-Type: application/json" \
173+
-d '{"maxSessionHours": 168, "idleTimeoutHours": 48}'
174+
```
175+
</Tab>
176+
</Tabs>
177+
</Step>
178+
</Steps>
179+
180+
Deleting an organization requires echoing its slug, because the delete cascades to members, invitations, and permission groups, and detaches its workspaces:
181+
182+
```bash
183+
curl -X DELETE "https://sim.example.com/api/v1/admin/organizations/$ORG_ID?confirmSlug=acme-inc" \
184+
-H "x-admin-key: $ADMIN_API_KEY"
185+
```
186+
187+
## Verifying it worked
188+
189+
If a feature is enabled but nothing appears, check these in order.
190+
191+
**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.
192+
193+
**The section appears but the API returns 403.** The server-side variable is missing while its client twin is set. Set both.
194+
195+
**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`:
196+
197+
```sql
198+
SELECT id, name, workspace_mode, organization_id FROM workspace;
199+
```
200+
201+
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.
202+
203+
**Retention is configured but nothing is deleted.** `DATA_RETENTION_ENABLED` is unset. Configuring windows and running the cleanup pass are separate switches by design.
204+
205+
## Related
206+
207+
- [Environment variables](/platform/self-hosting/environment-variables)
208+
- [Single Sign-On](/platform/enterprise/sso)
209+
- [Access control](/platform/enterprise/access-control)
210+
- [Roles and permissions](/platform/permissions)
211+
- [Workspaces](/platform/workspaces)

apps/docs/content/docs/en/platform/self-hosting/environment-variables.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ import { Callout } from 'fumadocs-ui/components/callout'
7070
| `ALLOWED_LOGIN_EMAILS` | Restrict signups to specific emails (comma-separated) |
7171
| `DISABLE_REGISTRATION` | Set to `true` to disable new user signups |
7272

73+
## Enterprise Features
74+
75+
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.
76+
77+
| Variable | Description |
78+
|----------|-------------|
79+
| `ENTERPRISE_ENABLED`, `NEXT_PUBLIC_ENTERPRISE_ENABLED` | Enable the whole enterprise feature set |
80+
| `INSTANCE_ORG_NAME` | Name of the organization every user joins automatically at signup |
81+
| `INSTANCE_ORG_SLUG` | Slug for that organization (derived from the name when omitted) |
82+
| `INSTANCE_ORG_OWNER_EMAIL` | Owner of that organization (defaults to the first user to sign up) |
83+
84+
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.
85+
7386
## File Storage
7487

7588
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.

apps/docs/content/docs/en/platform/self-hosting/index.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ Open [http://localhost:3000](http://localhost:3000)
5555
</Card>
5656
</Cards>
5757

58+
## Enterprise Features
59+
60+
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:
61+
62+
```bash
63+
ENTERPRISE_ENABLED=true
64+
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
65+
```
66+
67+
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.
68+
69+
See the [self-hosted enterprise guide](/platform/enterprise/self-hosted) for both patterns, the per-feature flags, and troubleshooting.
70+
5871
## Architecture
5972

6073
| Component | Port | Description |

apps/sim/.env.example

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,34 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
141141
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
142142
# Usage: curl -H "x-admin-key: your_key" https://your-instance/api/v1/admin/workspaces
143143

144+
# Enterprise Features (Optional - self-hosted). One switch enables organizations, SSO,
145+
# permission groups, audit logs, whitelabeling, session policies, data retention, data
146+
# drains, forks, and the inbox. Set both — the server value grants access, the
147+
# NEXT_PUBLIC_ value decides what the settings UI shows.
148+
# Docs: https://docs.sim.ai/platform/enterprise/self-hosted
149+
# ENTERPRISE_ENABLED=true
150+
# NEXT_PUBLIC_ENTERPRISE_ENABLED=true
151+
152+
# Per-feature overrides. An explicitly set flag wins over ENTERPRISE_ENABLED, so use these
153+
# to enable one feature on its own or to switch a single feature back off.
154+
# ACCESS_CONTROL_ENABLED= / NEXT_PUBLIC_ACCESS_CONTROL_ENABLED= # Permission groups
155+
# SSO_ENABLED= / NEXT_PUBLIC_SSO_ENABLED= # SAML and OIDC sign-in
156+
# WHITELABELING_ENABLED= / NEXT_PUBLIC_WHITELABELING_ENABLED= # Custom branding
157+
# AUDIT_LOGS_ENABLED= / NEXT_PUBLIC_AUDIT_LOGS_ENABLED= # Audit logging
158+
# SESSION_POLICIES_ENABLED= / NEXT_PUBLIC_SESSION_POLICIES_ENABLED=
159+
# DATA_RETENTION_ENABLED= / NEXT_PUBLIC_DATA_RETENTION_ENABLED= # Runs retention deletion — off by default
160+
# DATA_DRAINS_ENABLED= / NEXT_PUBLIC_DATA_DRAINS_ENABLED= # Export streams
161+
# FORKING_ENABLED= # Workspace forks
162+
# ORGANIZATIONS_ENABLED= / NEXT_PUBLIC_ORGANIZATIONS_ENABLED= # Organizations only
163+
164+
# Instance organization (Optional). Most enterprise features read their settings from the
165+
# organization that owns a workspace, so a deployment needs an organization for them to
166+
# apply. Setting a name puts every user in one shared org at signup and makes their
167+
# workspaces org-owned. Leave unset to manage organizations yourself via the Admin API.
168+
# INSTANCE_ORG_NAME=Acme Inc
169+
# INSTANCE_ORG_SLUG=acme-inc # Optional — derived from the name when unset
170+
# INSTANCE_ORG_OWNER_EMAIL=admin@acme.com # Optional — defaults to the first user to sign up
171+
144172
# Limits (Optional - self-hosted). With billing disabled (BILLING_ENABLED unset), no plan
145173
# limits are enforced. Explicitly setting a free-tier variable below opts that specific
146174
# limit back in at the configured value.

apps/sim/app/(auth)/components/sso-login-button.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22
import { Chip, cn } from '@sim/emcn'
33
import { useRouter } from 'next/navigation'
4-
import { getEnv, isTruthy } from '@/lib/core/config/env'
4+
import { isSsoEnabled } from '@/lib/core/config/env-flags'
55
import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
66

77
interface SSOLoginButtonProps {
@@ -17,7 +17,7 @@ export function SSOLoginButton({
1717
}: SSOLoginButtonProps) {
1818
const router = useRouter()
1919

20-
if (!isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))) {
20+
if (!isSsoEnabled) {
2121
return null
2222
}
2323

apps/sim/app/(auth)/login/login-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { useRouter, useSearchParams } from 'next/navigation'
1616
import { requestJson } from '@/lib/api/client/request'
1717
import { forgetPasswordContract } from '@/lib/api/contracts'
1818
import { client } from '@/lib/auth/auth-client'
19-
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
19+
import { getEnv, isFalsy } from '@/lib/core/config/env'
20+
import { isSsoEnabled } from '@/lib/core/config/env-flags'
2021
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
2122
import { getBaseUrl } from '@/lib/core/utils/urls'
2223
import { quickValidateEmail } from '@/lib/messaging/email/validation'
@@ -343,7 +344,7 @@ export default function LoginPage({
343344
}
344345
}
345346

346-
const ssoEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))
347+
const ssoEnabled = isSsoEnabled
347348
const emailEnabled = !isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED'))
348349
const hasSocial = githubAvailable || googleAvailable || microsoftAvailable
349350
const hasOnlySSO = ssoEnabled && !emailEnabled && !hasSocial

apps/sim/app/(auth)/signup/signup-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { createLogger } from '@sim/logger'
66
import { useRouter, useSearchParams } from 'next/navigation'
77
import { usePostHog } from 'posthog-js/react'
88
import { client, useSession } from '@/lib/auth/auth-client'
9-
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
9+
import { getEnv, isFalsy } from '@/lib/core/config/env'
10+
import { isSsoEnabled } from '@/lib/core/config/env-flags'
1011
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
1112
import { quickValidateEmail } from '@/lib/messaging/email/validation'
1213
import { captureClientEvent, captureEvent } from '@/lib/posthog/client'
@@ -360,7 +361,7 @@ function SignupFormContent({
360361
}
361362
}
362363

363-
const ssoEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED'))
364+
const ssoEnabled = isSsoEnabled
364365
const emailEnabled =
365366
!isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED')) && emailSignupEnabled
366367
const hasSocial = githubAvailable || googleAvailable || microsoftAvailable

0 commit comments

Comments
 (0)