Files
twenty/packages/twenty-docs/user-guide/permissions-access/how-tos/permissions-faq.mdx
T
Félix Malfait 34b927ff23 feat(public-domain): bind public domains to apps + reorganize settings (#20360)
## Summary

- **Public domains can now be bound to a specific app.** When a request
hits an app-bound public domain, route resolution restricts
logic-function matching to that app's HTTP-routed functions only —
isolating each app's routes to its own domain instead of letting routes
from other apps in the workspace match nondeterministically.
- **Settings sidebar reorganized.** Removed the standalone Domains page.
Workspace Domain → General. Approved Domains + Invitations → Members
"Access" tab. Emailing Domains + Public Domains → Apps "Developer" tab.
Roles → Members "Roles" tab.

## Why

The use case: someone building a partner portal app or a lead-collection
app declares private objects (leads, partners…) plus a few public HTTP
routes. Each app needs its own domain (`partners.acme.com`,
`leads.acme.com`) without those domains exposing every other app's
routes in the same workspace. Today's PublicDomainEntity is
workspace-scoped only, so all HTTP-routed logic functions in a workspace
compete for any public domain — first match wins nondeterministically.

## Backend

- Added nullable `applicationId` FK to `PublicDomainEntity`
(cascade-deleted with the app); indexed for the route-trigger lookup.
- New fast instance command
`2-4-instance-command-fast-1798000003000-add-application-id-to-public-domain`
adds the column, index, and FK constraint.
- `createPublicDomain(domain, applicationId)` accepts an optional app
binding; new `updatePublicDomain(domain, applicationId)` mutation
rebinds/unbinds an existing domain. Both validate the application
belongs to the workspace.
- `WorkspaceDomainsService.resolveWorkspaceAndPublicDomain(origin)`
returns both the workspace and the matched public domain in one query —
replacing the old back-to-back lookups in the route-trigger hot path.
`getWorkspaceByOriginOrDefaultWorkspace` is preserved as a thin wrapper.
- `RouteTriggerService` filters `logicFunction` by `applicationId` when
the matched public domain is app-scoped; falls back to workspace-wide
when unbound.
- Three sequential validation queries in `createPublicDomain` now run in
parallel via `Promise.all`.

## Frontend

| Old location | New location |
|---|---|
| Settings sidebar → Domains (standalone page) | Removed |
| Domains page → Workspace Domain | General page |
| Domains page → Approved Domains | Members → Access tab |
| Domains page → Emailing Domains | Apps → Developer tab |
| Domains page → Public Domains | Apps → Developer tab |
| Settings sidebar → Roles (standalone) | Members → Roles tab |
| `pages/settings/roles/` | `pages/settings/members/roles/` |

- The Public Domain detail page has an Application picker that uses
`Select`'s native `emptyOption` + `null` value pattern (matches
`SettingsDataModelObjectIdentifiersForm`).
- Members page tabs use the existing `TabListFromUrlOptionalEffect`
mechanism (rendered automatically by `TabList`) for hash-based tab
activation.
- `/settings/members/roles` redirects to `/settings/members#roles` so
role sub-pages' `navigate(SettingsPath.Roles)` lands on the Members page
with the Roles tab pre-selected.
- All affected breadcrumbs updated to nest under their new parents.
- `SettingsPath.Roles` and friends now nest under `members/`;
`Subdomain` and `CustomDomain` under `general/`; `PublicDomain` and
`EmailingDomain` under `applications/`.

## Test plan

- [x] `nx typecheck twenty-front` passes
- [x] `nx typecheck twenty-server` passes
- [x] `oxlint --type-aware` clean on all touched files
- [x] `prettier --check` clean on all touched files
- [x] Migration applied locally; `publicDomain.applicationId` (uuid,
nullable) confirmed in DB
- [x] GraphQL schema exposes `PublicDomain.applicationId`,
`createPublicDomain.applicationId`, `updatePublicDomain` mutation
- [x] **End-to-end route resolution scenarios verified locally:**
  - Domain bound to App A, function in App A → route matches 
- Domain bound to App B, function in App A → route does NOT match (HTTP
404 `TRIGGER_NOT_FOUND`) 
- Domain unbound (`applicationId = NULL`) → route matches workspace-wide

  - Unknown path on bound domain → returns 404 cleanly 
- [x] UI sanity (browser-tested at `apple.localhost:3001`):
  - General page shows Workspace Domain card
  - Members page shows Team / Access / Roles tabs
  - Access tab combines Invite by link + by email + Approved Domains
  - Roles tab embeds the role list
- `/settings/members/roles` direct URL → redirects + Roles tab
pre-selected
  - Apps Developer tab shows Emailing Domains + Public Domains sections
