Files
twenty/packages/twenty-docs
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
..
2026-04-30 14:54:49 +00:00
2026-05-08 03:13:20 +02:00
2026-05-07 18:01:57 +02:00
2026-05-08 03:13:20 +02:00
2025-11-18 17:21:48 +01:00

Twenty Documentation

Official documentation for Twenty CRM, powered by Mintlify.

🌐 Live Site

Visit the documentation at docs.twenty.com

📚 Content

This repository contains:

  • User Guide (46 pages) - Complete guide for Twenty users
  • Developers (24 pages) - Technical documentation for developers
  • Twenty UI (25 pages) - UI component library documentation

🚀 Local Development

To run the documentation locally:

# From the twenty monorepo root
npx nx run twenty-docs:dev

The documentation will be available at http://localhost:3000

📝 Editing Content

Adding/Editing Pages

  1. Edit MDX files in the appropriate directory:

    • user-guide/ - User documentation
    • developers/ - Developer documentation
    • twenty-ui/ - Component documentation
  2. Update navigation/base-structure.json if you need to change the tab/group hierarchy or add/remove pages. This file stays in the repo and is not uploaded to Crowdin.

  3. Keep the translation template (navigation/navigation.template.json) in sync by running yarn docs:generate-navigation-template after editing the base structure. This template is the only file that should be pushed to Crowdin.

  4. For each translated locale pulled from Crowdin, ensure a packages/twenty-docs/l/<language>/navigation.json file exists. These files contain labels only; page slugs always come from the base structure.

  5. Run yarn docs:generate to rebuild docs.json from the base structure + translated labels.

MDX Format

All documentation pages use MDX format with frontmatter:

---
title: Page Title
description: Page description
image: /images/path/to/image.png
---

Your content here...

Adding Images

  1. Place images in the /images/ directory
  2. Reference them in MDX: ![Alt text](/images/your-image.png)
  3. Or use Mintlify Frame component:
<Frame>
  <img src="/images/your-image.png" alt="Description" />
</Frame>

🔧 Configuration

  • navigation/base-structure.json - Source of truth for tabs, groups, icons, and page slugs (English only, not sent to Crowdin).
  • navigation/navigation.template.json - Generated translation template (labels only) that is uploaded to Crowdin.
  • l/<language>/navigation.json - Locale-specific label files pulled from Crowdin.
  • docs.json - Generated Mintlify configuration (always run yarn docs:generate after modifying navigation files).
  • package.json - Package dependencies and scripts (docs:generate, docs:generate-navigation-template, …).
  • project.json - Nx workspace configuration

📦 Building

# Build the documentation
npx nx run twenty-docs:build

🤝 Contributing

To contribute to the documentation:

  1. Fork the repository
  2. Make your changes in the packages/twenty-docs directory
  3. Test locally with npx nx run twenty-docs:dev
  4. Submit a pull request

📄 License

This documentation is part of the Twenty project and is licensed under AGPL-3.0.