## Summary
Two things, both fallout from #20360:
1. Rename the `Members → Access` tab to `Members → Invite`. The previous
label leaned security-flavored; "Invite" reads as the verb users come
here to do.
2. Fix the `signup_invite_email` Playwright test (failing on main, e.g.
https://github.com/twentyhq/twenty/actions/runs/25671161586/job/75356474079).
The invite-link button moved off the default Team tab when the Members
page got tabbed; the test was looking for it on the wrong tab.
## Rename details
- File: `SettingsWorkspaceMembersAccessTab.tsx` →
`SettingsWorkspaceMembersInviteTab.tsx` (single git rename, ~99%
similarity)
- Exported component: `SettingsWorkspaceMembersAccessTab` →
`SettingsWorkspaceMembersInviteTab`
- Tab id (and URL hash): `access` → `invite`
- Tab title: `Access` → `Invite`
- Icon: `IconKey` → `IconUserPlus`
- Doc breadcrumbs (3 files): `Members → Access` → `Members → Invite`
## E2E fix
`MembersSection` (Page Object Model) now has an `inviteTab` locator (via
`getByTestId('tab-invite')`) and a `goToInviteTab()` helper. Both
`copyInviteLink` and `sendInviteEmail` click the Invite tab first, so
they work regardless of which tab the page lands on initially.
Idempotent if already there.
## Test plan
- [x] CI green (e2e test + lint + typecheck + format)
- Lingui `.po` files will pick up the new source paths on the next
translation pass — not touched here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## 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>
Add missing info about how to edit navigation bar, add many-to-many
relation and get Enterprise key for Org license on self-hosted
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Summary
- **New Getting Started section** with quickstart guide and restructured
navigation
- **Halftone-style illustrations** for User Guide and Developer
introduction cards using a Canvas 2D filter script
- **Removed hero images** (`image:` frontmatter + `<Frame><img>` blocks)
from all user-guide article pages
- **Cleaned up translations** (13 languages): removed hero images and
updated introduction cards to use halftone style
- **Cleaned up twenty-ui pages**: removed outdated hero images from
component docs
- **Deleted orphaned images**: `table.png`, `kanban.png`
- **Developer page**: fixed duplicate icon, switched to 3-column layout
## Test plan
- [ ] Verify docs site builds without errors
- [ ] Check User Guide introduction page renders halftone card images in
both light and dark mode
- [ ] Check Developer introduction page renders 3-column layout with
distinct icons
- [ ] Confirm article pages no longer show hero images at the top
- [ ] Spot-check a few translated pages to ensure hero images are
removed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@twenty.com>
As per title, update a documentation on how to upload a file given
increasing amount of questions for this problem
CC: @StephanieJoly4
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
## Summary
This PR implements OAuth 2.0 Dynamic Client Registration (RFC 7591) and
OAuth 2.0 Protected Resource Metadata (RFC 9728) support, enabling
third-party applications to dynamically register as OAuth clients
without manual configuration.
## Key Changes
### OAuth Dynamic Client Registration
- **New Controller**: `OAuthRegistrationController` at `POST
/oauth/register` endpoint
- Validates client metadata according to RFC 7591 specifications
- Enforces PKCE-only public client model (no client secrets)
- Supports only `authorization_code` grant type and `code` response type
- Rate limits registrations to 10 per hour per IP address
- Returns `client_id` and registration metadata in response
- **Input Validation**: `OAuthRegisterInput` DTO with constraints on:
- Client name (max 256 chars)
- Redirect URIs (max 20, validated for security)
- Grant types, response types, scopes, and auth methods
- Logo and client URIs (max 2048 chars)
- **Discovery Endpoint Update**: Added `registration_endpoint` to OAuth
discovery metadata
### Stale Registration Cleanup
- **Cleanup Service**: Automatically removes OAuth-only registrations
older than 30 days that have no active installations
- **Cron Job**: Runs daily at 02:30 AM UTC with batch processing (100
records per batch)
- **CLI Command**: `cron:stale-registration-cleanup` to manually trigger
cleanup
### MCP (Model Context Protocol) Authentication
- **New Guard**: `McpAuthGuard` implements RFC 9728 compliance
- Wraps JWT authentication with proper error responses
- Returns `WWW-Authenticate` header with protected resource metadata URL
on 401
- Enables OAuth-protected MCP endpoints
### Protected Resource Metadata
- **New Endpoint**: `GET /.well-known/oauth-protected-resource` (RFC
9728)
- Advertises MCP resource as OAuth-protected
- Lists supported scopes and bearer token methods
- Enables OAuth clients to discover authorization requirements
### Application Registration Updates
- **New Source Type**: `OAUTH_ONLY` enum value for OAuth-only
registrations
- **Install Service**: Skips artifact installation for OAuth-only apps
(no code artifacts)
### Frontend Updates
- **Authorization Page**: Support both snake_case (standard OAuth) and
camelCase (legacy) query parameters
- `client_id` / `clientId`
- `code_challenge` / `codeChallenge`
- `redirect_uri` / `redirectUrl`
## Implementation Details
- **Rate Limiting**: Uses token bucket algorithm with 10 registrations
per 3,600,000ms window per IP
- **Scope Validation**: Requested scopes are capped to allowed OAuth
scopes; defaults to all scopes if not specified
- **Redirect URI Validation**: Uses existing `validateRedirectUri`
utility for security
- **Cache Headers**: Registration responses include `Cache-Control:
no-store` and `Pragma: no-cache`
- **Batch Processing**: Cleanup operations process 100 records at a time
to avoid memory issues
- **Grace Period**: 30-day grace period before cleanup to allow time for
client activation
https://claude.ai/code/session_01PxcuWFFRuXMASMaMGTLYk2
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
## Summary
- Restructures the developer Extend documentation: moves API and
Webhooks to top-level pages, creates dedicated Apps section with Getting
Started, Building, and Publishing pages
- Updates navigation structure (`docs.json`, `base-structure.json`,
`navigation.template.json`)
- Updates translated docs for all locales and LLMS.md references across
app packages
## Test plan
- [ ] Run `mintlify dev` locally and verify navigation structure
- [ ] Check that all links in the Extend section work correctly
- [ ] Verify translated pages render properly
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: github-actions <github-actions@twenty.com>
## Summary
- **Fix junction relation toggle not being saved**: The form schema
wasn't tracking the `settings` field, so changes to
`junctionTargetFieldId` weren't marked as dirty
- **Add type-safe documentation paths**: Generate TypeScript constants
from `base-structure.json` to prevent broken documentation links
- **Create many-to-many relations documentation**: Step-by-step guide
for building many-to-many relations using junction objects
- **Update `getDocumentationUrl`**: Now uses shared constants from
`twenty-shared` for base URL, default path, and supported languages
## Key Changes
### Junction Toggle Fix
- Added `settings` field to the form schema in
`SettingsDataModelFieldRelationForm.tsx`
- Fixed the toggle to properly merge settings when updating
`junctionTargetFieldId`
### Type-Safe Documentation Paths
- New constants in `twenty-shared/constants`:
- `DOCUMENTATION_PATHS` - All 161 documentation paths as typed constants
- `DOCUMENTATION_SUPPORTED_LANGUAGES` - 14 supported languages
- `DOCUMENTATION_BASE_URL` / `DOCUMENTATION_DEFAULT_PATH`
- Generator script: `yarn docs:generate-paths`
- CI integration: Added to `docs-i18n-pull.yaml` workflow
### Documentation
- New article:
`/user-guide/data-model/how-tos/create-many-to-many-relations`
- Updated `/user-guide/data-model/capabilities/relation-fields.mdx` with
Lab warning and link
## Test plan
- [ ] Verify junction toggle saves correctly when enabled/disabled
- [ ] Verify documentation link opens correct localized page
- [ ] Verify `yarn docs:generate-paths` regenerates paths correctly
- added article to detail how to attach a pdf to a given record
- added link in another article
- updated the english section of the docs.json to have the file appear
in the left menu
- updated the 2 navigation files
---------
Co-authored-by: github-actions <github-actions@twenty.com>
- more details on rate at which messages are imported from Gmail
- Settings -> Release deprecated to Settings -> Updates => this leads to
updating the file title, file name and the navigation files
- Support & Documentation accessible via Settings and no longer the nav
bar
- updated features out of the lab
- no more integration page
- added a new article with step by step on how to notify a teammate of a
note to review
docs.json is updated only for the English part
---------
Co-authored-by: github-actions <github-actions@twenty.com>
- added an image, one article
- updated the 2 files under the Navigation folder but not the docs.json
- no need to redirect the links given this is a new article
Reorganizing by Feature sections
Capabilities folders to give an overview of each feature
How-Tos folders to give guidance for advanced customizations
Reorganized the Developers section as well, moving the API sub section
there
added some new visuals and videos to illustrate the How-Tos articles
checked the typos, the links and added a section at the end of the
doc.json file to redirect existing links to the new ones (SEO purpose +
continuity of the user experience)
What I have not updated is the "l" folder that, per my understanding,
contains the translation of the User Guide - that I only edited in
English
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
5301502a32. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Abdul Rahman <ar5438376@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>