Compare commits

..
Author SHA1 Message Date
Etienne a630874c35 fix(ai-agent-node) - agent node execution error (#20534)
**Root cause:** getWorkflowRunContext(stepInfos) builds a Record<string,
unknown> from the previous steps' results. There is no workspaceId key
in it, so context.workspaceId as string silently evaluated to undefined.
That undefined was then passed all the way down to
WorkspaceCacheService.getOrRecompute, **which correctly throws** when
workspaceId is not a valid UUID.

Before : 
<img width="525" height="130" alt="Screenshot 2026-05-13 at 14 58 54"
src="https://github.com/user-attachments/assets/0549b4dc-7063-44e5-95a1-00a460a6d7f1"
/>

Introduced with billing v2 yesterday, since then, workspaceId is needed
to bill credit usage
2026-05-13 15:37:27 +02:00
Etienne 9dc8333908 Billing - Add default ff (#20480) 2026-05-12 13:28:24 +02:00
8909badc59 i18n - website translations (#20434)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-11 14:15:28 +02:00
Abdullah.andGitHub c81019965d [Website] Extract HomeVisual into shared AppPreview section. (#20432) 2026-05-11 06:46:09 +02:00
58dd5d3561 i18n - docs translations (#20431)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-10 22:35:26 +02:00
c611a7ac20 i18n - docs translations (#20429)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-10 20:37:38 +02:00
50a4fe5040 i18n - translations (#20428)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-10 20:24:31 +02:00
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
Thomas HeinrichsdoblerandGitHub 086830f81b fix(messaging): reset sync state when IMAP/SMTP/CalDAV credentials are updated (#20405)
## Problem

Updating credentials for an existing IMAP/SMTP/CalDAV connected account
in **Settings → Accounts → Connection settings** has no effect on the
sync. The save persists the new `connectionParameters`, but
`messageChannel.syncStatus` / `messageChannel.syncStage` /
`connectedAccount.authFailedAt` are left untouched, and no fetch job is
queued.

This matters most when the channel is in
`FAILED_INSUFFICIENT_PERMISSIONS` (e.g. after Apple invalidates iCloud
app-specific passwords, or on any other auth failure):
`MessagingRelaunchFailedMessageChannelsCronJob` only retries
`FAILED_UNKNOWN`, so the account is stuck on "Sync failed" forever
despite the credentials now being correct. The only known workarounds
are a direct DB update or deleting and recreating the account.

#19273 fixed the frontend cache angle of credential editing; this PR
fixes the backend half of the same UX (the channel state machine).

## Reproduce

1. Connect an IMAP/SMTP account.
2. Force an auth failure (e.g. revoke the app-specific password
upstream). Wait until `messageChannel.syncStatus` flips to
`FAILED_INSUFFICIENT_PERMISSIONS`.
3. Generate a fresh password, edit the account in **Settings →
Accounts**, save.
4. Observe: account stays "Sync failed" indefinitely;
`core.messageChannel.syncStatus` and
`core.connectedAccount.authFailedAt` are unchanged; no IMAP connect
attempt in the worker logs.

## Root cause


`packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts
→ processAccount` saves the updated `connectionParameters` but never
resets the sync state nor enqueues a fetch job.

The OAuth providers handle this:

| Reset step | `google-apis.service.ts` | `microsoft-apis.service.ts` |
`imap-smtp-caldav-apis.service.ts` (before this PR) |
|---|---|---|---|
| `updateConnectedAccountOnReconnect` (clears `authFailedAt`) | yes |
yes | — |
| `accountsToReconnectService.removeAccountToReconnect` | yes | yes | —
|
| `resetAndMarkAsMessagesListFetchPending` | yes | yes | — |
| Enqueue `MessagingMessageListFetchJob` | yes | yes | — |
| `resetAndMarkAsCalendarEventListFetchPending` | yes | yes | — |
| Enqueue `CalendarEventListFetchJob` | yes | yes | — |

#12061 introduced this behaviour for Google/Microsoft. The IMAP service
was added later and the equivalent reconnect plumbing was never ported.

## Fix

Mirrors the Google/Microsoft pattern in `processAccount`:

- **Inside** the transaction, when an account already exists: clear
`authFailedAt` on the connected account.
- **After** the transaction, when an existing account is being updated:
  - drop the account from `accountsToReconnect` user-vars,
- if the message channel exists and IMAP is configured, call
`resetAndMarkAsMessagesListFetchPending` and enqueue
`MessagingMessageListFetchJob` (skipped while the channel is still
`PENDING_CONFIGURATION`),
  - same logic for the calendar channel and `CalendarEventListFetchJob`.

Wires `MessageChannelSyncStatusService`,
`CalendarChannelSyncStatusService`, `AccountsToReconnectService` and the
messaging/calendar queues into `IMAPAPIsModule`.

## Tests

- Extended the existing `should preserve existing channels when updating
account credentials` case to assert: `authFailedAt: null` is written
within the transaction; `removeAccountToReconnect` is called with the
resolved `userId`; `resetAndMarkAs*` and queue `add` are called for both
channels.
- New case: `should not queue fetch jobs for channels still in
PENDING_CONFIGURATION`.
- New case: `should not run reconnect logic when creating a brand new
account`.

I could not run the full server test suite locally (no `node_modules`
checked out); relying on CI.

## Out of scope

- Extending `UpdateConnectedAccountOnReconnectService` to a non-OAuth
shape: kept inline to minimise the blast radius. Refactoring opportunity
for a follow-up.
- Behaviour when the user removes IMAP or CALDAV from the parameters on
update (the channel currently lingers in its old state). Pre-existing
and not made worse by this PR.
2026-05-10 11:58:53 +00:00
a962cdc34f i18n - website translations (#20418)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-10 11:17:55 +02:00
neo773andGitHub a15bf1e608 Reserve inbound subdomain for SES (#20414) 2026-05-10 11:17:10 +02:00
524e5d8cf7 fix: scroll AI chat to bottom on side panel reopen (#20413)
## Summary
- Fix: when reopening the AI chat side panel, users landed at the top of
the conversation and had to scroll down to find the latest messages
- Root cause: the side panel fully unmounts on close
([SidePanelForDesktop.tsx](packages/twenty-front/src/modules/side-panel/components/SidePanelForDesktop.tsx)
clears `shouldShowContent` after the close transition), so on reopen the
scroll wrapper is recreated with `scrollTop = 0`. The existing
initial-scroll-to-bottom only fires on thread change, but the
displayed-thread atom outlives the unmount, so no thread change is
detected on a remount and the scroll-to-bottom never runs
- Fix: add a tiny `AgentChatScrollToBottomOnMountLayoutEffect` rendered
inside the message list that calls `scrollAiChatToBottom()` directly in
`useLayoutEffect`. Because the parent returns `null` when there are no
messages, the mount only fires when there is content to scroll past

## Why direct scroll, not the existing flag
`agentChatIsInitialScrollPendingOnThreadChangeState` is paired with a
`MutationObserver` settle that only clears the flag once the subtree is
quiet for 150 ms. During a live stream the message subtree mutates on
every token, the settle resets indefinitely and `visibility: hidden`
never lifts. The thread-change handler avoids this because it is gated
on `!agentChatIsStreaming`
([AgentChatStreamSubscriptionEffect.tsx:78-79](packages/twenty-front/src/modules/ai/components/AgentChatStreamSubscriptionEffect.tsx)),
but a mount can happen at any time, including mid-stream. Scrolling the
DOM directly in `useLayoutEffect` runs synchronously between commit and
paint, so the user sees the bottom on the first paint with no flash and
no settle dependency.

## Tradeoff
A user who scrolled up to read history and then closes/reopens the panel
will land back at the bottom instead of where they were. Standard chat
UX (Slack, ChatGPT, iMessage); preserving per-thread scroll position
would need a new atom and is left out of scope.

## Test plan
- [ ] Open AI chat with messages, close the side panel, reopen → lands
at the bottom (latest messages visible)
- [ ] Reopen the side panel **mid-stream** → lands at the bottom and
continues to follow new tokens (chat is not hidden)
- [ ] Switch between threads → existing thread-change scroll still works
(no double-scroll, no regression)
- [ ] Open AI chat with no messages → no flash, no errors
- [ ] Rapidly close/reopen the panel a few times → each reopen lands at
the bottom

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 10:47:52 +02:00
Abdullah.andGitHub 2e5ccd9b86 [Website] Codebase cleanup and SEO improvements. (#20415) 2026-05-09 22:07:31 +02:00
459c64f642 Prevent non-admin users from impersonating admin users (#20412)
## Summary
- Adds a privilege check to workspace-level impersonation: non-admin
users can no longer impersonate users who have `canAccessFullAdminPanel`
or `canImpersonate` flags
- Adds the same check in JWT token validation as defense-in-depth
(invalidates existing impersonation sessions targeting admin users)
- Adds 3 unit tests covering: non-admin → admin blocked, non-admin →
canImpersonate blocked, admin → admin allowed

## Test plan
- [x] Unit tests pass (14/14 in `impersonation.service.spec.ts`)
- [x] Typecheck passes
- [ ] Verify workspace-level impersonation of regular users still works
normally
- [ ] Verify server-level impersonation by admins is unaffected

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-09 11:57:38 +02:00
3420d63b7a i18n - translations (#20411)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-09 11:12:05 +02:00
Félix MalfaitGitHubClaudeCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>claude[bot] <41898282+claude[bot]@users.noreply.github.com>neo773neo773
4da8878697 feat: add email forwarding message channel (#19535)
## Summary

- Add email forwarding as a new message channel type, allowing users to
forward emails from addresses like `support@mycompany.com` into Twenty
- Inbound emails arrive via S3 (SES → S3 bucket), are polled by a cron
job, parsed, routed to the correct workspace/channel, and persisted as
messages
- Dedicated settings page at `/settings/accounts/new-email-forwarding`
where users provide their source email handle and receive a unique
forwarding address
- Forwarding channels bypass the IMAP/mailbox sync state machine — they
skip cron-driven sync, relaunch, and message-list-fetch lifecycle stages
- Forwarding address section shown at the top of the Emails settings
page so users can find/copy their addresses after initial setup
- Tab names for forwarding channels display the user-provided handle
(e.g. `support@mycompany.com`) instead of the internal routing address
- Shared utilities extracted from IMAP driver: `extractThreadId`,
`extractParticipants`, `extractAddresses` to avoid code duplication
- Uses the existing S3 bucket (STORAGE_S3_*) with `inbound-email/`
prefix — no separate bucket needed
- Feature gated behind `isEmailForwardingEnabled` client config
(requires `INBOUND_EMAIL_DOMAIN` + S3 storage)

## New backend modules

- `InboundEmailS3ClientProvider` — lazy-initialized S3 client using
existing storage config
- `InboundEmailStorageService` — S3 operations (get, move to
processed/unmatched/failed)
- `InboundEmailParserService` — RFC 822 parsing via `postal-mime`,
builds `MessageWithParticipants`
- `InboundEmailImportService` — orchestrates download → parse → route →
persist → archive
- `MessagingInboundEmailPollCronJob` — polls S3 `incoming/` prefix,
enqueues import jobs
- `CreateEmailForwardingChannelInput` DTO — accepts user-provided
`handle`

## New frontend components

- `SettingsAccountsNewEmailForwardingChannel` — dedicated page with
handle input form + forwarding address result
- `SettingsAccountsEmailForwardingSection` — forwarding address list on
the Emails settings page
- `useConnectedAccountHandleMap` — shared hook for account ID → handle
lookup
- `useCreateEmailForwardingChannel` — mutation hook accepting handle
parameter

## Test plan

- [x] 17 unit tests for inbound email import service (all outcomes:
imported, unmatched, loop_dropped, unconfigured, parse_failed,
persist_failed)
- [x] 16 tests for `computeSyncStatus` including EMAIL_FORWARDING cases
- [x] 11 tests for `extractEnvelopeRecipient` utility
- [x] TypeScript typechecks pass for both twenty-server and twenty-front
- [x] Lint passes for both packages
- [ ] Manual: create forwarding channel, verify forwarding address
generated
- [ ] Manual: send email to forwarding address, verify it appears in
Twenty

https://claude.ai/code/session_01KpyF6p4cUEnuaT4h8DP5Pm

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
Co-authored-by: neo773 <neo773@protonmail.com>
2026-05-09 11:00:57 +02:00
23aa859502 refactor: scope ApplicationRegistrationService findOneById to tenant rows (#20408)
## Summary

- `findOneById` is the lookup used by tenant-scoped operations
(`update`, `delete`, `rotateClientSecret`, `getStats`,
`transferOwnership`). It currently also matches `ownerWorkspaceId IS
NULL` rows, which was a leftover from when `ownerWorkspaceId` was made
nullable to support catalog-synced apps.
- System-level rows (marketplace catalog entries, the Twenty CLI
registration, dynamic OAuth client registrations) are already managed
through dedicated admin paths — `findAll()` and `findOneByIdGlobal()`
behind `AdminPanelGuard` — so the `IS NULL` fallback in `findOneById` is
unused by any real caller.
- Dropping it tightens the contract: tenant-scoped helpers operate on
tenant rows, global helpers operate on the global view. No behavior
change for any current legitimate flow.

## Test plan

- [ ] Existing application-registration GraphQL queries/mutations
(`findOneApplicationRegistration`, `updateApplicationRegistration`,
`deleteApplicationRegistration`,
`rotateApplicationRegistrationClientSecret`,
`findApplicationRegistrationStats`,
`transferApplicationRegistrationOwnership`) continue to work for a
workspace's own registrations.
- [ ] Admin Panel "Apps" tab continues to list and view all
registrations (uses `findAllApplicationRegistrations` /
`findOneAdminApplicationRegistration`, unaffected).
- [ ] Marketplace catalog sync still upserts catalog rows (uses
`findOneByUniversalIdentifier`, unaffected).
- [ ] Twenty CLI registration bootstrap still works (uses
`findOneByUniversalIdentifier`, unaffected).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 15:37:21 +02:00
martmullandGitHub 773245fa65 Isolate twenty apps from nx project (#20406)
- avoids importing twenty-shared or else in twenty-apps applications
- update and add workflow action in twenty linear app
2026-05-08 13:16:25 +00:00
Félix MalfaitandGitHub 0f8ee5714f feat(sdk): warn when local server image is behind latest (#20352)
Closes #20328.

## Summary
- Adds a CLI-side check that warns when `twenty-app-dev` is older than
the latest published Docker Hub tag.
- Reads `APP_VERSION` from the running container via `docker inspect` —
no server endpoint, no version exposed publicly. (`APP_VERSION` is
already baked in by `packages/twenty-docker/twenty/Dockerfile` for both
`twenty` and `twenty-app-dev` targets.)
- Fetches latest semver tag from Docker Hub (same API the admin panel
already uses) and caches the result for 24h in
`~/.twenty/version-check-cache.json`.
- Wired into `twenty dev`, `twenty install`, and `twenty server start`.
- Best-effort: silent on container-missing / docker / network errors,
never blocks a command.

## Why CLI-side instead of a `/healthz` extension
The original issue suggested comparing the running server version
against Docker Hub. Exposing the running version on a public endpoint
has a small but real security cost (helps attackers fingerprint
vulnerable deployments), and the version is already inside the image —
so the CLI can read it directly without ever calling the server.

## Test plan
- [x] `nx run twenty-sdk:test` — added unit tests for `parseSemver` /
`compareSemver`
- [x] `nx run twenty-sdk:typecheck`
- [x] `nx run twenty-sdk:lint`
- [ ] Manual: with an old `twenty-app-dev` image running, run `yarn
twenty install` → see warning
- [ ] Manual: with an up-to-date image, run `yarn twenty dev` → no
warning, cache file written
- [ ] Manual: no container at all → no warning, no error
2026-05-08 14:11:34 +02:00
Paul RastoinandGitHub 7f4f2e932c Simplify dispatch pr review (#20397)
# Introduction
Sending minimal information for required metadata to be fetched
afterwards
2026-05-08 12:53:59 +02:00
martmullandGitHub 0d05788547 Protect sendEmail endpoint and thread user context through logic function executor (#20369)
- Thread userId and userWorkspaceId through
LogicFunctionExecutorService.execute() so
application access tokens carry user context when available. This allows
logic functions
triggered by authenticated HTTP routes to call sendEmail with proper
user identity, making
  the existing verifyOwnership() check work naturally.
  - Gate the sendEmail resolver with
SettingsPermissionGuard(PermissionFlagType.SEND_EMAIL_TOOL) instead of
NoPermissionGuard,
ensuring only callers with the SEND_EMAIL_TOOL permission can send
emails.
2026-05-08 10:03:39 +00:00
Abdullah.andGitHub 837a946b5f fix: basic-ftp has FTP Command injection via CRLF (#20396)
Resolves [Dependabot Alert
918](https://github.com/twentyhq/twenty/security/dependabot/918).
2026-05-08 09:38:40 +00:00
WeikoandGitHub cb0b71dbdc fix: validate enum values before opening transaction in alterEnumValues (#20376)
## Context
The validation throws after startTransaction() and outside the
surrounding try.
If the empty-enum branch ever fires, a BEGIN is left open on the
borrowed QueryRunner and never rolled back by this method the caller has
no way of knowing it now owes a ROLLBACK. Whatever the caller does next
on that QueryRunner runs inside the leftover transaction, and if its
lifecycle ends with a release() instead of a rollbackTransaction(), the
connection goes back to the pool with state still pending.
2026-05-08 07:00:13 +00:00
Charles BochetandGitHub 546ab0a036 fix: handle widgets with missing universalConfiguration in 2.3 delete-gauge-widgets command (#20393)
## Summary

The 2.3 `upgrade:2-3:delete-gauge-widgets` workspace command crashed in
production for ~10 workspaces (out of 5000) with:

```
[Error] Cannot read properties of undefined (reading 'configurationType')
    at .../2-3-workspace-command-1798000000000-delete-gauge-widgets.command.js:35:164
    at Array.filter (<anonymous>)
```

### Root cause

Those workspaces have legacy `pageLayoutWidget` rows whose
`configuration` JSONB does not contain a recognized `configurationType`.
This is consistent with the 1.15 backfill
(`MigratePageLayoutWidgetConfigurationCommand`) only migrating widgets
with the deprecated `graphType` and the `IFRAME` /
`STANDALONE_RICH_TEXT` types — any other widget type that was already
missing `configurationType` (or has a value not in the current enum) was
left as-is.

When the cache is recomputed,
`fromPageLayoutWidgetConfigurationToUniversalConfiguration` switches on
`configuration.configurationType`. With no matching case, the function
falls through and returns `undefined`, so the cached
`widget.universalConfiguration` ends up `undefined`. The gauge filter
then dereferences `.configurationType` and throws.

We can't reproduce the affected data locally, but the symptom uniquely
points at this fall-through path — every other code path either throws
earlier (e.g. when `configuration` itself is null) or yields a defined
`universalConfiguration`.

### Fix

In
`2-3-workspace-command-1798000000000-delete-gauge-widgets.command.ts`:

- Skip widgets whose `universalConfiguration` is `undefined` — by
definition they aren't gauge widgets, so they don't belong in the
deletion set.
- Log them as a warning (id and count) so we still have visibility on
the corrupt rows for follow-up cleanup.
- Use optional chaining when comparing the configuration type so the
filter is robust to the same shape going forward.

The fix is minimal and additive: workspaces without corrupt widgets
behave exactly as before, and the upgrade can now succeed on the
affected workspaces.

## Test plan

- [ ] CI lint + typecheck green
- [ ] Run the upgrade on a healthy workspace locally — gauge widgets are
still deleted, no warnings logged
- [ ] On production, verify the 2.3 upgrade no longer fails on the
affected ~10 workspaces and that the warning logs surface the offending
widget ids for follow-up

## Follow-ups (out of scope of this PR)

- Investigate the corrupt widgets surfaced by the new warning log and
decide whether to backfill / delete them in a dedicated upgrade command
- Consider hardening
`fromPageLayoutWidgetConfigurationToUniversalConfiguration` so the
switch fall-through fails loudly (or returns a sentinel) instead of
silently yielding `undefined`
2026-05-08 09:05:22 +02:00
89eeb34ca7 i18n - website translations (#20384)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-08 09:00:45 +02:00
ee8004922e chore: sync AI model catalog from models.dev (#20392)
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).

This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.

**Please review before merging** — verify no critical models were
incorrectly deprecated.

Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com>
2026-05-08 08:32:12 +02:00
18b9cc5281 i18n - docs translations (#20385)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-08 03:13:20 +02:00
Abdullah.andGitHub 284ebeb12d [Website] Reintroduce the product page. (#20349)
This PR re-introduces the Product page that we decided to hold back in
the first release. Subsequent PRs will work on polishing the Product
page to have interactive visuals, but merging the static version to
record a snapshot.

No production deployments are planned until product page is polished and
blog has the required content - we'd push to prod next week with these
two checkpoints.
2026-05-08 00:01:04 +00:00
fa903c6971 i18n - docs translations (#20378)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-08 00:41:35 +02:00
martmullandGitHub e294d74e07 Detail steps during create twenty app (#20374)
## Before
<img width="391" height="188" alt="image"
src="https://github.com/user-attachments/assets/78a0139f-d992-49cb-98ff-531bdbadc64b"
/>

## After
<img width="419" height="773" alt="image"
src="https://github.com/user-attachments/assets/da9b36a4-74e0-4445-b8c5-7a2329eb4f46"
/>
2026-05-07 20:51:24 +00:00
59f2b1c724 i18n - docs translations (#20375)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-07 22:47:24 +02:00
4aca4d1143 Add defineApplicationRole method (#20314)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 18:55:13 +00:00
1553ff2857 i18n - docs translations (#20373)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-07 20:55:43 +02:00
9441e0456c i18n - translations (#20372)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-05-07 20:47:07 +02:00
bitloiandGitHub 7999cd3dde fix: Use settings table rows and detail page for app connections (#20257)
## Summary

Closes #20220

- Replace app connection-provider `SettingsListCard` rows with settings
table rows that link to a per-connection detail page.
- Add a connection detail page with inline display-name editing,
provider and handle metadata, visibility, scopes, timestamps, reconnect,
and confirmed disconnect.
- Add a scoped connected-account rename mutation and persist visibility
when reconnecting an existing app OAuth account.

## Tests

- `./node_modules/.bin/jest
packages/twenty-front/src/pages/settings/applications/__tests__/SettingsApplicationConnectionDetail.test.tsx
--config packages/twenty-front/jest.config.mjs --runInBand`
- `./node_modules/.bin/jest
packages/twenty-front/src/pages/settings/applications/tabs/__tests__/SettingsApplicationConnectionsSection.test.tsx
--config packages/twenty-front/jest.config.mjs --runInBand`
- `./node_modules/.bin/jest
packages/twenty-shared/src/utils/navigation/__tests__/getSettingsPath.test.ts
--config packages/twenty-shared/jest.config.mjs --runInBand`
- `./node_modules/.bin/jest
packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/__tests__/connected-account.resolver.spec.ts
--config packages/twenty-server/jest.config.mjs --runInBand`
- `./node_modules/.bin/jest
packages/twenty-server/src/engine/core-modules/application/application-oauth-provider/__tests__/application-oauth-provider-flow.service.spec.ts
--config packages/twenty-server/jest.config.mjs --runInBand`
- `./node_modules/.bin/oxlint ...`
- `./node_modules/.bin/prettier --check ...`
- `./node_modules/.bin/tsgo -p packages/twenty-shared/tsconfig.json`

## Notes

Full frontend and server typechecks are currently blocked by unrelated
existing workspace issues:
- frontend implicit `any` errors in
`useFrontComponentExecutionContext.ts`
- server missing workspace/dependency modules such as `twenty-emails`,
`twenty-client-sdk/generate`, and `@ai-sdk/azure`
2026-05-07 18:29:02 +00:00
1539 changed files with 41854 additions and 27084 deletions
@@ -105,7 +105,7 @@ jobs:
create-twenty-app --version
mkdir -p /tmp/e2e-test-workspace
cd /tmp/e2e-test-workspace
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --skip-local-instance
create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --skip-local-instance --yes
- name: Install scaffolded app dependencies
run: |
@@ -20,15 +20,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
REPO: ${{ github.repository }}
run: |
gh api repos/twentyhq/ci-privileged/dispatches \
-f event_type=pr-review \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[pr_head_sha]=$PR_HEAD_SHA" \
-f "client_payload[pr_author]=$PR_AUTHOR" \
-f "client_payload[pr_author_association]=$PR_AUTHOR_ASSOCIATION" \
-f "client_payload[repo]=$REPO"
-f "client_payload[pr_number]=$PR_NUMBER"
+27 -27
View File
@@ -6,14 +6,14 @@
<h2 align="center" >The #1 Open-Source CRM</h2>
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website-new/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website-new/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website-new/public/images/readme/map-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website-new/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website-new/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a></p>
<p align="center"><a href="https://twenty.com"><img src="./packages/twenty-website-new/public/images/readme/globe-icon.svg" width="12" height="12"/> Website</a> · <a href="https://docs.twenty.com"><img src="./packages/twenty-website-new/public/images/readme/book-icon.svg" width="12" height="12"/> Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website-new/public/images/readme/map-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website-new/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website-new/public/images/readme/figma-icon.webp" width="12" height="12"/> Figma</a></p>
<p align="center">
<a href="https://www.twenty.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/github-cover-light.png" alt="Twenty banner" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/github-cover-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/github-cover-light.webp" alt="Twenty banner" />
</picture>
</a>
</p>
@@ -24,7 +24,7 @@
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
<a href="https://twenty.com/why-twenty"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
<a href="https://twenty.com/resources/why-twenty"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="14" height="14"/> Learn more about why we built Twenty</a>
<br />
@@ -85,17 +85,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.png" alt="Create your apps" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-build-apps-light.webp" alt="Create your apps" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/getting-started"><img src="./packages/twenty-website-new/public/images/readme/code-icon.svg" width="16" height="16"/> Learn more about apps in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-version-control-light.png" alt="Stay on top with version control" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-version-control-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-version-control-light.webp" alt="Stay on top with version control" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/publishing"><img src="./packages/twenty-website-new/public/images/readme/monitor-icon.svg" width="16" height="16"/> Learn more about version control in doc</a></p>
</td>
@@ -103,17 +103,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.png" alt="All the tools you need to build anything" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-all-tools-light.webp" alt="All the tools you need to build anything" />
</picture>
<p align="center"><a href="https://docs.twenty.com/developers/extend/apps/building"><img src="./packages/twenty-website-new/public/images/readme/rocket-icon.svg" width="16" height="16"/> Learn more about primitives in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-tools-light.png" alt="Customize your layouts" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-tools-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-tools-light.webp" alt="Customize your layouts" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/layout/overview"><img src="./packages/twenty-website-new/public/images/readme/planner-icon.svg" width="16" height="16"/> Learn more about layouts in doc</a></p>
</td>
@@ -121,17 +121,17 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
<tr>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.png" alt="AI agents and chats" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-ai-agents-light.webp" alt="AI agents and chats" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/ai/overview"><img src="./packages/twenty-website-new/public/images/readme/message-icon.svg" width="16" height="16"/> Learn more about AI in doc</a></p>
</td>
<td width="50%">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.png" />
<img src="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.png" alt="Plus all the tools of a good CRM" />
<source media="(prefers-color-scheme: dark)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-dark.webp" />
<source media="(prefers-color-scheme: light)" srcset="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.webp" />
<img src="./packages/twenty-website-new/public/images/readme/v2-crm-tools-light.webp" alt="Plus all the tools of a good CRM" />
</picture>
<p align="center"><a href="https://docs.twenty.com/user-guide/introduction"><img src="./packages/twenty-website-new/public/images/readme/star-icon.svg" width="16" height="16"/> Learn more about CRM features in doc</a></p>
</td>
@@ -152,13 +152,13 @@ Want to go deeper? Read the <a href="https://docs.twenty.com/user-guide/introduc
# Thanks
<p align="center">
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website-new/public/images/readme/chromatic.png" height="28" alt="Chromatic" /></a>
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website-new/public/images/readme/chromatic.webp" height="28" alt="Chromatic" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://greptile.com"><img src="./packages/twenty-website-new/public/images/readme/greptile.png" height="28" alt="Greptile" /></a>
<a href="https://greptile.com"><img src="./packages/twenty-website-new/public/images/readme/greptile.webp" height="28" alt="Greptile" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://sentry.io/"><img src="./packages/twenty-website-new/public/images/readme/sentry.png" height="28" alt="Sentry" /></a>
<a href="https://sentry.io/"><img src="./packages/twenty-website-new/public/images/readme/sentry.webp" height="28" alt="Sentry" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/"><img src="./packages/twenty-website-new/public/images/readme/crowdin.png" height="28" alt="Crowdin" /></a>
<a href="https://crowdin.com/"><img src="./packages/twenty-website-new/public/images/readme/crowdin.webp" height="28" alt="Crowdin" /></a>
</p>
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
-1
View File
@@ -60,7 +60,6 @@
"packages/twenty-sdk",
"packages/twenty-front-component-renderer",
"packages/twenty-client-sdk",
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
"packages/twenty-oxlint-rules",
@@ -4,12 +4,10 @@ import {
APP_DESCRIPTION,
APP_DISPLAY_NAME,
APPLICATION_UNIVERSAL_IDENTIFIER,
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: APP_DISPLAY_NAME,
description: APP_DESCRIPTION,
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
@@ -1,11 +1,11 @@
import { defineRole } from 'twenty-sdk/define';
import { defineApplicationRole } from 'twenty-sdk/define';
import {
APP_DISPLAY_NAME,
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineRole({
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: `${APP_DISPLAY_NAME} default function role`,
description: `${APP_DISPLAY_NAME} default function role`,
@@ -16,7 +16,6 @@ import {
containerExists,
detectLocalServer,
serverStart,
type ServerStartResult,
} from 'twenty-sdk/cli';
import { isDefined } from 'twenty-shared/utils';
@@ -33,6 +32,8 @@ type CreateAppOptions = {
};
export class CreateAppCommand {
private static TOTAL_STEPS = 4;
async execute(options: CreateAppOptions = {}): Promise<void> {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(options);
@@ -40,9 +41,26 @@ export class CreateAppCommand {
try {
await this.validateDirectory(appDirectory);
this.logCreationInfo({ appDirectory, appName });
const confirmed = await this.promptScaffoldConfirmation({
appName,
appDisplayName,
appDescription,
appDirectory,
autoConfirm: options.yes,
});
if (!confirmed) {
console.log(chalk.gray('\nScaffolding cancelled.'));
process.exit(0);
}
console.log('');
this.logStep(1, 'Creating project directory');
await fs.ensureDir(appDirectory);
this.logDetail(appDirectory);
this.logStep(2, 'Scaffolding project files');
if (options.example) {
const exampleSucceeded = await this.tryDownloadExample(
@@ -56,6 +74,7 @@ export class CreateAppCommand {
appDisplayName,
appDescription,
appDirectory,
onProgress: (message) => this.logDetail(message),
});
}
} else {
@@ -64,33 +83,59 @@ export class CreateAppCommand {
appDisplayName,
appDescription,
appDirectory,
onProgress: (message) => this.logDetail(message),
});
}
await install(appDirectory);
this.logStep(3, 'Installing dependencies');
await install(appDirectory, (message) => this.logDetail(message));
await tryGitInit(appDirectory);
this.logStep(4, 'Initializing Git repository');
const gitInitialized = await tryGitInit(appDirectory);
let serverResult: ServerStartResult | undefined;
if (gitInitialized) {
this.logDetail('Initialized on branch main');
this.logDetail('Created initial commit');
} else {
this.logDetail(
'Skipped (Git unavailable, initialization failed, or already in a repository)',
);
}
console.log('');
let hasLocalServer = false;
let authSucceeded = false;
if (!options.skipLocalInstance) {
const shouldStartServer = await this.shouldStartServer(options.yes);
const existingServerUrl = await detectLocalServer();
if (shouldStartServer) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
if (existingServerUrl) {
hasLocalServer = true;
authSucceeded = await this.promptConnectToLocal(existingServerUrl);
} else {
const shouldStart = await this.shouldStartServer(options.yes);
if (startResult.success) {
serverResult = startResult.data;
await this.promptConnectToLocal(serverResult.url);
if (shouldStart) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
if (startResult.success) {
hasLocalServer = true;
authSucceeded = await this.promptConnectToLocal(
startResult.data.url,
);
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
}
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
this.logServerSkipped();
}
}
}
this.logSuccess(appDirectory, serverResult);
this.logSuccess(appDirectory, hasLocalServer, authSucceeded);
} catch (error) {
console.error(
chalk.red('\nCreate application failed:'),
@@ -213,25 +258,80 @@ export class CreateAppCommand {
}
}
private logCreationInfo({
appDirectory,
private async promptScaffoldConfirmation({
appName,
appDisplayName,
appDescription,
appDirectory,
autoConfirm,
}: {
appDirectory: string;
appName: string;
}): void {
appDisplayName: string;
appDescription: string;
appDirectory: string;
autoConfirm?: boolean;
}): Promise<boolean> {
console.log(chalk.blue('\nCreating Twenty Application\n'));
console.log(chalk.white(` Name: ${appName}`));
console.log(chalk.white(` Display name: ${appDisplayName}`));
if (appDescription) {
console.log(chalk.white(` Description: ${appDescription}`));
}
console.log(chalk.white(` Directory: ${appDirectory}`));
console.log(chalk.white('\nThe following steps will be performed:\n'));
console.log(chalk.gray(' 1. Create project directory'));
console.log(
chalk.blue('\n', 'Creating Twenty Application\n'),
chalk.gray(`- Directory: ${appDirectory}\n`, `- Name: ${appName}\n`),
chalk.gray(
' 2. Scaffold project files from base template\n' +
' - Copy template files\n' +
' - Configure dotfiles (.gitignore, .github)\n' +
' - Generate unique application identifiers\n' +
' - Update package.json with app name and SDK versions',
),
);
console.log(chalk.gray(' 3. Install dependencies (yarn)'));
console.log(
chalk.gray(' 4. Initialize Git repository with initial commit'),
);
console.log('');
if (autoConfirm) {
return true;
}
const { proceed } = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Proceed?',
default: true,
},
]);
return proceed;
}
private logStep(step: number, title: string): void {
console.log(
chalk.blue(`\n[${step}/${CreateAppCommand.TOTAL_STEPS}]`) +
chalk.white(` ${title}...`),
);
}
private async shouldStartServer(autoConfirm?: boolean): Promise<boolean> {
const existingServerUrl = await detectLocalServer();
private logDetail(message: string): void {
console.log(chalk.gray(`${message}`));
}
if (existingServerUrl) {
return true;
}
private async shouldStartServer(autoConfirm?: boolean): Promise<boolean> {
console.log(
chalk.white(
'\n A local Twenty instance is required for app development.\n' +
' It provides the API and schema your application connects to.\n',
),
);
if (checkDockerRunning() && containerExists()) {
if (autoConfirm) {
@@ -268,12 +368,31 @@ export class CreateAppCommand {
return startDocker;
}
private async promptConnectToLocal(serverUrl: string): Promise<void> {
private logServerSkipped(): void {
console.log(
chalk.gray(
'\n To start a Twenty instance later:\n' +
' yarn twenty server start\n\n' +
' To connect to a remote instance instead:\n' +
' yarn twenty remote add\n',
),
);
}
private async promptConnectToLocal(serverUrl: string): Promise<boolean> {
console.log(
chalk.white(
'\n Authentication links your app to a Twenty instance so you can\n' +
' sync custom objects, fields, and roles during development.\n' +
' This will open a browser window to complete the OAuth flow.\n',
),
);
const { shouldAuthenticate } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldAuthenticate',
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
message: `Authenticate to the local Twenty instance (${serverUrl})?`,
default: true,
},
]);
@@ -281,13 +400,22 @@ export class CreateAppCommand {
if (!shouldAuthenticate) {
console.log(
chalk.gray(
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
'\n Authentication skipped. To authenticate later:\n' +
` yarn twenty remote add --local\n`,
),
);
return;
return false;
}
await inquirer.prompt([
{
type: 'input',
name: 'confirm',
message: 'Press Enter to open the browser for authentication...',
},
]);
try {
const result = await authLoginOAuth({
apiUrl: serverUrl,
@@ -298,12 +426,16 @@ export class CreateAppCommand {
const configService = new ConfigService();
await configService.setDefaultRemote('local');
return true;
} else {
console.log(
chalk.yellow(
'Authentication failed. Run `yarn twenty remote add --local` manually.',
),
);
return false;
}
} catch {
console.log(
@@ -311,28 +443,44 @@ export class CreateAppCommand {
'Authentication failed. Run `yarn twenty remote add` manually.',
),
);
return false;
}
}
private logSuccess(
appDirectory: string,
serverResult?: ServerStartResult,
hasLocalServer: boolean,
authSucceeded: boolean,
): void {
const dirName = basename(appDirectory);
console.log(chalk.blue('\nApplication created. Next steps:'));
console.log(chalk.gray(`- cd ${dirName}`));
console.log(chalk.green('\nApplication created successfully!\n'));
console.log(chalk.white(' Next steps:\n'));
if (!serverResult) {
console.log(
chalk.gray(
'- yarn twenty remote add # Authenticate with Twenty',
),
);
let stepNumber = 1;
console.log(chalk.white(` ${stepNumber}. Navigate to your project`));
console.log(chalk.cyan(` cd ${dirName}\n`));
stepNumber++;
if (!authSucceeded) {
const remoteCommand = hasLocalServer
? 'yarn twenty remote add --local'
: 'yarn twenty remote add';
console.log(chalk.white(` ${stepNumber}. Connect to a Twenty instance`));
console.log(chalk.cyan(` ${remoteCommand}\n`));
stepNumber++;
}
console.log(chalk.white(` ${stepNumber}. Start developing`));
console.log(chalk.cyan(' yarn twenty dev\n'));
console.log(
chalk.gray('- yarn twenty dev # Start dev mode'),
chalk.gray(
' Documentation: https://docs.twenty.com/developers/extend/capabilities/apps',
),
);
}
}
@@ -3,7 +3,6 @@ import { join } from 'path';
import { v4 } from 'uuid';
import createTwentyAppPackageJson from 'package.json';
import chalk from 'chalk';
const SRC_FOLDER = 'src';
@@ -12,27 +11,33 @@ export const copyBaseApplicationProject = async ({
appDisplayName,
appDescription,
appDirectory,
onProgress,
}: {
appName: string;
appDisplayName: string;
appDescription: string;
appDirectory: string;
onProgress?: (message: string) => void;
}) => {
console.log(chalk.gray('Generating application project...'));
onProgress?.('Copying base template');
await fs.copy(join(__dirname, './constants/template'), appDirectory);
onProgress?.('Configuring dotfiles (.gitignore, .github)');
await renameDotfiles({ appDirectory });
onProgress?.('Mirroring AGENTS.md to CLAUDE.md');
await mirrorAgentsToClaude({ appDirectory });
await addEmptyPublicDirectory({ appDirectory });
onProgress?.('Generating unique application identifiers');
await generateUniversalIdentifiers({
appDisplayName,
appDescription,
appDirectory,
});
onProgress?.('Updating package.json');
await updatePackageJson({ appName, appDirectory });
};
@@ -4,14 +4,18 @@ import { exec } from 'child_process';
const execPromise = promisify(exec);
export const install = async (root: string) => {
console.log(chalk.gray('Installing yarn dependencies...'));
export const install = async (
root: string,
onProgress?: (message: string) => void,
) => {
onProgress?.('Enabling corepack');
try {
await execPromise('corepack enable', { cwd: root });
} catch (error: any) {
console.warn(chalk.yellow('corepack enabled failed:'), error.stderr);
console.warn(chalk.yellow('corepack enable failed:'), error.stderr);
}
onProgress?.('Running yarn install');
try {
await execPromise('yarn install', { cwd: root });
} catch (error: any) {
@@ -0,0 +1,14 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/apps/getting-started
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -0,0 +1,14 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/apps/getting-started
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -0,0 +1,14 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/apps/getting-started
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
@@ -20,7 +20,7 @@
"test:watch": "vitest --config vitest.unit.config.ts"
},
"dependencies": {
"twenty-sdk": "2.1.0"
"twenty-sdk": "2.3.0"
},
"devDependencies": {
"@types/node": "^24.7.2",
@@ -31,4 +31,35 @@ export default defineLogicFunction({
required: ['teamId', 'title'],
},
},
workflowActionTriggerSettings: {
label: 'Create Linear Issue',
inputSchema: [
{
type: 'object',
properties: {
teamId: { type: 'string' },
title: { type: 'string' },
description: { type: 'string' },
},
},
],
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
issue: {
type: 'object',
properties: {
id: { type: 'string' },
identifier: { type: 'string' },
title: { type: 'string' },
url: { type: 'string' },
},
},
error: { type: 'string' },
},
},
],
},
});
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
{"tags": ["scope:apps"]}
@@ -1752,6 +1752,7 @@ enum FeatureFlagKey {
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_EMAIL_GROUP_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
IS_RICH_TEXT_V1_MIGRATED
@@ -1926,6 +1927,7 @@ type ClientConfig {
isGoogleCalendarEnabled: Boolean!
isConfigVariablesInDbEnabled: Boolean!
isImapSmtpCaldavEnabled: Boolean!
isEmailGroupEnabled: Boolean!
allowRequestsToTwentyIcons: Boolean!
calendarBookingPageId: String
isCloudflareIntegrationEnabled: Boolean!
@@ -2354,6 +2356,7 @@ type PublicDomain {
id: UUID!
domain: String!
isValidated: Boolean!
applicationId: UUID
createdAt: DateTime!
}
@@ -2777,6 +2780,7 @@ type MessageChannel {
connectedAccountId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
connectedAccount: ConnectedAccountPublicDTO
}
enum MessageChannelVisibility {
@@ -2788,6 +2792,7 @@ enum MessageChannelVisibility {
enum MessageChannelType {
EMAIL
SMS
EMAIL_GROUP
}
enum MessageChannelContactAutoCreationPolicy {
@@ -2826,6 +2831,11 @@ enum MessageChannelSyncStage {
FAILED
}
type CreateEmailGroupChannelOutput {
messageChannel: MessageChannel!
forwardingAddress: String!
}
type MessageFolder {
id: UUID!
name: String
@@ -3243,6 +3253,8 @@ type Mutation {
updateMessageFolder(input: UpdateMessageFolderInput!): MessageFolder!
updateMessageFolders(input: UpdateMessageFoldersInput!): [MessageFolder!]!
updateMessageChannel(input: UpdateMessageChannelInput!): MessageChannel!
createEmailGroupChannel(input: CreateEmailGroupChannelInput!): CreateEmailGroupChannelOutput!
deleteEmailGroupChannel(id: UUID!): MessageChannel!
deleteConnectedAccount(id: UUID!): ConnectedAccountDTO!
updateCalendarChannel(input: UpdateCalendarChannelInput!): CalendarChannel!
createWebhook(input: CreateWebhookInput!): Webhook!
@@ -3316,7 +3328,8 @@ type Mutation {
updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag!
enablePostgresProxy: PostgresCredentials!
disablePostgresProxy: PostgresCredentials!
createPublicDomain(domain: String!): PublicDomain!
createPublicDomain(domain: String!, applicationId: String): PublicDomain!
updatePublicDomain(domain: String!, applicationId: String): PublicDomain!
deletePublicDomain(domain: String!): Boolean!
checkPublicDomainValidRecords(domain: String!): DomainValidRecords
createEmailingDomain(domain: String!, driver: EmailingDomainDriver!): EmailingDomain!
@@ -4171,6 +4184,10 @@ input UpdateMessageChannelInputUpdates {
excludeGroupEmails: Boolean
}
input CreateEmailGroupChannelInput {
handle: String!
}
input UpdateCalendarChannelInput {
id: UUID!
update: UpdateCalendarChannelInputUpdates!
@@ -1388,7 +1388,7 @@ export interface FeatureFlag {
__typename: 'FeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' | 'IS_BILLING_V2_ENABLED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' | 'IS_BILLING_V2_ENABLED'
export interface WorkspaceUrls {
customUrl?: Scalars['String']
@@ -1554,6 +1554,7 @@ export interface ClientConfig {
isGoogleCalendarEnabled: Scalars['Boolean']
isConfigVariablesInDbEnabled: Scalars['Boolean']
isImapSmtpCaldavEnabled: Scalars['Boolean']
isEmailGroupEnabled: Scalars['Boolean']
allowRequestsToTwentyIcons: Scalars['Boolean']
calendarBookingPageId?: Scalars['String']
isCloudflareIntegrationEnabled: Scalars['Boolean']
@@ -2025,6 +2026,7 @@ export interface PublicDomain {
id: Scalars['UUID']
domain: Scalars['String']
isValidated: Scalars['Boolean']
applicationId?: Scalars['UUID']
createdAt: Scalars['DateTime']
__typename: 'PublicDomain'
}
@@ -2459,12 +2461,13 @@ export interface MessageChannel {
connectedAccountId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
connectedAccount?: ConnectedAccountPublicDTO
__typename: 'MessageChannel'
}
export type MessageChannelVisibility = 'METADATA' | 'SUBJECT' | 'SHARE_EVERYTHING'
export type MessageChannelType = 'EMAIL' | 'SMS'
export type MessageChannelType = 'EMAIL' | 'SMS' | 'EMAIL_GROUP'
export type MessageChannelContactAutoCreationPolicy = 'SENT_AND_RECEIVED' | 'SENT' | 'NONE'
@@ -2476,6 +2479,12 @@ export type MessageChannelSyncStatus = 'NOT_SYNCED' | 'ONGOING' | 'ACTIVE' | 'FA
export type MessageChannelSyncStage = 'PENDING_CONFIGURATION' | 'MESSAGE_LIST_FETCH_PENDING' | 'MESSAGE_LIST_FETCH_SCHEDULED' | 'MESSAGE_LIST_FETCH_ONGOING' | 'MESSAGES_IMPORT_PENDING' | 'MESSAGES_IMPORT_SCHEDULED' | 'MESSAGES_IMPORT_ONGOING' | 'FAILED'
export interface CreateEmailGroupChannelOutput {
messageChannel: MessageChannel
forwardingAddress: Scalars['String']
__typename: 'CreateEmailGroupChannelOutput'
}
export interface MessageFolder {
id: Scalars['UUID']
name?: Scalars['String']
@@ -2772,6 +2781,8 @@ export interface Mutation {
updateMessageFolder: MessageFolder
updateMessageFolders: MessageFolder[]
updateMessageChannel: MessageChannel
createEmailGroupChannel: CreateEmailGroupChannelOutput
deleteEmailGroupChannel: MessageChannel
deleteConnectedAccount: ConnectedAccountDTO
updateCalendarChannel: CalendarChannel
createWebhook: Webhook
@@ -2846,6 +2857,7 @@ export interface Mutation {
enablePostgresProxy: PostgresCredentials
disablePostgresProxy: PostgresCredentials
createPublicDomain: PublicDomain
updatePublicDomain: PublicDomain
deletePublicDomain: Scalars['Boolean']
checkPublicDomainValidRecords?: DomainValidRecords
createEmailingDomain: EmailingDomain
@@ -4495,6 +4507,7 @@ export interface ClientConfigGenqlSelection{
isGoogleCalendarEnabled?: boolean | number
isConfigVariablesInDbEnabled?: boolean | number
isImapSmtpCaldavEnabled?: boolean | number
isEmailGroupEnabled?: boolean | number
allowRequestsToTwentyIcons?: boolean | number
calendarBookingPageId?: boolean | number
isCloudflareIntegrationEnabled?: boolean | number
@@ -5024,6 +5037,7 @@ export interface PublicDomainGenqlSelection{
id?: boolean | number
domain?: boolean | number
isValidated?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
@@ -5487,6 +5501,14 @@ export interface MessageChannelGenqlSelection{
connectedAccountId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
connectedAccount?: ConnectedAccountPublicDTOGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface CreateEmailGroupChannelOutputGenqlSelection{
messageChannel?: MessageChannelGenqlSelection
forwardingAddress?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -5828,6 +5850,8 @@ export interface MutationGenqlSelection{
updateMessageFolder?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFolderInput} })
updateMessageFolders?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFoldersInput} })
updateMessageChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateMessageChannelInput} })
createEmailGroupChannel?: (CreateEmailGroupChannelOutputGenqlSelection & { __args: {input: CreateEmailGroupChannelInput} })
deleteEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {id: Scalars['UUID']} })
deleteConnectedAccount?: (ConnectedAccountDTOGenqlSelection & { __args: {id: Scalars['UUID']} })
updateCalendarChannel?: (CalendarChannelGenqlSelection & { __args: {input: UpdateCalendarChannelInput} })
createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} })
@@ -5901,7 +5925,8 @@ export interface MutationGenqlSelection{
updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} })
enablePostgresProxy?: PostgresCredentialsGenqlSelection
disablePostgresProxy?: PostgresCredentialsGenqlSelection
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String']} })
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
deletePublicDomain?: { __args: {domain: Scalars['String']} }
checkPublicDomainValidRecords?: (DomainValidRecordsGenqlSelection & { __args: {domain: Scalars['String']} })
createEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {domain: Scalars['String'], driver: EmailingDomainDriver} })
@@ -6206,6 +6231,8 @@ export interface UpdateMessageChannelInput {id: Scalars['UUID'],update: UpdateMe
export interface UpdateMessageChannelInputUpdates {visibility?: (MessageChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (MessageChannelContactAutoCreationPolicy | null),messageFolderImportPolicy?: (MessageFolderImportPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null),excludeNonProfessionalEmails?: (Scalars['Boolean'] | null),excludeGroupEmails?: (Scalars['Boolean'] | null)}
export interface CreateEmailGroupChannelInput {handle: Scalars['String']}
export interface UpdateCalendarChannelInput {id: Scalars['UUID'],update: UpdateCalendarChannelInputUpdates}
export interface UpdateCalendarChannelInputUpdates {visibility?: (CalendarChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (CalendarChannelContactAutoCreationPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null)}
@@ -8163,6 +8190,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const CreateEmailGroupChannelOutput_possibleTypes: string[] = ['CreateEmailGroupChannelOutput']
export const isCreateEmailGroupChannelOutput = (obj?: { __typename?: any } | null): obj is CreateEmailGroupChannelOutput => {
if (!obj?.__typename) throw new Error('__typename is missing in "isCreateEmailGroupChannelOutput"')
return CreateEmailGroupChannelOutput_possibleTypes.includes(obj.__typename)
}
const MessageFolder_possibleTypes: string[] = ['MessageFolder']
export const isMessageFolder = (obj?: { __typename?: any } | null): obj is MessageFolder => {
if (!obj?.__typename) throw new Error('__typename is missing in "isMessageFolder"')
@@ -8691,6 +8726,7 @@ export const enumFeatureFlagKey = {
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
@@ -8790,7 +8826,8 @@ export const enumMessageChannelVisibility = {
export const enumMessageChannelType = {
EMAIL: 'EMAIL' as const,
SMS: 'SMS' as const
SMS: 'SMS' as const,
EMAIL_GROUP: 'EMAIL_GROUP' as const
}
export const enumMessageChannelContactAutoCreationPolicy = {
File diff suppressed because it is too large Load Diff
@@ -37,7 +37,7 @@ Both are available as REST and GraphQL. GraphQL adds batch upserts and the abili
Authorization: Bearer YOUR_API_KEY
```
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Members → Roles → Assignment tab** to limit what they can access.
<VimeoEmbed videoId="928786722" title="Creating API key" />
@@ -13,7 +13,6 @@ Every app must have exactly one `defineApplication` call. It declares:
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
@@ -27,19 +26,19 @@ export default defineApplication({
isSecret: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
Notes:
- `universalIdentifier` fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
- `applicationVariables` become environment variables for your functions and front components (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `defaultRoleUniversalIdentifier` must reference a role defined with [`defineRole()`](/developers/extend/apps/config/roles).
- The default role is detected automatically from the role file marked with [`defineApplicationRole()`](/developers/extend/apps/config/roles) — you do not need to reference it from `defineApplication()`.
- Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
- Passing `defaultRoleUniversalIdentifier` explicitly is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`.
## Default function role
The `defaultRoleUniversalIdentifier` controls what the app's logic functions and front components can access:
The role declared with [`defineApplicationRole()`](/developers/extend/apps/config/roles) controls what the app's logic functions and front components can access:
- The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role.
- The typed API client is restricted to the permissions granted to that role.
@@ -4,7 +4,7 @@ description: Declare what objects and fields your app's logic functions and fron
icon: "shield-halved"
---
A **role** is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app's logic functions and front components inherit the permissions of the role declared as `defaultRoleUniversalIdentifier` in [`defineApplication`](/developers/extend/apps/config/application).
A **role** is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app's logic functions and front components inherit the permissions of the role marked with `defineApplicationRole()` (see [The default function role](#the-default-function-role) below).
```ts src/roles/restricted-company-role.ts
import {
@@ -51,15 +51,15 @@ export default defineRole({
## The default function role
When you scaffold a new app, the CLI creates a default role file:
When you scaffold a new app, the CLI creates a default role file declared with `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
@@ -77,10 +77,12 @@ export default defineRole({
});
```
This role's `universalIdentifier` is referenced from `application-config.ts` as `defaultRoleUniversalIdentifier`:
`defineApplicationRole()` is a thin wrapper around `defineRole()` that flags **the** role used as your application's default at install time. Validation is identical to `defineRole`, but the build pipeline auto-wires its `universalIdentifier` into the application manifest's `defaultRoleUniversalIdentifier` — so you do not need to reference it from [`defineApplication`](/developers/extend/apps/config/application) yourself.
- **`*.role.ts`** declares what the role can do.
- **`application-config.ts`** points to that role so your functions inherit its permissions.
Notes:
- Exactly **one** `defineApplicationRole(...)` is allowed per app — the manifest build will fail if it finds more than one.
- Use `defineRole()` (not `defineApplicationRole()`) for any **additional** roles your app ships.
- Setting `defaultRoleUniversalIdentifier` explicitly on `defineApplication()` is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`.
## Best practices
@@ -52,7 +52,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
defaultRoleUniversalIdentifier: '...',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
@@ -372,5 +372,5 @@ Key points:
- `TWENTY_API_URL` — Base URL of the Twenty API
- `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role
You do **not** need to pass these to the clients — they read from `process.env` automatically. The API key's permissions are determined by the role referenced in `defaultRoleUniversalIdentifier` in your `application-config.ts`.
You do **not** need to pass these to the clients — they read from `process.env` automatically. The API key's permissions are determined by the role declared with `defineApplicationRole()` (or referenced via `defaultRoleUniversalIdentifier` in `application-config.ts`).
</Note>
@@ -187,7 +187,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
@@ -83,7 +83,7 @@ Your API key grants access to sensitive data. Don't share it with untrusted serv
For better security, assign a specific role to limit access:
1. Go to **Settings → Roles**
1. Go to **Settings → Members → Roles**
2. Click on the role to assign
3. Open the **Assignment** tab
4. Under **API Keys**, click **+ Assign to API key**
+1 -1
View File
@@ -4293,7 +4293,7 @@
]
},
{
"group": "Data",
"group": "Dados",
"pages": [
"l/pt/developers/extend/apps/data/overview",
"l/pt/developers/extend/apps/data/objects",
@@ -37,7 +37,7 @@ Beide sind als REST und GraphQL verfügbar. GraphQL bietet Batch-Upserts und die
Authorization: Bearer YOUR_API_KEY
```
Erstellen Sie einen API-Schlüssel unter **Settings > APIs & Webhooks > + Create key**. Kopieren Sie ihn sofort — er wird nur einmal angezeigt. Schlüssel können unter **Settings > Roles > Assignment tab** auf eine bestimmte Rolle beschränkt werden, um ihren Zugriff einzuschränken.
Erstellen Sie einen API-Schlüssel unter **Settings > APIs & Webhooks > + Create key**. Kopieren Sie ihn sofort — er wird nur einmal angezeigt. Schlüssel können unter **Settings → Members → Roles Assignment tab** auf eine bestimmte Rolle beschränkt werden, um ihren Zugriff einzuschränken.
<VimeoEmbed videoId="928786722" title="API-Schlüssel erstellen" />
@@ -13,7 +13,6 @@ Jede App muss genau einen Aufruf von `defineApplication` haben. Dieser deklarier
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
@@ -27,7 +26,6 @@ export default defineApplication({
isSecret: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
@@ -35,12 +33,13 @@ Notizen:
* `universalIdentifier`-Felder sind deterministische IDs, die Ihnen gehören. Erzeugen Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen und Frontend-Komponenten (z. B. ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
* `defaultRoleUniversalIdentifier` muss auf eine mit [`defineRole()`](/l/de/developers/extend/apps/config/roles) definierte Rolle verweisen.
* Die Standardrolle wird automatisch aus der Rollen-Datei erkannt, die mit [`defineApplicationRole()`](/l/de/developers/extend/apps/config/roles) markiert ist Sie müssen sie nicht aus `defineApplication()` referenzieren.
* Pre- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt — Sie müssen sie in `defineApplication()` nicht referenzieren.
* Die explizite Übergabe von `defaultRoleUniversalIdentifier` wird für die Abwärtskompatibilität weiterhin unterstützt, ist jedoch zugunsten von `defineApplicationRole()` veraltet.
## Standard-Funktionsrolle
Der `defaultRoleUniversalIdentifier` steuert, worauf die Logikfunktionen und Frontend-Komponenten der App zugreifen können:
Die mit [`defineApplicationRole()`](/l/de/developers/extend/apps/config/roles) deklarierte Rolle steuert, worauf die Logikfunktionen und Frontend-Komponenten der App zugreifen können:
* Das zur Laufzeit als `TWENTY_APP_ACCESS_TOKEN` injizierte Token wird aus dieser Rolle abgeleitet.
* Der typisierte API-Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt.
@@ -20,9 +20,9 @@ Jede App darf **höchstens eine Pre-Install-Funktion** und **höchstens eine Pos
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Wird ausgeführt, nachdem die Workspace-Metadatenmigration angewendet wurde">
<Accordion title="definePostInstallLogicFunction" description="Wird ausgeführt, nachdem die Metadatenmigration des Arbeitsbereichs angewendet wurde">
Eine Post-Install-Funktion wird automatisch ausgeführt, sobald Ihre App die Installation in einem Workspace abgeschlossen hat. Der Server führt sie **nach** der Synchronisierung der Metadaten der App und der Generierung des SDK-Clients aus, sodass der Arbeitsbereich vollständig einsatzbereit ist und das neue Schema bereitsteht. Typische Anwendungsfälle umfassen das Befüllen von Standarddaten, das Erstellen anfänglicher Datensätze, das Konfigurieren von Arbeitsbereichseinstellungen oder das Bereitstellen von Ressourcen bei Diensten von Drittanbietern.
Eine Post-Install-Funktion wird automatisch ausgeführt, sobald Ihre App die Installation in einem Arbeitsbereich abgeschlossen hat. Der Server führt sie **nach** der Synchronisierung der Metadaten der App und der Generierung des SDK-Clients aus, sodass der Arbeitsbereich vollständig einsatzbereit ist und das neue Schema bereitsteht. Typische Anwendungsfälle umfassen das Befüllen von Standarddaten, das Erstellen anfänglicher Datensätze, das Konfigurieren von Arbeitsbereichseinstellungen oder das Bereitstellen von Ressourcen bei Diensten von Drittanbietern.
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -60,12 +60,12 @@ Hauptpunkte:
* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird.
* Die `universalIdentifier`, `shouldRunOnVersionUpgrade` und `shouldRunSynchronously` der Funktion werden während des Builds automatisch dem Anwendungsmanifest unter dem Feld `postInstallLogicFunction` hinzugefügt  Sie müssen sie in [`defineApplication()`](/l/de/developers/extend/apps/config/application) nicht referenzieren.
* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen.
* **Nicht im Dev-Modus ausgeführt**: Wenn eine App lokal registriert ist (über `yarn twenty dev`), überspringt der Server den Installationsablauf vollständig und synchronisiert Dateien direkt über den CLI-Watcher — daher läuft Post-Install im Dev-Modus nie, unabhängig von `shouldRunSynchronously`. Verwenden Sie `yarn twenty exec --postInstall`, um es manuell gegen einen laufenden Workspace auszulösen.
* **Nicht im Dev-Modus ausgeführt**: Wenn eine App lokal registriert ist (über `yarn twenty dev`), überspringt der Server den Installationsablauf vollständig und synchronisiert Dateien direkt über den CLI-Watcher — daher läuft Post-Install im Dev-Modus nie, unabhängig von `shouldRunSynchronously`. Verwenden Sie `yarn twenty exec --postInstall`, um es manuell gegen einen laufenden Arbeitsbereich auszulösen.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Wird ausgeführt, bevor die Workspace-Metadatenmigration angewendet wird">
<Accordion title="definePreInstallLogicFunction" description="Wird ausgeführt, bevor die Metadatenmigration des Arbeitsbereichs angewendet wird">
Eine Pre-Install-Funktion wird automatisch während der Installation ausgeführt, **bevor die Workspace-Metadatenmigration angewendet wird**. Sie hat die gleiche Payload-Struktur wie Post-Install (`InstallPayload`), ist aber früher im Installationsablauf positioniert, sodass sie Zustände vorbereiten kann, von denen die bevorstehende Migration abhängt — typische Anwendungsfälle sind das Sichern von Daten, die Validierung der Kompatibilität mit dem neuen Schema oder das Archivieren von Datensätzen, die umstrukturiert oder entfernt werden sollen.
Eine Pre-Install-Funktion wird automatisch während der Installation ausgeführt, **bevor die Metadatenmigration des Arbeitsbereichs angewendet wird**. Sie hat die gleiche Payload-Struktur wie Post-Install (`InstallPayload`), ist aber früher im Installationsablauf positioniert, sodass sie Zustände vorbereiten kann, von denen die bevorstehende Migration abhängt — typische Anwendungsfälle sind das Sichern von Daten, die Validierung der Kompatibilität mit dem neuen Schema oder das Archivieren von Datensätzen, die umstrukturiert oder entfernt werden sollen.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
@@ -93,8 +93,8 @@ yarn twenty exec --preInstall
Hauptpunkte:
* Pre-Install-Funktionen verwenden `definePreInstallLogicFunction()` — dieselbe spezialisierte Konfiguration wie bei Post-Install, nur an einen anderen Lifecycle-Slot gebunden.
* Sowohl Pre- als auch Post-Install-Handler erhalten denselben `InstallPayload`-Typ: `{ previousVersion?: string; newVersion: string }`. Importieren Sie ihn einmal und verwenden Sie ihn für beide Hooks wieder.
* **Wann der Hook ausgeführt wird**: positioniert direkt vor der Metadatenmigration des Workspaces (`synchronizeFromManifest`). Vor der Ausführung führt der Server einen rein additiven "pared-down sync" durch, der die Pre-Install-Funktion der **neuen** Version in den Workspace-Metadaten registriert — sonst wird nichts angefasst — und führt sie dann aus. Da dieser Sync nur additiv ist, sind die Objekte, Felder und Daten der vorherigen Version noch intakt, wenn Ihr Handler läuft: Sie können den Zustand vor der Migration gefahrlos lesen und sichern.
* **Ausführungsmodell**: Pre-Install wird **synchron** ausgeführt und **blockiert die Installation**. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor Schemaänderungen angewendet werden — der Workspace verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen.
* **Wann der Hook ausgeführt wird**: positioniert direkt vor der Metadatenmigration des Arbeitsbereichs (`synchronizeFromManifest`). Vor der Ausführung führt der Server einen rein additiven "pared-down sync" durch, der die Pre-Install-Funktion der **neuen** Version in den Metadaten des Arbeitsbereichs registriert — sonst wird nichts angefasst — und führt sie dann aus. Da dieser Sync nur additiv ist, sind die Objekte, Felder und Daten der vorherigen Version noch intakt, wenn Ihr Handler läuft: Sie können den Zustand vor der Migration gefahrlos lesen und sichern.
* **Ausführungsmodell**: Pre-Install wird **synchron** ausgeführt und **blockiert die Installation**. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor Schemaänderungen angewendet werden — der Arbeitsbereich verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen.
* Wie bei Post-Install ist pro Anwendung nur eine Pre-Installationsfunktion zulässig. Sie wird während des Builds automatisch dem Anwendungsmanifest unter `preInstallLogicFunction` hinzugefügt.
* **Nicht im Dev-Modus ausgeführt**: wie bei Post-Install — der Installationsablauf wird für lokal registrierte Apps vollständig übersprungen, daher läuft Pre-Install unter `yarn twenty dev` nie. Verwenden Sie `yarn twenty exec --preInstall`, um es manuell auszulösen.
@@ -190,7 +190,7 @@ export default definePreInstallLogicFunction({
| Sie möchten ... | Verwenden |
| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Standarddaten befüllen, den Workspace konfigurieren, externe Ressourcen registrieren | `post-install` |
| Standarddaten befüllen, den Arbeitsbereich konfigurieren, externe Ressourcen registrieren | `post-install` |
| Lang laufendes Seeding oder Drittanbieteraufrufe ausführen, die die Installationsantwort nicht blockieren sollten | `post-install` (Standard — `shouldRunSynchronously: false`, mit Worker-Wiederholungen) |
| Schnelle Einrichtung ausführen, auf die sich der Aufrufer unmittelbar nach der Rückkehr des Installationsaufrufs verlassen wird | `post-install` mit `shouldRunSynchronously: true` |
| Daten lesen oder sichern, die bei der bevorstehenden Migration verloren gingen | `pre-install` |
@@ -29,7 +29,7 @@ Die **Konfigurationsebene** einer Twenty-App beschreibt die App *für die Plattf
## In diesem Abschnitt
<CardGroup cols={2}>
<Card title="Anwendungskonfiguration" icon="rocket" href="/l/de/developers/extend/apps/config/application">
<Card title="App-Konfiguration" icon="rocket" href="/l/de/developers/extend/apps/config/application">
`defineApplication` Identität, Standardrolle, Variablen, Marketplace-Metadaten.
</Card>
<Card title="Rollen & Berechtigungen" icon="shield-halved" href="/l/de/developers/extend/apps/config/roles">
@@ -42,8 +42,8 @@ Die **Konfigurationsebene** einer Twenty-App beschreibt die App *für die Plattf
## Wie die Bausteine zusammenhängen
* **Application** ist der Einstiegspunkt. Jede App hat genau einen `defineApplication()`-Aufruf, und dieser verweist auf eine **Role** als Standard.
* Die **Role** steuert, was die Logikfunktionen und Front-Komponenten der App lesen und schreiben können. Folgen Sie dem Prinzip der geringsten Privilegien: Gewähren Sie nur die Berechtigungen, die Ihr Code tatsächlich benötigt.
* **Application** ist der Einstiegspunkt. Jede App hat genau einen `defineApplication()`-Aufruf, und dieser verweist auf eine **Rolle** als Standard.
* Die **Rolle** steuert, was die Logikfunktionen und Frontend-Komponenten der App lesen und schreiben können. Folgen Sie dem Prinzip der geringsten Privilegien: Gewähren Sie nur die Berechtigungen, die Ihr Code tatsächlich benötigt.
* **Install Hooks** laufen während der Installation oder Aktualisierung Pre-Install vor der Metadatenmigration (so kann ein riskantes Upgrade abgelehnt werden), Post-Install nach der Migration (so können Standarddaten gegen das neue Schema befüllt werden).
<Note>
@@ -1,6 +1,6 @@
---
title: Öffentliche Assets
description: Liefere statische Dateien Bilder, Symbole, Schriftarten zusammen mit deiner App über den Ordner public/.
description: Liefern Sie statische Dateien Bilder, Icons, Schriftarten zusammen mit Ihrer App über den Ordner public/ aus.
icon: folder-open
---
@@ -1,10 +1,10 @@
---
title: Rollen & Berechtigungen
description: Legen Sie fest, welche Objekte und Felder die Logikfunktionen und Front-Komponenten Ihrer App lesen und schreiben können.
description: Legen Sie fest, welche Objekte und Felder die Logikfunktionen und Frontend-Komponenten Ihrer App lesen und schreiben können.
icon: shield-halved
---
Eine **Rolle** ist ein Berechtigungssatz: welche Objekte eine App lesen oder schreiben kann, welche Felder sie sehen kann und welche plattformbezogenen Funktionen sie nutzen kann. Alle Logikfunktionen und Front-Komponenten einer App erben die Berechtigungen der Rolle, die in `defineApplication` als `defaultRoleUniversalIdentifier` deklariert ist ([`defineApplication`](/l/de/developers/extend/apps/config/application)).
Eine **Rolle** ist ein Berechtigungssatz: welche Objekte eine App lesen oder schreiben kann, welche Felder sie sehen kann und welche plattformbezogenen Funktionen sie nutzen kann. Alle Logikfunktionen und Frontend-Komponenten einer App erben die Berechtigungen der Rolle, die mit `defineApplicationRole()` markiert ist (siehe [Die Standardfunktionsrolle](#the-default-function-role) unten).
```ts src/roles/restricted-company-role.ts
import {
@@ -51,15 +51,15 @@ export default defineRole({
## Die Standard-Funktionsrolle
Wenn Sie eine neue App erzeugen, erstellt die CLI eine Standard-Rolldatei:
Wenn Sie eine neue App erzeugen, erstellt die CLI eine Datei für die Standardrolle, die mit `defineApplicationRole()` deklariert ist:
```ts src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
@@ -77,10 +77,13 @@ export default defineRole({
});
```
Der `universalIdentifier` dieser Rolle wird in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert:
`defineApplicationRole()` ist ein dünner Wrapper um `defineRole()`, der **die** Rolle kennzeichnet, die zum Installationszeitpunkt als Standardrolle Ihrer Anwendung verwendet wird. Die Validierung ist identisch zu `defineRole`, aber die Build-Pipeline verdrahtet deren `universalIdentifier` automatisch in das `defaultRoleUniversalIdentifier` des Anwendungsmanifests sodass Sie es nicht selbst aus [`defineApplication`](/l/de/developers/extend/apps/config/application) referenzieren müssen.
* **`*.role.ts`** deklariert, was die Rolle darf.
* **`application-config.ts`** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben.
Notizen:
* Genau **eine** `defineApplicationRole(...)` ist pro App zulässig der Manifest-Build schlägt fehl, wenn mehr als eine gefunden wird.
* Verwenden Sie `defineRole()` (nicht `defineApplicationRole()`) für alle **zusätzlichen** Rollen, die Ihre App mitliefert.
* Das explizite Setzen von `defaultRoleUniversalIdentifier` in `defineApplication()` wird für die Abwärtskompatibilität weiterhin unterstützt, ist aber zugunsten von `defineApplicationRole()` veraltet.
## Beste Praktiken
@@ -1,7 +1,7 @@
---
title: Objekte
description: Deklariere neue Record-Typen  benutzerdefinierte Tabellen mit eigenen Feldern  mit defineObject.
icon: tabelle
description: Deklarieren Sie neue Datensatztypen benutzerdefinierte Tabellen mit eigenen Feldern mit defineObject.
icon: table
---
Benutzerdefinierte **Objekte** sind neue Datensatztypen, die Ihre App zu einem Arbeitsbereich hinzufügt Postkarte, Rechnung, Abonnement, alles, was spezifisch für Ihre Domäne ist. Jedes Objekt deklariert sein Schema (Felder, Relationen, Standardwerte) und einen stabilen universellen Bezeichner, der über Synchronisierungen und Deployments hinweg bestehen bleibt.
@@ -1,6 +1,6 @@
---
title: Beziehungen
description: Objekte mit bidirektionalen MANY_TO_ONE- / ONE_TO_MANY-Relationen verbinden.
description: Objekte mit bidirektionalen MANY_TO_ONE-/ONE_TO_MANY-Relationen verbinden.
icon: diagram-project
---
@@ -1,6 +1,6 @@
---
title: Konzepte
description: Funktionsweise von Twenty-Apps Entity-Modell, Sandboxing und der Installations-Lebenszyklus.
description: Wie Twenty-Apps funktionieren Entitätenmodell, Sandboxing und Installationslebenszyklus.
icon: sitemap
---
@@ -34,22 +34,22 @@ your-app/
## Entitätstypen
| Entität | Zweck | Dokumentation |
| -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Anwendung** | App-Identität, Standardrolle, Variablen | [Application Config](/l/de/developers/extend/apps/config/application) |
| **Rolle** | Berechtigungssätze für Objekte und Felder | [Roles & Permissions](/l/de/developers/extend/apps/config/roles) |
| **Objekt** | Benutzerdefinierte Datensatztypen mit Feldern | [Objekte](/l/de/developers/extend/apps/data/objects) |
| **Feld** | Felder zu Objekten aus anderen Apps hinzufügen | [Extending Objects](/l/de/developers/extend/apps/data/extending-objects) |
| **Beziehung** | Bidirektionale Verknüpfungen zwischen Objekten | [Beziehungen](/l/de/developers/extend/apps/data/relations) |
| **Logikfunktion** | Serverseitiges TypeScript mit Triggern | [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) |
| **Skill** | Wiederverwendbare Anweisungen für KI-Agenten | [Skills & Agenten](/l/de/developers/extend/apps/logic/skills-and-agents) |
| **Agent** | KI-Assistenten mit benutzerdefinierten Prompts | [Skills & Agenten](/l/de/developers/extend/apps/logic/skills-and-agents) |
| **Verbindungsanbieter** | OAuth-Zugangsdaten für Drittanbieter-APIs | [Connections](/l/de/developers/extend/apps/logic/connections) |
| **Ansicht** | Vorkonfigurierte Listenansichten für Datensätze | [Ansichten](/l/de/developers/extend/apps/layout/views) |
| **Navigationsmenüeintrag** | Benutzerdefinierte Seitenleisten-Einträge | [Navigationsmenüeinträge](/l/de/developers/extend/apps/layout/navigation-menu-items) |
| **Seitenlayout** | Tabs und Widgets auf der Detailseite eines Datensatzes | [Seiten-Layouts](/l/de/developers/extend/apps/layout/page-layouts) |
| **Frontend-Komponente** | Gedisplayte React-UI in einer Sandbox innerhalb von Twenty | [Frontend-Komponenten](/l/de/developers/extend/apps/layout/front-components) |
| **Befehlsmenü-Eintrag** | Schnellaktionen und Cmd+K-Einträge | [Befehlsmenü-Einträge](/l/de/developers/extend/apps/layout/command-menu-items) |
| Entität | Zweck | Dokumentation |
| -------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------- |
| **Anwendung** | App-Identität, Standardrolle, Variablen | [Anwendungskonfiguration](/l/de/developers/extend/apps/config/application) |
| **Rolle** | Berechtigungssätze für Objekte und Felder | [Rollen & Berechtigungen](/l/de/developers/extend/apps/config/roles) |
| **Objekt** | Benutzerdefinierte Datensatztypen mit Feldern | [Objekte](/l/de/developers/extend/apps/data/objects) |
| **Feld** | Felder zu Objekten aus anderen Apps hinzufügen | [Objekte erweitern](/l/de/developers/extend/apps/data/extending-objects) |
| **Beziehung** | Bidirektionale Verknüpfungen zwischen Objekten | [Beziehungen](/l/de/developers/extend/apps/data/relations) |
| **Logikfunktion** | Serverseitiges TypeScript mit Triggern | [Logikfunktionen](/l/de/developers/extend/apps/logic/logic-functions) |
| **Skill** | Wiederverwendbare Anweisungen für KI-Agenten | [Skills & Agenten](/l/de/developers/extend/apps/logic/skills-and-agents) |
| **Agent** | KI-Assistenten mit benutzerdefinierten Prompts | [Skills & Agenten](/l/de/developers/extend/apps/logic/skills-and-agents) |
| **Verbindungsanbieter** | OAuth-Zugangsdaten für Drittanbieter-APIs | [Verbindungen](/l/de/developers/extend/apps/logic/connections) |
| **Ansicht** | Vorkonfigurierte Listenansichten für Datensätze | [Ansichten](/l/de/developers/extend/apps/layout/views) |
| **Navigationsmenüeintrag** | Benutzerdefinierte Seitenleisten-Einträge | [Navigationsmenüeinträge](/l/de/developers/extend/apps/layout/navigation-menu-items) |
| **Seitenlayout** | Tabs und Widgets auf der Detailseite eines Datensatzes | [Seiten-Layouts](/l/de/developers/extend/apps/layout/page-layouts) |
| **Frontend-Komponente** | Isolierte React-UI innerhalb von Twenty | [Frontend-Komponenten](/l/de/developers/extend/apps/layout/front-components) |
| **Befehlsmenü-Eintrag** | Schnellaktionen und Cmd+K-Einträge | [Befehlsmenü-Einträge](/l/de/developers/extend/apps/layout/command-menu-items) |
## Sandboxing
@@ -90,10 +90,10 @@ your-app/
Objekte, Felder und bidirektionale Relationen.
</Card>
<Card title="Logik" icon="bolt" href="/l/de/developers/extend/apps/logic/overview">
Logikfunktionen, Skills, Agents und OAuth-Verbindungen.
Logikfunktionen, Skills, Agenten und OAuth-Verbindungen.
</Card>
<Card title="Layout" icon="table-columns" href="/l/de/developers/extend/apps/layout/overview">
Ansichten, Navigation, Seiten-Layouts, Front-Komponenten.
Ansichten, Navigation, Seiten-Layouts, Frontend-Komponenten.
</Card>
<Card title="Operationen" icon="rocket" href="/l/de/developers/extend/apps/operations/overview">
CLI, Tests, Remotes, CI und das Veröffentlichen Ihrer App.
@@ -131,7 +131,7 @@ yarn twenty dev --once
Beide Modi benötigen einen Server im Entwicklungsmodus und eine authentifizierte Remote-Verbindung.
<Warning>
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Sync-Anfragen ab — verwenden Sie `yarn twenty deploy`, um auf Produktionsserver bereitzustellen. Siehe [Veröffentlichung](/l/de/developers/extend/apps/operations/publishing).
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Sync-Anfragen ab — verwenden Sie `yarn twenty deploy`, um auf Produktionsserver bereitzustellen. Siehe [Apps veröffentlichen](/l/de/developers/extend/apps/operations/publishing).
</Warning>
---
@@ -1,6 +1,6 @@
---
title: Gerüst erstellen
description: Generiere Entitätsdateien interaktiv mit yarn twenty add Objekte, Felder, Ansichten, Logikfunktionen und mehr.
title: Scaffolding
description: Generieren Sie Entitätsdateien interaktiv mit yarn twenty add Objekte, Felder, Ansichten, Logikfunktionen und mehr.
icon: wand-magic-sparkles
---
@@ -1,10 +1,10 @@
---
title: Befehlsmenü-Einträge
description: Stelle Front-Komponenten als Schnellaktionen und Einträge im Befehlsmenü (Cmd+K) mit defineCommandMenuItem bereit.
description: Stellen Sie Front-Komponenten als Schnellaktionen und Einträge im Befehlsmenü (Cmd+K) mit defineCommandMenuItem bereit.
icon: Terminal
---
Ein **Befehlsmenü-Eintrag** ist die Brücke zwischen dem Benutzer und einer [Front-Komponente](/l/de/developers/extend/apps/layout/front-components). Er registriert die Komponente im Twenty-Befehlsmenü (Cmd+K) und optional als angeheftete Schnellaktions-Schaltfläche in der oberen rechten Ecke der Seite.
Ein **Befehlsmenü-Eintrag** ist die Brücke zwischen dem Benutzer und einer [Front-Komponente](/l/de/developers/extend/apps/layout/front-components). Er registriert die Komponente im Twenty-Befehlsmenü (Cmd+K) und zeigt sie optional als angeheftete Schnellaktionsschaltfläche oben rechts auf der Seite an.
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -36,7 +36,7 @@ export default defineCommandMenuItem({
## Headless-Befehle
Ein Befehlsmenü-Eintrag, der mit einer [Headless-Front-Komponente](/l/de/developers/extend/apps/layout/front-components#headless-vs-non-headless) gekoppelt ist, ist die idiomatische Art, eine One-Click-Aktion bereitzustellen Code ausführen, navigieren oder bestätigen und ausführen. Die Seite „Front Components" behandelt die [SDK Command-Komponenten](/l/de/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), die das Action-and-Unmount-Muster handhaben.
Ein Befehlsmenü-Eintrag, der mit einer [Headless-Front-Komponente](/l/de/developers/extend/apps/layout/front-components#headless-vs-non-headless) gekoppelt ist, ist die idiomatische Art, eine One-Click-Aktion bereitzustellen Code ausführen, navigieren oder bestätigen und ausführen. Die Seite „Front Components behandelt die [SDK Command-Komponenten](/l/de/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), die das Action-and-Unmount-Muster handhaben.
Ein typischer Ablauf:
@@ -107,24 +107,24 @@ export default defineCommandMenuItem({
Diese repräsentieren den aktuellen Zustand der Seite:
| Variable | Typ | Beschreibung |
| ------------------------------ | -------------- | --------------------------------------------------------------- |
| `pageType` | `Zeichenkette` | Aktueller Seitentyp (z. B. 'RecordIndexPage', 'RecordShowPage') |
| `isInSidePanel` | `boolean` | Ob die Komponente in einem Seitenpanel gerendert wird |
| `numberOfSelectedRecords` | `number` | Anzahl der aktuell ausgewählten Datensätze |
| `isSelectAll` | `boolean` | Ob „Alle auswählen“ aktiv ist |
| `selectedRecords` | `array` | Die ausgewählten Datensatzobjekte |
| `favoriteRecordIds` | `array` | IDs der favorisierten Datensätze |
| `objectPermissions` | `object` | Berechtigungen für den aktuellen Objekttyp |
| `targetObjectReadPermissions` | `object` | Leseberechtigungen für das Zielobjekt |
| `targetObjectWritePermissions` | `object` | Schreibberechtigungen für das Zielobjekt |
| `featureFlags` | `object` | Aktive Feature-Flags |
| `objectMetadataItem` | `object` | Metadaten des aktuellen Objekttyps |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Ob die aktuelle Ansicht einen Soft-Delete-Filter hat |
| Variable | Typ | Beschreibung |
| ------------------------------ | --------- | --------------------------------------------------------------- |
| `pageType` | `string` | Aktueller Seitentyp (z. B. 'RecordIndexPage', 'RecordShowPage') |
| `isInSidePanel` | `boolean` | Ob die Komponente in einem Seitenpanel gerendert wird |
| `numberOfSelectedRecords` | `number` | Anzahl der aktuell ausgewählten Datensätze |
| `isSelectAll` | `boolean` | Ob „Alle auswählen“ aktiv ist |
| `selectedRecords` | `array` | Die ausgewählten Datensatzobjekte |
| `favoriteRecordIds` | `array` | IDs der favorisierten Datensätze |
| `objectPermissions` | `object` | Berechtigungen für den aktuellen Objekttyp |
| `targetObjectReadPermissions` | `object` | Leseberechtigungen für das Zielobjekt |
| `targetObjectWritePermissions` | `object` | Schreibberechtigungen für das Zielobjekt |
| `featureFlags` | `object` | Aktive Feature-Flags |
| `objectMetadataItem` | `object` | Metadaten des aktuellen Objekttyps |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Ob die aktuelle Ansicht einen Soft-Delete-Filter hat |
### Operatoren
Kombiniere Variablen zu booleschen Ausdrücken:
Kombinieren Sie Variablen zu booleschen Ausdrücken:
| Operator | Beschreibung |
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
@@ -234,9 +234,9 @@ Verfügbare Hooks:
| Hook | Gibt zurück | Beschreibung |
| --------------------------------------------- | -------------------- | --------------------------------------------------------------------------- |
| `useUserId()` | `string` oder `null` | Die ID des aktuellen Benutzers |
| `useSelectedRecordIds()` | `Zeichenkette[]` | Alle ausgewählten Datensatz-IDs (leeres Array, wenn keine ausgewählt sind) |
| `useSelectedRecordIds()` | `string[]` | Alle ausgewählten Datensatz-IDs (leeres Array, wenn keine ausgewählt sind) |
| `useRecordId()` | `string` oder `null` | **Veraltet.** Verwenden Sie stattdessen `useSelectedRecordIds()` |
| `useFrontComponentId()` | `Zeichenkette` | Die ID dieser Komponenteninstanz |
| `useFrontComponentId()` | `string` | Die ID dieser Komponenteninstanz |
| `useFrontComponentExecutionContext(selector)` | variiert | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion |
## Host-Kommunikations-API
@@ -1,6 +1,6 @@
---
title: Übersicht
description: Binden Sie Ihre App in das UI von Twenty ein Seitleisten-Einträge, gespeicherte Ansichten, Registerkarten auf Datensatzseiten und isolierte React-Komponenten.
description: Binden Sie Ihre App in das UI von Twenty ein Seitenleisten-Einträge, gespeicherte Ansichten, Registerkarten auf Datensatzseiten und isolierte React-Komponenten.
icon: table-columns
---
@@ -26,11 +26,11 @@ Die **Layout-Ebene** einer Twenty-App umfasst alles, was der Benutzer sieht: wo
## In diesem Abschnitt
<CardGroup cols={2}>
<Card title="Ansichten" icon="Liste" href="/l/de/developers/extend/apps/layout/views">
<Card title="Ansichten" icon="list" href="/l/de/developers/extend/apps/layout/views">
`defineView` — gespeicherte Listen-Konfigurationen: sichtbare Spalten, Filter, Gruppen.
</Card>
<Card title="Navigationselemente im Menü" icon="Balken" href="/l/de/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — Seitleisten-Einträge, die auf Ansichten oder externe URLs verweisen.
<Card title="Navigationsmenüeinträge" icon="bars" href="/l/de/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — Seitenleisten-Einträge, die auf Ansichten oder externe URLs verweisen.
</Card>
<Card title="Seitenlayouts" icon="table-columns" href="/l/de/developers/extend/apps/layout/page-layouts">
`definePageLayout` und `definePageLayoutTab` — Registerkarten und Widgets auf der Detailseite eines Datensatzes.
@@ -38,8 +38,8 @@ Die **Layout-Ebene** einer Twenty-App umfasst alles, was der Benutzer sieht: wo
<Card title="Frontend-Komponenten" icon="window-maximize" href="/l/de/developers/extend/apps/layout/front-components">
`defineFrontComponent` — isolierte React-Komponenten, die innerhalb von Twenty gerendert werden.
</Card>
<Card title="Befehlsmenüeinträge" icon="Terminal" href="/l/de/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — Front-Komponenten als Cmd+K-Einträge und Schnellaktionen registrieren.
<Card title="Befehlsmenü-Einträge" icon="terminal" href="/l/de/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — Frontend-Komponenten als Cmd+K-Einträge und Schnellaktionen registrieren.
</Card>
</CardGroup>
@@ -53,4 +53,4 @@ Die **Layout-Ebene** einer Twenty-App umfasst alles, was der Benutzer sieht: wo
| **Innerhalb eines der oben genannten Bereiche** | Ein benutzerdefiniertes React-Widget Schaltflächen, Formulare, Dashboards, Integrationen | `defineFrontComponent` |
| **Befehlsmenü (Cmd+K)** | Eine angeheftete Schnellaktion oder ein versteckter Befehl | `defineCommandMenuItem` |
Front-Komponenten laufen in einem isolierten Web Worker unter Verwendung von Remote DOM sie werden *nativ* auf der Seite gerendert (nicht in einem iframe), können aber die Hostseite oder das DOM nicht direkt erreichen. Die Kommunikation mit Twenty erfolgt über eine Message-Passing-Host-API.
Frontend-Komponenten laufen in einem isolierten Web Worker unter Verwendung von Remote DOM sie werden nativ auf der Seite gerendert (nicht in einem iframe), können aber die Hostseite oder das DOM nicht direkt erreichen. Die Kommunikation mit Twenty erfolgt über eine Message-Passing-Host-API.
@@ -40,4 +40,4 @@ export default defineView({
## Wie Ansichten in der UI angezeigt werden
Eine Ansicht für sich ist aus der Seitenleiste nicht erreichbar. Damit sie dort erscheint, verknüpfen Sie sie mit einem [Navigationsmenüeintrag](/l/de/developers/extend/apps/layout/navigation-menu-items) des Typs `VIEW`, der auf die `universalIdentifier` der Ansicht zeigt. Das ist das kanonische Muster: Jedes benutzerdefinierte Objekt liefert typischerweise eine Standardansicht plus einen Eintrag in der Seitenleiste, der sie öffnet.
Eine Ansicht für sich ist aus der Seitenleiste nicht erreichbar. Damit sie dort erscheint, verknüpfen Sie sie mit einem [Navigationsmenüeintrag](/l/de/developers/extend/apps/layout/navigation-menu-items) des Typs `VIEW`, der auf den `universalIdentifier` der Ansicht zeigt. Das ist das kanonische Muster: Jedes benutzerdefinierte Objekt liefert typischerweise eine Standardansicht plus einen Eintrag in der Seitenleiste, der sie öffnet.
@@ -52,7 +52,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
defaultRoleUniversalIdentifier: '...',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
@@ -137,15 +136,15 @@ export const createLinearIssueHandler = async (input: {
Jede Verbindung hat:
| Feld | Beschreibung |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Eindeutige Zeilen-ID; an `getConnection(id)` übergeben, um eine einzelne Verbindung erneut abzurufen |
| `sichtbarkeit` | `'user'` (privat für ein Mitglied des Arbeitsbereichs) oder `'workspace'` (mit allen Mitgliedern geteilt) |
| `geltungsbereiche` | Vom Upstream-Anbieter gewährte OAuth-Berechtigungen (unabhängig von `visibility` — diese sind nicht miteinander verknüpft) |
| `userWorkspaceId` | Die userWorkspace-ID des Eigentümers — nützlich, um "die Verbindung des anfragenden Benutzers" in HTTP-Routen-Triggern auszuwählen |
| `accessToken` | Frisches OAuth-Zugriffstoken (wird bei Ablauf automatisch erneuert) |
| `name` / `handle` | Anzeigename der Verbindung (automatisch beim OAuth-Callback abgeleitet, vom Benutzer umbenennbar) |
| `authFailedAt` | Gesetzt, wenn die jüngste Aktualisierung fehlgeschlagen ist; der Benutzer muss die Verbindung erneut herstellen |
| Feld | Beschreibung |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Eindeutige Zeilen-ID; an `getConnection(id)` übergeben, um eine einzelne Verbindung erneut abzurufen |
| `visibility` | `'user'` (privat für ein Mitglied des Arbeitsbereichs) oder `'workspace'` (mit allen Mitgliedern geteilt) |
| `scopes` | Vom Upstream-Anbieter gewährte OAuth-Berechtigungen (unabhängig von `visibility` — diese sind nicht miteinander verknüpft) |
| `userWorkspaceId` | Die userWorkspace-ID des Eigentümers — nützlich, um "die Verbindung des anfragenden Benutzers" in HTTP-Routen-Triggern auszuwählen |
| `accessToken` | Frisches OAuth-Zugriffstoken (wird bei Ablauf automatisch erneuert) |
| `name` / `handle` | Anzeigename der Verbindung (automatisch beim OAuth-Callback abgeleitet, vom Benutzer umbenennbar) |
| `authFailedAt` | Gesetzt, wenn die jüngste Aktualisierung fehlgeschlagen ist; der Benutzer muss die Verbindung erneut herstellen |
Hauptpunkte:
@@ -370,5 +370,5 @@ Hauptpunkte:
* `TWENTY_API_URL` — Basis-URL der Twenty-API
* `TWENTY_APP_ACCESS_TOKEN` — Kurzlebiger Schlüssel, der auf die Standard-Funktionsrolle Ihrer Anwendung begrenzt ist
Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in `defaultRoleUniversalIdentifier` in Ihrer `application-config.ts` verwiesen wird.
Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, die mit `defineApplicationRole()` deklariert wird (oder über `defaultRoleUniversalIdentifier` in `application-config.ts` referenziert wird).
</Note>
@@ -28,11 +28,11 @@ Die **Logikschicht** einer Twenty-App ist der Code, der *ausgeführt wird* s
<Card title="Logikfunktionen" icon="bolt" href="/l/de/developers/extend/apps/logic/logic-functions">
Der zentrale Baustein Auslösertypen, Payloads und der typisierte API-Client.
</Card>
<Card title="Fähigkeiten & Agenten" icon="robot" href="/l/de/developers/extend/apps/logic/skills-and-agents">
Wiederverwendbare KI-Agenten-Anweisungen und Assistenten mit benutzerdefinierten System-Prompts.
<Card title="Skills & Agenten" icon="robot" href="/l/de/developers/extend/apps/logic/skills-and-agents">
Wiederverwendbare Anweisungen für KI-Agenten und Assistenten mit benutzerdefinierten System-Prompts.
</Card>
<Card title="Verbindungen" icon="plug" href="/l/de/developers/extend/apps/logic/connections">
OAuth-Anmeldedaten, die Ihre App für Dienste von Drittanbietern hält Linear, GitHub, Slack und mehr.
OAuth-Anmeldedaten, die Ihre App für Dienste von Drittanbietern verwaltet Linear, GitHub, Slack und mehr.
</Card>
</CardGroup>
@@ -40,16 +40,16 @@ Die **Logikschicht** einer Twenty-App ist der Code, der *ausgeführt wird* s
Eine Logikfunktion wählt einen oder mehrere Auslöser jeder Eintrag unten ist ein eigenes Feld auf `defineLogicFunction()`:
| Auslöser | Wann sie ausgeführt wird | Einstellung |
| --------------------- | -------------------------------------------------------------------- | ------------------------------- |
| **HTTP-Route** | Eine Anfrage trifft auf Ihren `/s/\<path>`-Endpunkt | `httpRouteTriggerSettings` |
| **Cron** | Ein CRON-Ausdruck stimmt überein | `cronTriggerSettings` |
| **Datenbankereignis** | Ein Workspace-Datensatz wird erstellt, aktualisiert oder gelöscht | `databaseEventTriggerSettings` |
| **KI-Tool** | Eine Twenty-KI-Funktion entscheidet sich, Ihre Funktion aufzurufen | `toolTriggerSettings` |
| **Workflow-Aktion** | Ein Workflow-Schritt ruft Ihre Funktion auf | `workflowActionTriggerSettings` |
| Auslöser | Wann sie ausgeführt wird | Einstellung |
| --------------------- | ------------------------------------------------------------------ | ------------------------------- |
| **HTTP-Route** | Eine Anfrage erreicht Ihren `/s/\<path>`-Endpunkt | `httpRouteTriggerSettings` |
| **Cron** | Ein CRON-Ausdruck trifft zu | `cronTriggerSettings` |
| **Datenbankereignis** | Ein Workspace-Datensatz wird erstellt, aktualisiert oder gelöscht | `databaseEventTriggerSettings` |
| **KI-Tool** | Eine Twenty-KI-Funktion entscheidet sich, Ihre Funktion aufzurufen | `toolTriggerSettings` |
| **Workflow-Aktion** | Ein Workflow-Schritt ruft Ihre Funktion auf | `workflowActionTriggerSettings` |
Funktionen werden in isolierten Node.js-Prozessen sandboxed ausgeführt und greifen über einen typisierten API-Client, der auf die in [`defineApplication()`](/l/de/developers/extend/apps/config/application) deklarierte Rolle beschränkt ist, auf den Workspace zu.
Funktionen werden in isolierten Node.js-Prozessen in einer Sandbox ausgeführt und greifen über einen typisierten API-Client, der auf die in [`defineApplication()`](/l/de/developers/extend/apps/config/application) deklarierte Rolle beschränkt ist, auf den Workspace zu.
<Note>
**Installations-Hooks zur Installationszeit** Code, der vor oder nach der Installation ausgeführt wird teilen sich diese Laufzeitumgebung, verwenden jedoch ihre eigenen define-Funktionen und befinden sich unter [Config → Install Hooks](/l/de/developers/extend/apps/config/install-hooks).
**Installations-Hooks** Code, der vor oder nach der Installation ausgeführt wird teilen sich diese Laufzeitumgebung, verwenden jedoch eigene Define-Funktionen und befinden sich unter [Config → Install Hooks](/l/de/developers/extend/apps/config/install-hooks).
</Note>
@@ -1,11 +1,11 @@
---
title: Fähigkeiten & Agenten
title: Skills & Agenten
description: Definieren Sie KI-Skills und Agenten für Ihre App.
icon: robot
---
<Warning>
Fähigkeiten und Agenten befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
Skills und Agenten befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
</Warning>
Apps können KI-Funktionen definieren, die im Arbeitsbereich verfügbar sind — wiederverwendbare Skill-Anweisungen und Agenten mit benutzerdefinierten System-Prompts.
@@ -4,7 +4,7 @@ description: Erstellen, testen und ausliefern Sie Ihre App CLI-Befehle, Inte
icon: rocket
---
Die **Operationsschicht** umfasst alles, was Sie *mit* Ihrer App tun, anstatt *in* ihr: das Ausführen von CLI-Befehlen, das Starten von Integrationstests gegen einen realen Twenty-Server, das Konfigurieren von CI und das Ausliefern von Releases entweder als Tarball, der auf einem einzelnen Server bereitgestellt wird, oder als npm-Paket, das im Marketplace gelistet ist.
Die **Operationsschicht** umfasst alles, was Sie *an* Ihrer App tun, statt *mit* ihr: das Ausführen von CLI-Befehlen, das Durchführen von Integrationstests gegen einen realen Twenty-Server, das Konfigurieren von CI und das Ausliefern von Releases entweder als Tarball, der auf einem einzelnen Server bereitgestellt wird, oder als npm-Paket, das im Marketplace gelistet ist.
```text
develop ─▶ test ─▶ build ─▶ deploy / publish
@@ -17,13 +17,13 @@ Die **Operationsschicht** umfasst alles, was Sie *mit* Ihrer App tun, anstatt *i
## In diesem Abschnitt
<CardGroup cols={2}>
<Card title="CLI" icon="Terminal" href="/l/de/developers/extend/apps/operations/cli">
<Card title="CLI" icon="terminal" href="/l/de/developers/extend/apps/operations/cli">
`yarn twenty`-Referenz exec, logs, uninstall, remotes.
</Card>
<Card title="Tests" icon="flask" href="/l/de/developers/extend/apps/operations/testing">
Vitest-Setup, Integrationstests, Typprüfung, CI-Workflow.
</Card>
<Card title="Veröffentlichen" icon="hochladen" href="/l/de/developers/extend/apps/operations/publishing">
Build, Tarball bereitstellen, auf npm veröffentlichen, installieren.
<Card title="Veröffentlichen" icon="upload" href="/l/de/developers/extend/apps/operations/publishing">
Build erstellen, Tarball bereitstellen, auf npm veröffentlichen, installieren.
</Card>
</CardGroup>
@@ -187,7 +187,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
@@ -144,7 +144,7 @@ Der Subpfad `twenty-sdk/cli` exportiert Funktionen, die Sie direkt aus Testcode
| Funktion | Beschreibung |
| -------------- | ----------------------------------------------------- |
| `appBuild` | Die App bauen und optional ein Tarball packen |
| `appBuild` | Die App bauen und optional ein Tarball erstellen |
| `appDeploy` | Ein Tarball auf den Server hochladen |
| `appInstall` | Die App im aktiven Arbeitsbereich installieren |
| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren |
@@ -88,7 +88,7 @@ Ihr API-Schlüssel gewährt Zugriff auf sensible Daten. Teilen Sie ihn nicht mit
Für mehr Sicherheit weisen Sie eine spezifische Rolle zu, um den Zugriff zu beschränken:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **API-Schlüssel** auf **+ API-Schlüssel zuweisen** klicken
@@ -9,7 +9,7 @@ KI-Agenten respektieren Ihre bestehende Berechtigungsstruktur. Dies ist besonder
## Weisen Sie einem KI-Agenten eine Rolle zu
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **KI-Agenten** klicken Sie auf **+ KI-Agent zuweisen**
@@ -17,7 +17,7 @@ description: Häufig gestellte Fragen zu KI-Funktionen in Twenty.
</Accordion>
<Accordion title="Haben KI-Agenten Zugriff auf alle meine Daten?">
KI-Agenten werden über das Berechtigungssystem verwaltet. Sie können KI-Agenten unter **Einstellungen → Rollen** bestimmte Rollen zuweisen und haben damit volle Kontrolle darüber, auf welche Daten sie zugreifen können und welche Aktionen sie ausführen dürfen.
KI-Agenten werden über das Berechtigungssystem verwaltet. Sie können KI-Agenten unter **Einstellungen → Mitglieder → Rollen** bestimmte Rollen zuweisen und haben damit volle Kontrolle darüber, auf welche Daten sie zugreifen können und welche Aktionen sie ausführen dürfen.
</Accordion>
<Accordion title="Wie funktionieren KI-Credits?">
@@ -48,7 +48,7 @@ Erweitern Sie Ihre Workflows mit KI-gestützten Aktionen und autonomen Agenten.
KI-Agenten werden über das bestehende Berechtigungssystem verwaltet:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Konfigurieren Sie, auf welche Daten jeder KI-Agent zugreifen kann
3. Legen Sie Lese-/Schreibberechtigungen pro Objekt fest
@@ -214,7 +214,7 @@ Schließen Sie nach dem Import der Daten die Konfiguration Ihres Arbeitsbereichs
### Rollen und Berechtigungen konfigurieren
* Richten Sie Rollen unter **Einstellungen → Rollen** ein
* Richten Sie Rollen unter **Einstellungen → Mitglieder → Rollen** ein
* Weisen Sie Benutzer den entsprechenden Rollen zu
### E-Mail und Kalender verbinden
@@ -127,7 +127,7 @@ Erstellen Sie nach dem Import der Daten manuell neu:
### Rollen und Berechtigungen
* Konfigurieren Sie Rollen in **Einstellungen → Rollen**
* Richten Sie Rollen unter **Einstellungen → Mitglieder → Rollen** ein
* Weisen Sie Benutzer den entsprechenden Rollen zu
### Integrationen
@@ -13,7 +13,7 @@ Das Berechtigungssystem von Twenty ermöglicht es Ihnen, den Zugriff auf drei Ha
Um eine neue Rolle zu erstellen:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Unter **Alle Rollen** klicken Sie auf **+ Rolle erstellen**
3. Geben Sie einen Rollennamen ein
4. Im Standard-Tab **Berechtigungen** [Berechtigungen konfigurieren](#customize-permissions)
@@ -23,7 +23,7 @@ Um eine neue Rolle zu erstellen:
Um eine Rolle zu löschen:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie entfernen möchten
3. Öffnen Sie den Tab **Einstellungen** und klicken Sie auf **Rolle löschen**
4. Klicken Sie im Modal auf **Bestätigen**
@@ -36,13 +36,13 @@ Wenn eine Rolle gelöscht wird, wird jedes ihr zugewiesene Mitglied des Arbeitsb
### Aktuelle Zuweisungen anzeigen
* Gehen Sie zu **Einstellungen → Rollen**
* Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
* Sehen Sie alle Rollen und wie viele Mitglieder jeweils zugewiesen sind
* Anzeigen, welche Mitglieder welche Rollen haben
### Weisen Sie einem Mitglied eine Rolle zu
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Klicken Sie auf **+ Mitglied zuweisen**
@@ -51,7 +51,7 @@ Wenn eine Rolle gelöscht wird, wird jedes ihr zugewiesene Mitglied des Arbeitsb
### Standardrolle festlegen
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Suchen Sie im Abschnitt **Optionen** nach **Standardrolle**
3. Wählen Sie aus, welche Rolle neue Mitglieder automatisch erhalten sollen
4. Neue Arbeitsbereichsmitglieder werden beim Beitritt dieser Rolle zugewiesen
@@ -168,7 +168,7 @@ Neben Mitgliedern des Arbeitsbereichs können Rollen auch **API-Schlüsseln** un
### Einem API-Schlüssel eine Rolle zuweisen
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **API-Schlüssel** auf **+ API-Schlüssel zuweisen** klicken
@@ -183,7 +183,7 @@ API-Schlüssel ohne zugewiesene Rolle verwenden Standardberechtigungen. Weisen S
### Einem KI-Agenten eine Rolle zuweisen
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **KI-Agenten** auf **+ KI-Agenten zuweisen** klicken
@@ -19,7 +19,7 @@ Jedes Mitglied des Arbeitsbereichs, dem diese Rolle zugewiesen ist, wird automat
</Accordion>
<Accordion title="Wie lege ich eine Standardrolle für neue Mitglieder fest?">
Gehen Sie zu **Einstellungen → Rollen**, suchen Sie die Option **Standardrolle** und wählen Sie aus, welche Rolle neue Mitglieder beim Beitritt automatisch erhalten sollen.
Gehen Sie zu **Einstellungen → Mitglieder → Rollen**, suchen Sie die Option **Standardrolle** und wählen Sie aus, welche Rolle neue Mitglieder beim Beitritt automatisch erhalten sollen.
</Accordion>
<Accordion title="Kann ich einem Benutzer mehrere Rollen zuweisen?">
@@ -64,7 +64,7 @@ Berechtigungen auf Zeilenebene werden bis Q1 2026 im Tarif **Organization** verf
</Accordion>
<Accordion title="Wie mache ich ein Feld für bestimmte Benutzer schreibgeschützt?">
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Wählen Sie die Rolle aus
3. Navigieren Sie zu dem Objekt, das das Feld enthält
4. Setzen Sie die Feldberechtigung auf **Feld anzeigen** (ohne Feld bearbeiten)
@@ -3,10 +3,12 @@ title: Domäneneinstellungen
description: Konfigurieren Sie die Arbeitsbereichsdomäne, genehmigte Zugriffsdomänen und öffentliche Domänen.
---
Konfigurieren Sie die Domäneneinstellungen unter **Einstellungen → Domänen**.
Domain-Einstellungen befinden sich an drei Stellen, abhängig davon, was Sie konfigurieren.
## Arbeitsbereichsdomäne
Konfigurieren Sie dies unter **Einstellungen → Allgemein → Arbeitsbereichs-Domain**.
Bearbeiten Sie den Namen Ihrer Subdomäne oder legen Sie eine benutzerdefinierte Domäne für Ihren Arbeitsbereich fest.
### Domäne anpassen
@@ -19,6 +21,8 @@ Für benutzerdefinierte Domänen müssen Sie die DNS-Einstellungen bei Ihrem Dom
## Genehmigte Domänen
Konfigurieren Sie dies unter **Einstellungen → Mitglieder → Zugriff**.
Jede Person mit einer E-Mail-Adresse in diesen Domänen darf sich automatisch für diesen Arbeitsbereich registrieren.
### Genehmigte Zugriffsdomäne hinzufügen
@@ -35,13 +39,16 @@ Dies ist nützlich, um Ihrem gesamten Team die Selbstregistrierung zu ermöglich
## Öffentliche Domains
Stellen Sie eine vollständige und sichere Hosting-Umgebung auf diesen Domains bereit.
Konfigurieren Sie dies unter **Einstellungen → Apps → Entwickler**.
Stellen Sie eine vollständige und sichere Hosting-Umgebung auf diesen Domains bereit. Eine öffentliche Domain kann an eine bestimmte App gebunden werden wenn sie gebunden ist, sind unter dieser Domain nur die HTTP-gerouteten Logikfunktionen dieser App erreichbar. Lassen Sie die Bindung leer, um alle HTTP-Routen des Arbeitsbereichs bereitzustellen.
### Öffentliche Domäne hinzufügen
1. Klicken Sie auf **Öffentliche Domäne hinzufügen**
2. Geben Sie die Domäne ein, die Sie verwenden möchten
3. Konfigurieren Sie die DNS-Einstellungen gemäß Anleitung
4. Überprüfen Sie die Domäne
3. Optional an eine App binden
4. Konfigurieren Sie die DNS-Einstellungen gemäß Anleitung
5. Überprüfen Sie die Domäne
SSL-Zertifikate werden für öffentliche Domänen automatisch bereitgestellt.
@@ -77,7 +77,7 @@ Verwalten Sie Einladungen, die noch nicht angenommen wurden:
Erlauben Sie Teammitgliedern, basierend auf ihrer E-Mail-Domäne automatisch beizutreten:
1. Gehen Sie zu **Einstellungen → Domänen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Zugriff**
2. Fügen Sie die Domäne Ihres Unternehmens hinzu (z. B. `yourcompany.com`)
3. Jede Person mit dieser E-Mail-Domäne kann ohne Einladung beitreten
@@ -137,7 +137,7 @@ Ja, Sie können mehrere E-Mail-Konten verbinden. Gehen Sie zu **Einstellungen
<AccordionGroup>
<Accordion title="Kann ich meine Arbeitsbereichsdomäne anpassen?">
Ja! Gehen Sie zu **Einstellungen → Domänen** und klicken Sie auf **Domäne anpassen**. Sie haben zwei Optionen:
Ja! Gehen Sie zu **Einstellungen → Allgemein → Workspace-Domäne** und klicken Sie auf **Domäne anpassen**. Sie haben zwei Optionen:
* **Subdomain**: Verwenden Sie eine Twenty-Subdomain wie `yourcompany.twenty.com`
* **Benutzerdefinierte Domäne**: Verwenden Sie Ihre eigene Domäne wie `crm.yourcompany.com` (erfordert DNS-Konfiguration)
@@ -146,7 +146,7 @@ Eine Subdomain ist schnell eingerichtet, während eine benutzerdefinierte Domän
</Accordion>
<Accordion title="Wie funktionieren genehmigte Zugriffsdomänen?">
Sie können genehmigte Zugriffsdomänen konfigurieren, damit Teammitglieder mit Firmen-E-Mail-Adressen Ihrem Arbeitsbereich automatisch beitreten können. Gehen Sie zu **Einstellungen → Domänen** und fügen Sie Ihre Firmendomäne hinzu (z. B. `yourcompany.com`).
Sie können genehmigte Zugriffsdomänen konfigurieren, damit Teammitglieder mit Firmen-E-Mail-Adressen Ihrem Arbeitsbereich automatisch beitreten können. Gehen Sie zu **Einstellungen → Mitglieder → Zugriff** und fügen Sie Ihre Firmendomäne hinzu (z. B. `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -44,7 +44,7 @@ Fügen Sie Ihrem Arbeitsbereich Teammitglieder hinzu:
4. Weisen Sie geeignete Rollen zu
<Note>
Bevor Sie Ihr Team einladen, überprüfen Sie die Standardrolle unter **Einstellungen → Rollen**. Neuen Mitgliedern wird diese Rolle beim Beitritt automatisch zugewiesen.
Bevor Sie Ihr Team einladen, überprüfen Sie die Standardrolle unter **Einstellungen → Mitglieder → Rollen**. Neuen Mitgliedern wird diese Rolle beim Beitritt automatisch zugewiesen.
</Note>
## Checkliste für Arbeitsbereichseinstellungen
@@ -38,7 +38,7 @@ Sie benötigen zwei benutzerdefinierte Felder am Objekt „Opportunities“.
Wenn Benutzer diese berechneten Felder nicht manuell bearbeiten sollen:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Wählen Sie die zu konfigurierende Rolle aus
3. Suchen Sie das Objekt „Opportunities“
4. Setzen Sie die Felder **Probability** und **Expected Amount** auf schreibgeschützt
@@ -61,7 +61,7 @@ Sie benötigen keine "Days in"-Felder für Closed Won und Closed Lost, da dies f
Wenn Benutzer diese berechneten Felder nicht manuell bearbeiten sollen:
1. Gehen Sie zu **Einstellungen → Rollen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
2. Rolle zum Konfigurieren auswählen
3. Suchen Sie das Objekt Opportunities
4. Setzen Sie die Felder "Last Entered" und "Days in" auf schreibgeschützt
@@ -307,5 +307,5 @@ AI Agent actions consume workflow credits based on the AI model used. See [Workf
</Note>
<Note>
AI agents respect role-based permissions. You can assign specific roles to agents under **Settings → Roles** to control what data they can access. See [Permissions](/l/de/user-guide/permissions-access/capabilities/permissions) for details.
AI agents respect role-based permissions. Sie können Agenten unter **Einstellungen → Mitglieder → Rollen** bestimmte Rollen zuweisen, um zu steuern, auf welche Daten sie zugreifen können. See [Permissions](/l/de/user-guide/permissions-access/capabilities/permissions) for details.
</Note>
@@ -8,7 +8,7 @@ description: Häufig gestellte Fragen zu Workflows in Twenty.
<Accordion title="Warum kann ich einen Workflow nicht aktivieren?">
Dies ist wahrscheinlich ein Berechtigungsproblem. Sie benötigen Zugriff auf Workflows, um sie zu erstellen und zu aktivieren.
**Lösung**: Wenden Sie sich an Ihren Workspace-Administrator, damit er Ihnen unter **Einstellungen → Rollen** Zugriff auf Workflows gewährt.
**Lösung**: Wenden Sie sich an Ihren Workspace-Administrator, damit er Ihnen unter **Einstellungen → Mitglieder → Rollen** Zugriff auf Workflows gewährt.
Wenn Sie den Bereich Workflows in Ihrer Seitenleiste überhaupt nicht sehen, bestätigt dies ein Berechtigungsproblem.
</Accordion>
@@ -37,7 +37,7 @@ Ambas estão disponíveis como REST e GraphQL. GraphQL adiciona upserts em lote
Authorization: Bearer YOUR_API_KEY
```
Crie uma chave de API em **Settings → API & Webhooks → + Create key**. Copie-a imediatamente — ela é exibida apenas uma vez. As chaves podem ter escopo para uma função específica em **Settings → Roles → Assignment tab** para limitar o que podem acessar.
Crie uma chave de API em **Settings → API & Webhooks → + Create key**. Copie-a imediatamente — ela é exibida apenas uma vez. As chaves podem ser limitadas a uma função específica em **Settings → Members → Roles → Assignment tab** para restringir o que podem acessar.
<VimeoEmbed videoId="928786722" title="Criando chave de API" />
@@ -13,7 +13,6 @@ Todo app deve ter exatamente uma chamada a `defineApplication`. Ela declara:
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
@@ -27,7 +26,6 @@ export default defineApplication({
isSecret: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
@@ -35,12 +33,13 @@ Notas:
* Os campos `universalIdentifier` são IDs determinísticos que você controla. Gere-os uma vez e mantenha-os estáveis entre sincronizações.
* `applicationVariables` tornam-se variáveis de ambiente para suas funções e componentes de front-end (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` deve fazer referência a um papel definido com [`defineRole()`](/l/pt/developers/extend/apps/config/roles).
* O papel padrão é detectado automaticamente a partir do arquivo de definição de papel marcado com [`defineApplicationRole()`](/l/pt/developers/extend/apps/config/roles) — você não precisa referenciá-lo em `defineApplication()`.
* As funções de pré-instalação e pós-instalação são detectadas automaticamente durante a construção do manifesto — você não precisa referenciá-las em `defineApplication()`.
* Passar `defaultRoleUniversalIdentifier` explicitamente ainda é compatível para retrocompatibilidade, mas foi preterido em favor de `defineApplicationRole()`.
## Papel de função padrão
O `defaultRoleUniversalIdentifier` controla ao que as funções de lógica e os componentes de front-end do app podem acessar:
O papel declarado com [`defineApplicationRole()`](/l/pt/developers/extend/apps/config/roles) controla o que as funções de lógica e os componentes de front-end do aplicativo podem acessar:
* O token em tempo de execução injetado como `TWENTY_APP_ACCESS_TOKEN` é derivado desse papel.
* O cliente de API tipado é restrito às permissões concedidas a esse papel.
@@ -6,7 +6,7 @@ icon: wrench
Hooks de instalação são funções de lógica especiais que são executadas durante o ciclo de vida de instalação ou atualização. Elas compartilham o mesmo runtime de handler que as [logic functions](/l/pt/developers/extend/apps/logic/logic-functions) normais e recebem um `InstallPayload`, mas são declaradas com suas próprias funções de definição — `definePostInstallLogicFunction()` e `definePreInstallLogicFunction()` — e ficam fora do modelo de gatilhos normal (HTTP, cron, eventos de banco de dados).
Cada aplicativo pode definir **no máximo uma pré-instalação** e **no máximo uma pós-instalação**. A geração do manifesto apresentará erro se mais de uma de cada for detectada.
Cada aplicativo pode definir no máximo uma função de pré-instalação e no máximo uma função de pós-instalação. A geração do manifesto apresentará erro se mais de uma de cada for detectada.
```
┌─────────────────────────────────────────────────────────────┐
@@ -26,7 +26,7 @@ A **camada de configuração** de uma aplicação Twenty é o que descreve a apl
└──────────────────────────────────┘
```
## Nesta seção
## Nesta secção
<CardGroup cols={2}>
<Card title="Configuração da aplicação" icon="rocket" href="/l/pt/developers/extend/apps/config/application">
@@ -36,15 +36,15 @@ A **camada de configuração** de uma aplicação Twenty é o que descreve a apl
`defineRole` — declara o que as funções de lógica da sua aplicação podem ler e escrever.
</Card>
<Card title="Hooks de instalação" icon="wrench" href="/l/pt/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction` e `definePostInstallLogicFunction` — fazem cópias de segurança dos dados, pré-preenchem valores padrão, validam atualizações.
`definePreInstallLogicFunction` e `definePostInstallLogicFunction` — criam cópias de segurança dos dados, inicializam valores predefinidos, validam atualizações.
</Card>
</CardGroup>
## Como as peças se relacionam
* A **Aplicação** é o ponto de entrada. Cada aplicação tem exatamente uma chamada `defineApplication()`, e esta aponta para uma **Função** como predefinida.
* A **Função** controla o que as funções de lógica e os componentes de interface da aplicação podem ler e escrever. Siga o princípio do menor privilégio: conceda apenas as permissões de que o seu código realmente necessita.
* Os **Hooks de instalação** são executados durante a instalação ou atualização — o pré-instalação antes da migração de metadados (para que possa recusar uma atualização arriscada) e o pós-instalação depois da migração (para que possa pré-preencher dados padrão com base no novo esquema).
* A **Função** controla o que as funções de lógica e os componentes de front-end da aplicação podem ler e escrever. Siga o princípio do menor privilégio: conceda apenas as permissões de que o seu código realmente necessita.
* Os **Hooks de instalação** são executados durante a instalação ou atualização — a pré-instalação antes da migração de metadados (para que possa recusar uma atualização arriscada) e a pós-instalação após a migração (para que possa inicializar dados predefinidos com base no novo esquema).
<Note>
Os hooks de instalação partilham o ambiente de execução de [função de lógica](/l/pt/developers/extend/apps/logic/logic-functions) — a mesma assinatura de handler, as mesmas variáveis de ambiente, o mesmo cliente de API tipado — mas são declarados com as suas próprias funções "define" e vivem fora do modelo de disparo normal (HTTP, cron, eventos de base de dados).
@@ -4,7 +4,7 @@ description: Declare quais objetos e campos as funções de lógica e os compone
icon: shield-halved
---
Um **papel** é um conjunto de permissões: quais objetos um app pode ler ou gravar, quais campos ele pode ver e quais recursos em nível de plataforma ele pode usar. Todas as funções de lógica e os componentes de front-end do app herdam as permissões do papel declarado como `defaultRoleUniversalIdentifier` em [`defineApplication`](/l/pt/developers/extend/apps/config/application).
Um **papel** é um conjunto de permissões: quais objetos um app pode ler ou gravar, quais campos ele pode ver e quais recursos em nível de plataforma ele pode usar. Todas as funções de lógica e os componentes de front-end de cada app herdam as permissões do papel marcado com `defineApplicationRole()` (consulte [O papel padrão da função](#the-default-function-role) abaixo).
```ts src/roles/restricted-company-role.ts
import {
@@ -51,15 +51,15 @@ export default defineRole({
## Papel de função padrão
Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel padrão:
Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel padrão declarado com `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
@@ -77,10 +77,13 @@ export default defineRole({
});
```
O `universalIdentifier` desse papel é referenciado em `application-config.ts` como `defaultRoleUniversalIdentifier`:
`defineApplicationRole()` é um wrapper leve em torno de `defineRole()` que sinaliza **o** papel usado como padrão do seu app no momento da instalação. A validação é idêntica à de `defineRole`, mas o pipeline de build conecta automaticamente seu `universalIdentifier` ao `defaultRoleUniversalIdentifier` do manifesto do app — assim, você não precisa referenciá-lo em [`defineApplication`](/l/pt/developers/extend/apps/config/application).
* **`*.role.ts`** declara o que o papel pode fazer.
* **`application-config.ts`** aponta para esse papel para que suas funções herdem suas permissões.
Notas:
* Exatamente **um** `defineApplicationRole(...)` é permitido por app — o build do manifesto falhará se encontrar mais de um.
* Use `defineRole()` (não `defineApplicationRole()`) para quaisquer papéis **adicionais** que o seu app forneça.
* Definir `defaultRoleUniversalIdentifier` explicitamente em `defineApplication()` ainda é compatível para retrocompatibilidade, mas foi preterido em favor de `defineApplicationRole()`.
## Melhores Práticas
@@ -39,11 +39,11 @@ A **camada de dados** de um app Twenty é o conjunto de dados que seu app *adici
## Entidades em resumo
| Entidade | Finalidade | Definido com |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| **Objeto** | Um novo tipo de registro personalizado (por exemplo, PostCard, Invoice) com seus próprios campos | `defineObject()` |
| **Campo** | Uma coluna em um objeto. Campos independentes podem estender objetos que você não criou (por exemplo, adicionar `loyaltyTier` a Company) | `defineField()` |
| **Relação** | Um vínculo bidirecional entre dois objetos — ambos os lados declarados como campos | `defineField()` com `FieldType.RELATION` |
| Entidade | Finalidade | Definido com |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| **Objeto** | Um novo tipo de registro personalizado (por exemplo, PostCard, Invoice) com seus próprios campos | `defineObject()` |
| **Campo** | Uma coluna em um objeto. Campos independentes podem estender objetos que você não criou (por exemplo, adicionar `loyaltyTier` ao objeto Company) | `defineField()` |
| **Relação** | Um vínculo bidirecional entre dois objetos — ambos os lados declarados como campos | `defineField()` com `FieldType.RELATION` |
O SDK detecta esses elementos por meio de análise de AST em tempo de build, então a organização dos arquivos fica a seu critério — a convenção é `src/objects/` e `src/fields/`. UUIDs `universalIdentifier` estáveis conectam tudo em implantações diferentes.
@@ -1,6 +1,6 @@
---
title: Conceitos
description: Como os apps Twenty funcionam — modelo de entidade, sandboxing e ciclo de vida da instalação.
description: Como as aplicações Twenty funcionam — modelo de entidade, sandboxing e ciclo de vida da instalação.
icon: sitemap
---
@@ -34,22 +34,22 @@ your-app/
## Tipos de entidade
| Entidade | Finalidade | Documentação |
| ----------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------- |
| **Aplicação** | Identidade da aplicação, função padrão, variáveis | [Application Config](/l/pt/developers/extend/apps/config/application) |
| **Papel** | Conjuntos de permissões para objetos e campos | [Roles & Permissions](/l/pt/developers/extend/apps/config/roles) |
| **Objeto** | Tipos de registro personalizados com campos | [Objects](/l/pt/developers/extend/apps/data/objects) |
| **Campo** | Adicionar campos a objetos de outros apps | [Extending Objects](/l/pt/developers/extend/apps/data/extending-objects) |
| **Relação** | Links bidirecionais entre objetos | [Relations](/l/pt/developers/extend/apps/data/relations) |
| **Função lógica** | TypeScript no lado do servidor com gatilhos | [Funções lógicas](/l/pt/developers/extend/apps/logic/logic-functions) |
| **Habilidade** | Instruções reutilizáveis para agentes de IA | [Habilidades e Agentes](/l/pt/developers/extend/apps/logic/skills-and-agents) |
| **Agente** | Assistentes de IA com prompts personalizados | [Habilidades e Agentes](/l/pt/developers/extend/apps/logic/skills-and-agents) |
| **Provedor de conexão** | Credenciais OAuth para APIs de terceiros | [Connections](/l/pt/developers/extend/apps/logic/connections) |
| **Vista** | Vistas de lista de registros pré-configuradas | [Views](/l/pt/developers/extend/apps/layout/views) |
| **Item do menu de navegação** | Entradas personalizadas na barra lateral | [Navigation Menu Items](/l/pt/developers/extend/apps/layout/navigation-menu-items) |
| **Layout da Página** | Abas e widgets na página de detalhes de um registro | [Page Layouts](/l/pt/developers/extend/apps/layout/page-layouts) |
| **Componente de front-end** | UI React em sandbox dentro do Twenty | [Componentes de front-end](/l/pt/developers/extend/apps/layout/front-components) |
| **Item do menu de comandos** | Ações rápidas e entradas Cmd+K | [Command Menu Items](/l/pt/developers/extend/apps/layout/command-menu-items) |
| Entidade | Finalidade | Documentação |
| ----------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------- |
| **Aplicação** | Identidade da aplicação, função padrão, variáveis | [Configuração da aplicação](/l/pt/developers/extend/apps/config/application) |
| **Papel** | Conjuntos de permissões para objetos e campos | [Papéis e permissões](/l/pt/developers/extend/apps/config/roles) |
| **Objeto** | Tipos de registro personalizados com campos | [Objetos](/l/pt/developers/extend/apps/data/objects) |
| **Campo** | Adicionar campos a objetos de outros apps | [Extensão de objetos](/l/pt/developers/extend/apps/data/extending-objects) |
| **Relação** | Links bidirecionais entre objetos | [Relações](/l/pt/developers/extend/apps/data/relations) |
| **Função lógica** | TypeScript no lado do servidor com gatilhos | [Funções lógicas](/l/pt/developers/extend/apps/logic/logic-functions) |
| **Habilidade** | Instruções reutilizáveis para agentes de IA | [Habilidades e Agentes](/l/pt/developers/extend/apps/logic/skills-and-agents) |
| **Agente** | Assistentes de IA com prompts personalizados | [Habilidades e Agentes](/l/pt/developers/extend/apps/logic/skills-and-agents) |
| **Provedor de conexão** | Credenciais OAuth para APIs de terceiros | [Conexões](/l/pt/developers/extend/apps/logic/connections) |
| **Vista** | Vistas de lista de registros pré-configuradas | [Vistas](/l/pt/developers/extend/apps/layout/views) |
| **Item do menu de navegação** | Entradas personalizadas na barra lateral | [Itens do menu de navegação](/l/pt/developers/extend/apps/layout/navigation-menu-items) |
| **Layout da Página** | Abas e widgets na página de detalhes de um registro | [Layouts de página](/l/pt/developers/extend/apps/layout/page-layouts) |
| **Componente de front-end** | UI React em sandbox dentro do Twenty | [Componentes de front-end](/l/pt/developers/extend/apps/layout/front-components) |
| **Item do menu de comandos** | Ações rápidas e entradas Cmd+K | [Itens do menu de comandos](/l/pt/developers/extend/apps/layout/command-menu-items) |
## Sandboxing
@@ -78,7 +78,7 @@ your-app/
* **`yarn twenty dev`** — observa seus arquivos-fonte e sincroniza ao vivo as alterações com um servidor Twenty conectado. O cliente de API tipado é regenerado automaticamente quando o esquema muda.
* **`yarn twenty build`** — compila TypeScript, empacota funções de lógica e componentes de front-end com o esbuild e produz um manifesto.
* **Hooks de pré/pós-instalação** — funções opcionais que são executadas durante a instalação. Veja [Install Hooks](/l/pt/developers/extend/apps/config/install-hooks) para detalhes.
* **Hooks de pré/pós-instalação** — funções opcionais que são executadas durante a instalação. Veja [Hooks de instalação](/l/pt/developers/extend/apps/config/install-hooks) para detalhes.
## Próximos passos
@@ -86,16 +86,16 @@ your-app/
<Card title="Configuração" icon="screwdriver-wrench" href="/l/pt/developers/extend/apps/config/overview">
Identidade da aplicação, função padrão e hooks de instalação.
</Card>
<Card title="Data" icon="database" href="/l/pt/developers/extend/apps/data/overview">
<Card title="Dados" icon="database" href="/l/pt/developers/extend/apps/data/overview">
Objetos, campos e relações bidirecionais.
</Card>
<Card title="Lógica" icon="bolt" href="/l/pt/developers/extend/apps/logic/overview">
Funções de lógica, skills, agentes e conexões OAuth.
Funções lógicas, habilidades, agentes e conexões OAuth.
</Card>
<Card title="Layout" icon="table-columns" href="/l/pt/developers/extend/apps/layout/overview">
Views, navegação, layouts de página, componentes de front.
Vistas, navegação, layouts de página, componentes de front-end.
</Card>
<Card title="Operações" icon="rocket" href="/l/pt/developers/extend/apps/operations/overview">
CLI, testes, remotes, CI e publicação do seu app.
CLI, testes, remotos, CI e publicação do seu aplicativo.
</Card>
</CardGroup>
@@ -1,6 +1,6 @@
---
title: Servidor Local
description: Gerencie o servidor Twenty Docker local — inicie, pare, atualize, instância de teste em paralelo e configuração manual do SDK.
description: Gerencie o servidor Docker local do Twenty — iniciar, parar, atualizar, executar uma instância de teste paralela e configurar manualmente o SDK.
icon: server
---
@@ -11,13 +11,13 @@ Use `yarn twenty server` para controlar o contêiner Twenty local:
| Comando | O que faz |
| -------------------------------------- | ------------------------------------------------ |
| `yarn twenty server start` | Inicia o servidor (baixa a imagem se necessário) |
| `yarn twenty server start --port 3030` | Iniciar em uma porta personalizada |
| `yarn twenty server start --port 3030` | Inicia em uma porta personalizada |
| `yarn twenty server stop` | Interrompe o servidor (preserva os dados) |
| `yarn twenty server status` | Mostra a URL, a versão e as credenciais de login |
| `yarn twenty server logs` | Transmite os logs do servidor |
| `yarn twenty server reset` | Apaga os dados e começa do zero |
| `yarn twenty server upgrade` | Baixa a imagem mais recente `twenty-app-dev` |
| `yarn twenty server upgrade 2.2.0` | Atualizar para uma versão específica |
| `yarn twenty server upgrade 2.2.0` | Atualiza para uma versão específica |
Os dados são persistidos entre reinicializações em dois volumes do Docker (`twenty-app-dev-data` para PostgreSQL, `twenty-app-dev-storage` para arquivos). Use `reset` para apagar tudo.
@@ -39,17 +39,17 @@ Passe `--test` para qualquer comando de `server` para gerenciar uma segunda inst
| Comando | O que faz |
| ----------------------------------- | ------------------------------------------------ |
| `yarn twenty server start --test` | Inicia a instância de teste (padrão: porta 2021) |
| `yarn twenty server stop --test` | Parar |
| `yarn twenty server status --test` | Mostrar seu status |
| `yarn twenty server logs --test` | Transmitir seus logs |
| `yarn twenty server reset --test` | Apagar seus dados |
| `yarn twenty server upgrade --test` | Atualizar sua imagem |
| `yarn twenty server stop --test` | Para |
| `yarn twenty server status --test` | Mostra seu status |
| `yarn twenty server logs --test` | Transmite seus logs |
| `yarn twenty server reset --test` | Apaga seus dados |
| `yarn twenty server upgrade --test` | Atualiza sua imagem |
A instância de teste tem seu próprio contêiner (`twenty-app-dev-test`), volumes (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`) e configuração — ela é executada junto com sua instância principal sem conflitos. Combine `--test` com `--port` para substituir 2021.
## Configuração manual (sem o gerador)
Ignore a ferramenta de scaffolding se você estiver adicionando o SDK a um projeto existente:
Ignore o gerador se você estiver adicionando o SDK a um projeto existente:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
@@ -173,12 +173,12 @@ Referência completa: [Conceitos](/l/pt/developers/extend/apps/getting-started/c
Objetos, campos e relações bidirecionais.
</Card>
<Card title="Lógica" icon="bolt" href="/l/pt/developers/extend/apps/logic/overview">
Funções de lógica, skills, agents e conexões OAuth.
Funções lógicas, habilidades, agentes e conexões OAuth.
</Card>
<Card title="Layout" icon="table-columns" href="/l/pt/developers/extend/apps/layout/overview">
Views, navegação, layouts de página, front components.
Exibições, navegação, layouts de página, componentes de front-end.
</Card>
<Card title="Operações" icon="rocket" href="/l/pt/developers/extend/apps/operations/overview">
CLI, testes, remotes, CI e publicação do seu aplicativo.
CLI, testes, remotos, CI e publicação do seu aplicativo.
</Card>
</CardGroup>
@@ -1,6 +1,6 @@
---
title: Itens do menu de comandos
description: Apresente front components como ações rápidas e entradas do menu de comandos (Cmd+K) com `defineCommandMenuItem`.
description: Exponha componentes de front-end como ações rápidas e entradas do menu de comandos (Cmd+K) com `defineCommandMenuItem`.
icon: terminal
---
@@ -36,7 +36,7 @@ export default defineCommandMenuItem({
## Comandos sem interface
Um item de menu de comando emparelhado com um [headless front component](/l/pt/developers/extend/apps/layout/front-components#headless-vs-non-headless) é a forma idiomática de disponibilizar uma ação de um clique — executar código, navegar ou confirmar e executar. A página de Front Components aborda os [SDK Command components](/l/pt/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) que lidam com o padrão de ação e desmontagem.
Um item do menu de comandos emparelhado com um [componente de front-end sem interface](/l/pt/developers/extend/apps/layout/front-components#headless-vs-non-headless) é a forma idiomática de disponibilizar uma ação de um clique — executar código, navegar ou confirmar e executar. A página de Front Components aborda os [SDK Command components](/l/pt/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) que lidam com o padrão de ação e desmontagem.
Um fluxo típico:
@@ -11,16 +11,16 @@ Componentes de front-end são componentes React que renderizam diretamente dentr
Os componentes de front-end podem ser renderizados em dois locais dentro do Twenty:
* **Painel lateral** — Componentes de front-end não headless abrem no painel lateral direito. Este é o comportamento padrão quando um componente de front-end é acionado pelo menu de comandos.
* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets dentro de [page layouts](/l/pt/developers/extend/apps/layout/page-layouts). Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end.
* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets dentro de [layouts de página](/l/pt/developers/extend/apps/layout/page-layouts). Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end.
Um front component por si só não é acessível pela interface — é preciso *exibi-lo*. As duas maneiras de fazer isso são:
Um componente de front-end por si só não é acessível pela UI — é preciso *exibi-lo*. As duas maneiras de fazer isso são:
* **Associe-o a um [command menu item](/l/pt/developers/extend/apps/layout/command-menu-items)** — registra-o no menu de comandos (Cmd+K) e, opcionalmente, como uma ação rápida fixada.
* **Incorpore-o como um widget em um [page layout](/l/pt/developers/extend/apps/layout/page-layouts)** — posiciona-o na página de detalhes de um registro ou em um painel.
* **Associe-o a um [item do menu de comandos](/l/pt/developers/extend/apps/layout/command-menu-items)** — registra-o no menu de comandos (Cmd+K) e, opcionalmente, como uma ação rápida fixada.
* **Incorpore-o como um widget em um [layout de página](/l/pt/developers/extend/apps/layout/page-layouts)** — posiciona-o na página de detalhes de um registro ou em um painel.
## Exemplo básico
A maneira mais rápida de ver um front component em ação é associá-lo a um [`defineCommandMenuItem`](/l/pt/developers/extend/apps/layout/command-menu-items), para que ele apareça como um botão de ação rápida no canto superior direito da página:
A maneira mais rápida de ver um componente de front-end em ação é associá-lo a um [`defineCommandMenuItem`](/l/pt/developers/extend/apps/layout/command-menu-items), para que ele apareça como um botão de ação rápida no canto superior direito da página:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
@@ -76,7 +76,7 @@ Clique nele para renderizar o componente inline.
## Colocando um componente de front-end em uma página
Além de comandos, você pode incorporar um componente de front-end diretamente em uma página de registro adicionando-o como um widget em um **layout de página**. Veja [Page Layouts](/l/pt/developers/extend/apps/layout/page-layouts) para mais detalhes.
Além de comandos, você pode incorporar um componente de front-end diretamente em uma página de registro adicionando-o como um widget em um **layout de página**. Veja [Layouts de página](/l/pt/developers/extend/apps/layout/page-layouts) para detalhes.
## Headless vs não headless
@@ -26,7 +26,7 @@ A **camada de layout** de um app do Twenty é tudo o que o usuário vê: onde o
## Nesta seção
<CardGroup cols={2}>
<Card title="Visualizações" icon="lista" href="/l/pt/developers/extend/apps/layout/views">
<Card title="Visualizações" icon="list" href="/l/pt/developers/extend/apps/layout/views">
`defineView` — configurações salvas de lista: colunas visíveis, filtros, grupos.
</Card>
<Card title="Itens do menu de navegação" icon="bars" href="/l/pt/developers/extend/apps/layout/navigation-menu-items">
@@ -39,18 +39,18 @@ A **camada de layout** de um app do Twenty é tudo o que o usuário vê: onde o
`defineFrontComponent` — componentes React em sandbox que são renderizados dentro do Twenty.
</Card>
<Card title="Itens do menu de comandos" icon="terminal" href="/l/pt/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — registra front components como entradas Cmd+K e ações rápidas.
`defineCommandMenuItem` — registra componentes de front-end como entradas no Cmd+K e ações rápidas.
</Card>
</CardGroup>
## Onde o app aparece
| Superfície | O que controla | Entidade |
| ------------------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Barra lateral** | Uma entrada personalizada que aponta para uma visualização salva ou URL externa | `defineNavigationMenuItem` |
| **Lista de registros** | Uma configuração salva para uma lista de um objeto — colunas visíveis, ordem, filtros, grupos | `defineView` |
| **Página de detalhes do registro** | As abas e widgets em uma página de registro (do seu próprio objeto ou de um padrão) | `definePageLayout`, `definePageLayoutTab` |
| **Dentro de qualquer uma das opções acima** | Um widget React personalizado — botões, formulários, dashboards, integrações | `defineFrontComponent` |
| **Menu de comandos (Cmd+K)** | Uma ação rápida fixada ou comando oculto | `defineCommandMenuItem` |
| Superfície | O que controla | Entidade |
| ------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------- |
| **Barra lateral** | Uma entrada personalizada que aponta para uma visualização salva ou URL externa | `defineNavigationMenuItem` |
| **Lista de registros** | Uma configuração salva para um objeto — colunas visíveis, ordem, filtros, grupos | `defineView` |
| **Página de detalhes do registro** | As abas e widgets em uma página de registro (do seu próprio objeto ou de um objeto padrão) | `definePageLayout`, `definePageLayoutTab` |
| **Dentro de qualquer uma das opções acima** | Um widget React personalizado — botões, formulários, dashboards, integrações | `defineFrontComponent` |
| **Menu de comandos (Cmd+K)** | Uma ação rápida fixada ou comando oculto | `defineCommandMenuItem` |
Os front components são executados dentro de um Web Worker isolado usando Remote DOM — eles são renderizados *nativamente* na página (não dentro de um iframe), mas não podem acessar diretamente a página host ou o DOM. A comunicação com o Twenty acontece por meio de uma API de host com passagem de mensagens.
Os componentes de front-end são executados dentro de um Web Worker isolado usando Remote DOM — eles são renderizados *nativamente* na página (não dentro de um iframe), mas não podem acessar diretamente a página host ou o DOM. A comunicação com o Twenty acontece por meio de uma API de host com passagem de mensagens.
@@ -4,7 +4,7 @@ description: Personalize páginas de detalhes de registros — abas, widgets e o
icon: table-columns
---
Um **page layout** controla como a página de detalhes de um registro é organizada: quais abas aparecem e quais widgets elas contêm. Use `definePageLayout()` para declarar um layout para um objeto que você possui ou `definePageLayoutTab()` para adicionar uma única aba a um layout que já existe (seu ou um padrão da Twenty).
Um **layout de página** controla como a página de detalhes de um registro é organizada: quais abas aparecem e quais widgets elas contêm. Use `definePageLayout()` para declarar um layout para um objeto que você possui ou `definePageLayoutTab()` para adicionar uma única aba a um layout que já existe (seu ou um padrão da Twenty).
| Caso de uso | Entidade |
| ------------------------------------------------------------------------------ | --------------------- |
@@ -96,7 +96,7 @@ export default definePageLayoutTab({
### Pontos-chave
* `pageLayoutUniversalIdentifier` é **obrigatório** e deve apontar para um page layout que já exista no momento da instalação — seja um layout padrão da Twenty ou um definido pelo seu próprio aplicativo. Referências entre apps para layouts pertencentes a outro aplicativo instalado não são compatíveis atualmente. Quando o layout pai estiver ausente, a instalação falha com um erro de validação claro.
* `widgets` têm escopo apenas para esta aba — eles referenciam [front components](/l/pt/developers/extend/apps/layout/front-components), views etc., exatamente como widgets definidos inline em `definePageLayout`.
* `pageLayoutUniversalIdentifier` é **obrigatório** e deve apontar para um layout de página que já exista no momento da instalação — seja um layout padrão da Twenty ou um definido pelo seu próprio aplicativo. Referências entre aplicativos para layouts pertencentes a outro aplicativo instalado não são compatíveis atualmente. Quando o layout pai estiver ausente, a instalação falha com um erro de validação claro.
* `widgets` têm escopo apenas para esta aba — eles referenciam [front components](/l/pt/developers/extend/apps/layout/front-components), visualizações etc., exatamente como widgets definidos inline em `definePageLayout`.
* `position` controla a ordenação em relação às abas existentes no layout de destino. Escolha um valor que posicione sua aba onde você deseja em relação às abas nativas.
* Use isto em vez de `definePageLayout` quando você quiser apenas adicionar a um layout existente. Use `definePageLayout` quando você possuir todo o layout.
@@ -1,10 +1,10 @@
---
title: Visualizações
description: Envie visualizações salvas pré-configuradas — ordem das colunas, filtros, grupos — para objetos no seu app.
description: Distribua visualizações salvas e pré-configuradas — ordem das colunas, filtros, grupos — para objetos no seu app.
icon: list
---
Uma **visualização** é uma configuração salva de como os registros de um objeto são exibidos: quais campos aparecem, sua ordem, se estão visíveis e quaisquer filtros ou grupos aplicados. Use `defineView()` para enviar visualizações pré-configuradas com o seu app — normalmente uma visualização de índice padrão para cada objeto personalizado que você cria.
Uma **visualização** é uma configuração salva de como os registros de um objeto são exibidos: quais campos aparecem, sua ordem, se estão visíveis e quaisquer filtros ou grupos aplicados. Use `defineView()` para distribuir visualizações pré-configuradas com seu app — normalmente uma visualização de índice padrão para cada objeto personalizado que você cria.
```ts src/views/example-view.ts
import { defineView, ViewKey } from 'twenty-sdk/define';
@@ -40,4 +40,4 @@ export default defineView({
## Como as visualizações aparecem na UI
Uma visualização por si só não é acessível a partir da barra lateral. Para fazê-la aparecer lá, associe-a a um [item de menu de navegação](/l/pt/developers/extend/apps/layout/navigation-menu-items) do tipo `VIEW` que aponte para o `universalIdentifier` da visualização. Esse é o padrão canônico: cada objeto personalizado normalmente envia uma visualização padrão + uma entrada na barra lateral que a abre.
Uma visualização por si só não é acessível a partir da barra lateral. Para fazê-la aparecer lá, associe-a a um [item de menu de navegação](/l/pt/developers/extend/apps/layout/navigation-menu-items) do tipo `VIEW` que aponte para o `universalIdentifier` da visualização. Esse é o padrão canônico: cada objeto personalizado normalmente inclui uma visualização padrão + uma entrada na barra lateral que a abre.
@@ -52,7 +52,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
defaultRoleUniversalIdentifier: '...',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
@@ -370,5 +370,5 @@ Pontos-chave:
* `TWENTY_API_URL` — URL base da API do Twenty
* `TWENTY_APP_ACCESS_TOKEN` — Chave de curta duração com escopo para o papel de função padrão do seu aplicativo
Você **não** precisa passá-las para os clientes — eles leem de `process.env` automaticamente. As permissões da chave de API são determinadas pelo papel referenciado em `defaultRoleUniversalIdentifier` no seu `application-config.ts`.
Você **não** precisa passá-las para os clientes — eles leem de `process.env` automaticamente. As permissões da chave de API são determinadas pelo papel declarado com `defineApplicationRole()` (ou referenciado via `defaultRoleUniversalIdentifier` em `application-config.ts`).
</Note>
@@ -4,7 +4,7 @@ description: TypeScript do lado do servidor que é executado dentro do Twenty
icon: bolt
---
A **camada de lógica** de um app Twenty é o código que *é executado* — manipuladores TypeScript do lado do servidor reagindo a solicitações HTTP, agendamentos cron e alterações de registros; habilidades e agentes de IA que vivem dentro do workspace; e conexões OAuth que permitem que suas funções ajam em nome de um usuário em serviços de terceiros.
A **camada de lógica** de um app do Twenty é o código que *é executado* — manipuladores TypeScript do lado do servidor que reagem a solicitações HTTP, agendamentos CRON e alterações de registros; habilidades e agentes de IA que vivem dentro do workspace; e conexões OAuth que permitem que suas funções ajam em nome de um usuário em serviços de terceiros.
```text
┌─ HTTP route ──┐
@@ -48,7 +48,7 @@ Uma função de lógica escolhe um ou mais gatilhos — cada entrada abaixo é u
| **Ferramenta de IA** | Um recurso de IA do Twenty decide chamar sua função | `toolTriggerSettings` |
| **Ação de fluxo de trabalho** | Uma etapa de fluxo de trabalho invoca sua função | `workflowActionTriggerSettings` |
As funções são executadas em sandbox em processos Node.js isolados e acessam o workspace por meio de um cliente de API tipado com escopo para a função declarada em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
As funções são executadas em sandbox, em processos Node.js isolados, e acessam o workspace por meio de um cliente de API tipado, com escopo definido pelo papel declarado em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
<Note>
**Ganchos de instalação** — código que é executado antes ou depois da instalação — compartilham esse runtime, mas usam suas próprias funções define e ficam em [Config → Install Hooks](/l/pt/developers/extend/apps/config/install-hooks).
@@ -14,11 +14,11 @@ A **camada de operações** é tudo o que você faz *para* o seu app em vez de *
dev build yarn twenty publish (npm → marketplace)
```
## Nesta seção
## Nesta secção
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/pt/developers/extend/apps/operations/cli">
`yarn twenty` reference — exec, logs, uninstall, remotes.
Referência do `yarn twenty` — exec, logs, uninstall, remotes.
</Card>
<Card title="Testes" icon="flask" href="/l/pt/developers/extend/apps/operations/testing">
Configuração do Vitest, testes de integração, verificação de tipos, fluxo de trabalho de CI.
@@ -50,7 +50,7 @@ yarn twenty deploy
### Compartilhando um aplicativo implantado
<Warning>
Compartilhar aplicativos privados (tarball) entre espaços de trabalho é um recurso do plano **Enterprise**. A guia **Distribution** exibirá um aviso de atualização em vez dos controles de compartilhamento até que seu espaço de trabalho tenha uma chave Enterprise válida. Vá para [Configurações > Painel de Administração > Enterprise](/settings/admin-panel#enterprise) para ativá-lo.
Compartilhar aplicativos privados (tarball) entre espaços de trabalho é um recurso do plano **Enterprise**. A guia **Distribuição** exibirá um aviso de atualização em vez dos controles de compartilhamento até que seu espaço de trabalho tenha uma chave Enterprise válida. Vá para [Configurações > Painel de Administração > Enterprise](/settings/admin-panel#enterprise) para ativá-lo.
</Warning>
Aplicativos em tarball não são listados no marketplace público, então outros espaços de trabalho no mesmo servidor não os descobrirão ao navegar. Assim que o seu espaço de trabalho estiver no plano Enterprise, você pode compartilhar um app implantado desta forma:
@@ -107,7 +107,7 @@ O valor é um [intervalo semver](https://github.com/npm/node-semver#ranges) padr
* Se o servidor não tiver `APP_VERSION` configurado, a verificação será ignorada.
<Note>
O servidor realiza a verificação definitiva — ele valida `engines.twenty` tanto no upload do tarball quanto na instalação no workspace. Se você implantar um tarball fora de banda ou instalar a partir do marketplace, o servidor ainda impõe a compatibilidade.
O servidor realiza a verificação definitiva — ele valida `engines.twenty` tanto no upload do tarball quanto na instalação no espaço de trabalho. Se você implantar um tarball fora de banda ou instalar a partir do marketplace, o servidor ainda impõe a compatibilidade.
</Note>
## CI/CD automatizado (fluxos de trabalho pré-configurados)
@@ -140,7 +140,7 @@ Faz o deploy do seu app para um servidor Twenty configurado a cada push para `ma
1. Faz checkout do head do PR (para PRs rotulados) ou do commit enviado.
2. Executa `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — o equivalente em CI de `yarn twenty deploy`.
3. Executa `twentyhq/twenty/.github/actions/install-twenty-app@main` para que a versão recém-implantada seja instalada no workspace de destino.
3. Executa `twentyhq/twenty/.github/actions/install-twenty-app@main` para que a versão recém-implantada seja instalada no espaço de trabalho de destino.
**Configuração obrigatória:**
@@ -187,7 +187,6 @@ export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
@@ -88,7 +88,7 @@ Sua chave de API concede acesso a dados confidenciais. Não a compartilhe com se
Para maior segurança, atribua uma função específica para limitar o acesso:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja atribuir
3. Abra a aba de **Atribuição**
4. Em **Chaves de API**, clique em **+ Atribuir à chave de API**
+1 -1
View File
@@ -164,7 +164,7 @@
"label": "Configuração"
},
"appsData": {
"label": "Data"
"label": "Dados"
},
"appsLogic": {
"label": "Lógica"
@@ -9,7 +9,7 @@ Os agentes de IA respeitam sua estrutura de permissões existente. Isso é parti
## Atribuir uma Função a um Agente de IA
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja atribuir
3. Abra a aba de **Atribuição**
4. Em **Agentes de IA**, clique em **+ Atribuir ao agente de IA**
@@ -17,7 +17,7 @@ description: Perguntas frequentes sobre recursos de IA na Twenty.
</Accordion>
<Accordion title="Os agentes de IA terão acesso a todos os meus dados?">
Os agentes de IA funcionarão de acordo com o sistema de permissões. Você pode atribuir funções específicas aos agentes de IA em **Configurações → Funções**, dando a você controle total sobre quais dados eles podem acessar e quais ações podem executar.
Os agentes de IA funcionarão de acordo com o sistema de permissões. Você pode atribuir funções específicas aos agentes de IA em **Configurações → Membros → Funções**, dando a você controle total sobre quais dados eles podem acessar e quais ações podem executar.
</Accordion>
<Accordion title="Como os créditos de IA vão funcionar?">
@@ -48,7 +48,7 @@ Amplie seus fluxos de trabalho com ações com IA e agentes autônomos.
Os agentes de IA serão gerenciados pelo sistema de permissões existente:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Configure quais dados cada agente de IA pode acessar
3. Defina permissões de leitura/gravação por objeto
@@ -214,7 +214,7 @@ Após importar os dados, complete a configuração do seu espaço de trabalho:
### Configure funções e permissões
* Configure as funções em **Definições → Funções**
* Configure as funções em **Definições → Membros → Funções**
* Atribua os utilizadores às funções apropriadas
### Ligue o e-mail e o calendário
@@ -127,7 +127,7 @@ Depois de importar os dados, recrie manualmente:
### Funções e Permissões
* Configure as funções em **Configurações → Funções**
* Configure as funções em **Definições → Membros → Funções**
* Atribua os utilizadores às funções apropriadas
### Integrações
@@ -13,7 +13,7 @@ O sistema de permissões do Twenty permite que você controle o acesso a três
Para criar uma nova função:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Em **Todas as Funções**, clique em **+ Criar Função**
3. Digite um nome para a função
4. Na aba padrão **Permissões**, [configure as permissões](#customize-permissions)
@@ -23,7 +23,7 @@ Para criar uma nova função:
Para excluir uma função:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja remover
3. Abra a aba de **Configurações** e clique em **Excluir Função**
4. Clique em **Confirmar** no modal
@@ -36,13 +36,13 @@ Se uma função for excluída, qualquer membro do espaço de trabalho atribuído
### Visualizar Atribuições Atuais
* Vá para **Configurações → Funções**
* Vá para **Configurações → Membros → Funções**
* Veja todas as funções e quantos membros estão atribuídos a cada uma
* Veja quais membros têm quais funções
### Atribuir uma Função a um Membro
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja atribuir
3. Abra a aba de **Atribuição**
4. Clique em **+ Atribuir a membro**
@@ -51,7 +51,7 @@ Se uma função for excluída, qualquer membro do espaço de trabalho atribuído
### Definir Função Padrão
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Na seção **Opções**, encontre **Função Padrão**
3. Selecione qual função novos membros devem receber automaticamente
4. Novos membros do espaço de trabalho serão atribuídos a essa função ao ingressar
@@ -168,7 +168,7 @@ Além dos membros do espaço de trabalho, funções também podem ser atribuída
### Atribuir uma função a uma chave de API
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja atribuir
3. Abra a aba de **Atribuição**
4. Em **Chaves de API**, clique em **+ Atribuir à chave de API**
@@ -183,7 +183,7 @@ Chaves de API sem uma função atribuída usam permissões padrão. Para maior s
### Atribuir uma função a um agente de IA
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Clique na função que deseja atribuir
3. Abra a aba de **Atribuição**
4. Em **Agentes de IA**, clique em **+ Atribuir ao agente de IA**
@@ -19,7 +19,7 @@ Qualquer membro do espaço de trabalho atribuído a essa função será automati
</Accordion>
<Accordion title="Como defino uma função padrão para novos membros?">
Vá para **Configurações → Funções**, encontre a opção **Função Padrão** e selecione qual função os novos membros devem receber automaticamente ao entrar.
Vá para **Configurações → Membros → Funções**, encontre a opção **Função Padrão** e selecione qual função os novos membros devem receber automaticamente ao entrar.
</Accordion>
<Accordion title="Posso atribuir várias funções a um único usuário?">
@@ -64,7 +64,7 @@ As permissões em nível de linha estarão disponíveis no plano **Organization*
</Accordion>
<Accordion title="Como deixar um campo somente leitura para determinados usuários?">
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Selecione a função
3. Navegue até o objeto que contém o campo
4. Defina a permissão do campo como **Ver campo** (sem Editar campo)
@@ -3,10 +3,12 @@ title: Configurações de Domínio
description: Configure o domínio do espaço de trabalho, os domínios de acesso aprovados e os domínios públicos.
---
Configure as configurações de domínio em **Configurações → Domínios**.
As configurações de domínio ficam em três lugares, dependendo do que você está configurando.
## Domínio do espaço de trabalho
Configure em **Configurações → Geral → Domínio do workspace**.
Edite o nome do seu subdomínio ou defina um domínio personalizado para o seu espaço de trabalho.
### Personalizar domínio
@@ -19,6 +21,8 @@ Para domínios personalizados, você precisará configurar as configurações de
## Domínios Aprovados
Configure em **Configurações → Membros → Acesso**.
Qualquer pessoa com um endereço de e-mail nesses domínios pode inscrever-se neste espaço de trabalho automaticamente.
### Adicionar Domínio de Acesso Aprovado
@@ -35,13 +39,16 @@ Isso é útil para permitir que toda a sua equipe se registre por conta própria
## Domínios Públicos
Provisionar um ambiente de hospedagem completo e seguro nestes domínios.
Configure em **Configurações → Aplicativos → Desenvolvedor**.
Provisionar um ambiente de hospedagem completo e seguro nestes domínios. Um domínio público pode ser vinculado a um app específico — quando vinculado, somente as funções de lógica roteadas por HTTP desse app ficam acessíveis no domínio. Deixe a vinculação vazia para expor todas as rotas HTTP do workspace.
### Adicionar Domínio Público
1. Clique em **Adicionar Domínio Público**
2. Insira o domínio que você deseja usar
3. Configure as configurações de DNS conforme as instruções
4. Verifique o domínio
3. Opcionalmente vincule-o a um app
4. Configure as configurações de DNS conforme as instruções
5. Verifique o domínio
Os certificados SSL são provisionados automaticamente para domínios públicos.
@@ -77,7 +77,7 @@ Gerencie convites que ainda não foram aceitos:
Permita que membros da equipe ingressem automaticamente com base no domínio de e-mail:
1. Vá para **Configurações → Domínios**
1. Vá para **Configurações → Membros → Acesso**
2. Adicione o domínio da sua empresa (por exemplo, `yourcompany.com`)
3. Qualquer pessoa com esse domínio de e-mail pode ingressar sem convite
@@ -137,7 +137,7 @@ Sim, você pode conectar várias contas de e-mail. Vá para **Configurações
<AccordionGroup>
<Accordion title="Posso personalizar o domínio do meu espaço de trabalho?">
Sim! Vá para **Configurações → Domínios** e clique em **Personalizar domínio**. Você tem duas opções:
Sim! Vá para **Configurações → Geral → Domínio do espaço de trabalho** e clique em **Personalizar domínio**. Você tem duas opções:
* **Subdomínio**: Use um subdomínio do Twenty como `yourcompany.twenty.com`
* **Domínio personalizado**: Use seu próprio domínio, como `crm.yourcompany.com` (requer configuração de DNS)
@@ -146,7 +146,7 @@ Um subdomínio é rápido de configurar, enquanto um domínio personalizado ofer
</Accordion>
<Accordion title="Como funcionam os domínios de acesso aprovados?">
Você pode configurar domínios de acesso aprovados para que membros da equipe com endereços de e-mail da empresa possam ingressar automaticamente no seu espaço de trabalho. Vá para **Configurações → Domínios** e adicione o domínio da sua empresa (por exemplo, `yourcompany.com`).
Você pode configurar domínios de acesso aprovados para que membros da equipe com endereços de e-mail da empresa possam ingressar automaticamente no seu espaço de trabalho. Vá para **Configurações → Membros → Acesso** e adicione o domínio da sua empresa (por exemplo, `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -44,7 +44,7 @@ Adicione membros da equipa ao seu espaço de trabalho:
4. Atribua funções apropriadas
<Note>
Antes de convidar a sua equipa, verifique a função padrão em **Configurações → Funções**. Novos membros recebem automaticamente essa função ao entrar.
Antes de convidar a sua equipa, verifique a função padrão em **Configurações → Membros → Funções**. Novos membros recebem automaticamente essa função ao entrar.
</Note>
## Lista de Verificação das Configurações do Espaço de Trabalho
@@ -38,7 +38,7 @@ Você precisa de dois campos personalizados no objeto Oportunidades.
Se você não quiser que os usuários editem manualmente esses campos calculados:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Selecione a função a configurar
3. Encontre o objeto Oportunidades
4. Defina os campos **Probability** e **Expected Amount** como somente leitura
@@ -61,7 +61,7 @@ Você não precisa de campos "Dias em" para Fechado Ganho e Fechado Perdido, poi
Se você não quiser que os usuários editem manualmente esses campos calculados:
1. Vá para **Configurações → Funções**
1. Vá para **Configurações → Membros → Funções**
2. Selecione a função a configurar
3. Encontre o objeto Oportunidades
4. Defina os campos "Última entrada" e "Dias em" como somente leitura
@@ -307,5 +307,5 @@ As ações do Agente de IA consomem créditos de fluxo de trabalho com base no m
</Note>
<Note>
Os agentes de IA respeitam permissões baseadas em funções. Você pode atribuir funções específicas aos agentes em **Configurações → Funções** para controlar a quais dados eles podem acessar. Veja [Permissões](/l/pt/user-guide/permissions-access/capabilities/permissions) para obter detalhes.
Os agentes de IA respeitam permissões baseadas em funções. Você pode atribuir funções específicas aos agentes em **Configurações → Membros → Funções** para controlar a quais dados eles podem acessar. Veja [Permissões](/l/pt/user-guide/permissions-access/capabilities/permissions) para obter detalhes.
</Note>

Some files were not shown because too many files have changed in this diff Show More