- Public Domain detail page has Application picker dropdown listing
workspace apps
- Sidebar nav: "Domains" and "Roles" no longer present (now folded into
General/Members)

## Notes for reviewers

- Creating a public domain via the UI still requires Cloudflare
credentials in the dev `.env` (`CLOUDFLARE_API_KEY`,
`CLOUDFLARE_PUBLIC_DOMAIN_ZONE_ID`, `PUBLIC_DOMAIN_URL`). The DNS step
is unchanged from main.
- The `applicationId` column is nullable, so existing public-domain rows
continue to work workspace-wide — no data backfill required.
- `SettingsRolesContainer` was deleted (no longer referenced after
`SettingsRoles` index page was removed).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:17:28 +02:00

122 lines
4.4 KiB
Plaintext

---
title: Permissions FAQ
description: Frequently asked questions about roles and permissions.
---
## Roles
<AccordionGroup>
<Accordion title="What roles come with Twenty by default?">
Twenty comes with an **Admin** and **Member** roles by default. You can create additional custom roles based on your team's needs (e.g., Sales Rep, Manager, Read-Only User).
</Accordion>
<Accordion title="Can I delete the Admin role?">
No, the Admin role cannot be deleted. There must always be at least one member assigned to the Admin role.
</Accordion>
<Accordion title="What happens when I delete a role?">
Any workspace member assigned to that role will be automatically reassigned to the default role.
</Accordion>
<Accordion title="How do I set a default role for new members?">
Go to **Settings → Members → Roles**, find the **Default Role** option, and select which role new members should automatically receive when they join.
</Accordion>
<Accordion title="Can I assign multiple roles to one user?">
No, each user can only have one role at a time. Create a custom role if you need a combination of permissions.
</Accordion>
</AccordionGroup>
## Permissions
<AccordionGroup>
<Accordion title="What's the difference between object and field permissions?">
- **Object permissions**: Control access to entire records (e.g., can see/edit/delete People records)
- **Field permissions**: Control access to specific fields within an object (e.g., can see but not edit the Salary field)
Field permissions allow more granular control over sensitive data.
</Accordion>
<Accordion title="How do permission overrides work?">
Permissions cascade from global to specific:
1. **All Objects** sets the baseline for all objects
2. **Object-Level Permissions** can override the global setting for specific objects
3. **Field-Level Permissions** can override the object setting for specific fields
More specific settings always take precedence.
</Accordion>
<Accordion title="What do the different permission levels mean?">
For objects:
- **See Records**: View records in lists and detail pages
- **Edit Records**: Modify existing records
- **Delete Records**: Soft-delete records (can be restored)
- **Destroy Records**: Permanently delete records
For fields:
- **See Field**: View the field value
- **Edit Field**: Modify the field value
- **No Access**: Field is completely hidden
</Accordion>
<Accordion title="Can I restrict access to specific records (row-level permissions)?">
Row-level permissions will be available on the **Organization** plan by Q1 2026. This allows you to restrict access to specific records based on criteria (e.g., only see your own opportunities).
</Accordion>
<Accordion title="How do I make a field read-only for certain users?">
1. Go to **Settings → Members → Roles**
2. Select the role
3. Navigate to the object containing the field
4. Set the field permission to **See Field** (without Edit Field)
</Accordion>
</AccordionGroup>
## Settings & Actions
<AccordionGroup>
<Accordion title="What settings permissions are available?">
You can control access to:
- API key generation
- Workspace preferences
- Role assignment
- Data model configuration
- Security settings
- Workflow management
Use **Settings All Access** to grant full access, or enable specific permissions.
</Accordion>
<Accordion title="What action permissions are available?">
You can control:
- **Send Email**: Ability to send emails from Twenty
- **Import CSV**: Ability to import data via CSV
- **Export CSV**: Ability to export data to CSV
Use **Application All Access** to grant all actions, or enable specific ones.
</Accordion>
</AccordionGroup>
## SSO
<AccordionGroup>
<Accordion title="Is SSO available on all plans?">
No, SSO is a Premium feature available on the **Organization** plan only.
</Accordion>
<Accordion title="Which identity providers are supported?">
Twenty supports:
- **SAML 2.0** (works with most enterprise identity providers)
- **Google Workspace**
- **Microsoft Entra ID** (formerly Azure AD)
</Accordion>
<Accordion title="What is Just-in-Time (JIT) provisioning?">
With JIT provisioning, user accounts are automatically created in Twenty when someone logs in via SSO for the first time. They're assigned the default role automatically.
</Accordion>
<Accordion title="Can I require SSO for all users?">
Yes, once SSO is configured, you can disable password login for SSO users to enforce authentication through your identity provider.
</Accordion>
</AccordionGroup>