Compare commits

..
Author SHA1 Message Date
neo773 f5717752be wip 2026-02-12 02:38:33 +05:30
neo773 24796e9355 refactor caldav 2026-02-12 01:42:59 +05:30
15fc850212 Remove redundant self-build from Nx targets that compile on the fly (#17851)
## Summary

- Changed 6 Nx targets (`start`, `start:debug`, `typeorm`, `ts-node`,
`database:migrate`, `database:migrate:revert`) from `dependsOn:
["build"]` to `dependsOn: ["^build"]` to eliminate a redundant full SWC
compilation step (~30-60s per invocation).
- These targets all use `nest start --watch`, `ts-node`, or TypeORM's
CLI (which runs under ts-node), so they already compile TypeScript
themselves. The `build` dependency was causing `rimraf dist && nest
build` to run first, only for the target's own command to recompile
everything again.
- Fixed `start:debug` to call `nest start --watch --debug` directly
instead of routing through `nx start --debug`, which would trigger yet
another build cycle.

Note: targets that run pre-compiled code from `dist/` (like
`database:reset`) intentionally keep `dependsOn: ["build"]`.

## Test plan

- [ ] Run `npx nx start twenty-server` and verify the server starts with
only one SWC compilation pass instead of two
- [ ] Run `npx nx start:debug twenty-server` and verify the debugger
attaches correctly
- [ ] Run `npx nx run twenty-server:database:migrate` and verify
migrations run correctly
- [ ] Run `npx nx run twenty-server:database:reset twenty-server` and
verify it still builds before running (unchanged)


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-11 17:38:22 +00:00
e6d5df751b i18n - translations (#17874)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-11 18:25:44 +01:00
5b4fed1afe Fix redirect to deleted workspace subdomain after workspace deletion (#17865)
## Summary
- After deleting a workspace, the app was incorrectly redirecting to the
deleted workspace's subdomain (e.g. `myworkspace.ourapp.com/sign-in-up`)
because `signOut()` only performs a client-side React Router navigation
which stays on the current domain.
- Added an explicit `redirectToDefaultDomain()` call after sign out in
the workspace deletion flow, which does a hard browser redirect to the
base domain (e.g. `app.ourapp.com`).

## Test plan
- [ ] Delete a workspace in a multi-workspace environment
- [ ] Verify the browser redirects to the base domain (`app.ourapp.com`)
instead of staying on the deleted workspace's subdomain
- [ ] Verify normal sign-out (without workspace deletion) still works as
expected

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-11 18:25:27 +01:00
e757a1418d Fix hardcoded colors in 2FA verification screen for dark mode (#17868)
## Summary
- The dash separator and blinking caret in the sign-in 2FA OTP input had
hardcoded `black` and `white` background colors, making them invisible
in dark mode (black on black / white on white).
- Replaced with theme-aware colors (`theme.font.color.light` for the
dash, `theme.font.color.primary` for the caret) to match the existing
settings 2FA component.

## Test plan
- [ ] Open the 2FA verification screen in dark mode and verify the dash
between digit groups is visible
- [ ] Verify the blinking caret is visible in dark mode
- [ ] Confirm both still look correct in light mode

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-11 18:23:42 +01:00
EtienneandGitHub 2f298307f9 Handle 413 with user friendly message (#17870)
Add friendly message for 413 errors. Currently, 413 errors originate
from the nginx server.
2026-02-11 17:03:22 +00:00
WeikoandGitHub c15536f9f8 Fix page layout seeding for record page layouts (#17871)
## Context
Fix broken record page layout seeding. This was not detected by the CI
because it doesn't have the env variable yet.

Following the same mechanism as labelIdentifier in object for circular
dependency resolution
2026-02-11 16:56:21 +00:00
Paul RastoinandGitHub b2f7c745f8 Solo transaction application synchronization service refactor (#17864)
# Introduction
Refactoring the application sync service to be making a single validate
build and run transaction instead of calling all the services n times
thanks to the universal workspace migration refactor that allow doing so

## What's next
Migrating all below entities to by syncableEntities so they can be
universalised too ( right now they're still calling the services n times
and won't be returned in the workspace migration )
-
[objectPermission](https://github.com/twentyhq/core-team-issues/issues/2223)
-
[fieldPermission](https://github.com/twentyhq/core-team-issues/issues/2224)
-
[permissionFlag](https://github.com/twentyhq/core-team-issues/issues/2225)
2026-02-11 16:30:01 +00:00
0ee63a0525 i18n - translations (#17872)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-11 17:35:20 +01:00
18880f0385 i18n - translations (#17869)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-11 17:05:37 +01:00
Abdul RahmanGitHubCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>Devessier
0902579fbe Feat: Navbar customization (#17728)
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <[email protected]>
2026-02-11 15:33:51 +00:00
52e57e70fd Add wildcard documentation for like/ilike/containsIlike filters (#17825)
Add documentation for issue #16602 
After discussing with the team (Thomas),
https://discord.com/channels/1130383047699738754/1443986309436936212 we
decided that updating the documentation.
The issue is In compute-where-condition-parts.ts, the like/ilike cases
pass values directly to SQL without adding % wildcards for api using, so
they behave like exact matches.

This PR updates the documentation regarding the use of `like`, `ilike`
and `containsIlike` filters. Instead of auto-wrapping values with %
wildcards in the backend, we are choosing to leave the control to the
API users (%value% or value%).

<img width="651" height="409" alt="image"
src="https://github.com/user-attachments/assets/b3537af6-a0b0-4fff-a86d-a9ae334d628e"
/>

But I add wildcard for `startsWith` and `endsWith` because these
operators have a fixed semantic meaning.

(To see the results, please refresh the cache first, then restart the
server.)

<img width="878" height="458" alt="image"
src="https://github.com/user-attachments/assets/ab0f4e7c-df50-45ef-b1c8-e43c8881a9a3"
/><img width="482" height="288" alt="image"
src="https://github.com/user-attachments/assets/20dc39ee-2417-4ecc-810e-ea0ead33d803"
/>

---------

Co-authored-by: Thomas Trompette <[email protected]>
2026-02-11 15:32:41 +00:00
Thomas TrompetteandGitHub 1e01f15182 Fix code step and logic function step in workflows (#17856)
- AI still often forgets to update the code step after creating it.
Adding a next step
- Starting by loading logic functions, so it avoids creating code steps
when a function exists
- Fix create complete workflow logic. Should not create code steps
directly
2026-02-11 15:08:23 +00:00
EtienneandGitHub d0c1841f0f File - Migrate avatarUrl > avatarFile on person (data migration + logic) + Attachment data migration (#17752)
- Migration command
    - Check IS_FILES_FIELD_MIGRATED:false
    - Check or create avatarFile field
    - Fetch all people with avatarUrl
           - Move (Copy/move) file in storage
           - Create core.file record
           - Update person record
           
    - bonus : attachment migration  : fullPath > file (same logic)
   
- BE logic
    - Add avatarFile field on person

- FE logic 
   - Adapt logic to upload on/display avatarFile data

The whole imageIdentifier logic will be done later
2026-02-11 15:07:05 +00:00
Thomas TrompetteandGitHub 5be64bf4be Remove guard from find logic functions (#17862)
As title
2026-02-11 14:41:40 +01:00
Paul RastoinandGitHub d9dab75052 Do not throw on corrupted labelFieldMetadataIdentifier (#17859)
# Introduction
As we don't enforce any FK on object labelIdentifierFieldMetadataId we
have some that are either null or pointing to non-existing field
metadata resulting in exception thrown at cache computation lvl

Commenting the exception throw until we've closed
https://github.com/twentyhq/core-team-issues/issues/2172

closes https://github.com/twentyhq/core-team-issues/issues/2221
2026-02-11 12:33:38 +00:00
2237273869 Fix spurious logouts by deduplicating concurrent token renewals (#17858)
## Summary

- When returning to the app after idle (or after a deploy), the expired
access token causes multiple simultaneous GraphQL queries to fail with
`UNAUTHENTICATED`. Previously, each failure independently triggered its
own `renewToken` call with the same refresh token. If **any single**
renewal failed (e.g. server briefly slow after a deploy), the `catch`
handler would nuke the session and redirect to sign-in — even if another
concurrent renewal had already succeeded and written valid tokens.
- This adds a shared `renewalPromise` so that only the first
`UNAUTHENTICATED` error triggers a server-side renewal. All concurrent
callers await the same promise and replay their operations once it
resolves. This eliminates redundant refresh token rotation on the server
and removes the race condition where a straggling failure could log out
an already-renewed session.

## Test plan

- [ ] Log in, wait >30 minutes (or manually expire the access token),
then interact with the app — should silently renew without redirect to
sign-in
- [ ] Open browser DevTools Network tab, trigger the above scenario, and
verify only **one** `renewToken` mutation is sent (instead of N)
- [ ] With server temporarily stopped, verify that a genuine renewal
failure still correctly redirects to sign-in (single
`onUnauthenticatedError` call)
- [ ] Open multiple browser tabs, let access tokens expire, interact in
one tab — other tabs should also recover gracefully on their next
request


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
2026-02-11 12:28:52 +00:00
Charles BochetandGitHub 9e21e55db4 Prevent leak between /metadata and /graphql GQL schemas (#17845)
## Fix resolver schema leaking between `/metadata` and `/graphql`
endpoints

### Summary
- Patch `@nestjs/graphql` to support a `resolverSchemaScope` option that
filters resolvers at both schema generation and runtime, preventing
cross-endpoint leaking
- Introduce `@CoreResolver()` and `@MetadataResolver()` decorators to
explicitly scope each resolver to its endpoint
- Move most resolvers (auth, billing, workspace, user, etc.) to the
metadata schema where the frontend expects them; only workflow and
timeline calendar/messaging resolvers remain on `/graphql`
- Fix frontend `SSEQuerySubscribeEffect` to use the default (metadata)
Apollo client instead of the core client

### Problem
NestJS GraphQL's module-based resolver discovery traverses transitive
imports, causing resolvers from `/metadata` modules to leak into the
`/graphql` schema and vice versa. This made the schemas unpredictable
and tightly coupled to module import order.

### Approach
- Added `resolverSchemaScope` to `GqlModuleOptions` via a patch on
`@nestjs/graphql`, filtering in both `filterResolvers()` (runtime
binding) and `getAllCtors()` (schema generation)
- Each resolver is explicitly decorated with `@CoreResolver()` or
`@MetadataResolver()`
- Organized decorator, constant, and type files under `graphql-config/`
following project conventions


Core GQL Schema: (see: no more fields!)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/668f3f0f-485e-43f0-92be-4345aeccacb6"
/>

Metadata GQL Schema (see no more getTimelineCalendarEventsFromCompany)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/443913db-e5fe-4161-b0e7-4a971cc80a71"
/>
2026-02-11 10:05:24 +00:00
859241d237 Fix merge records page accumulating duplicate morph items (#17705)
## Why
When opening **Merge records** repeatedly, morph items for the same
command-menu page were appended instead of replaced. This could produce
duplicated IDs (e.g. `[A,B,B,A]`) in the merge flow and extra duplicate
tabs in the UI.

## What
- Update `useCommandMenuUpdateNavigationMorphItemsByPage` to replace
page morph items instead of appending existing ones.
- Add regression tests covering:
  - replacing existing morph items for the same page
  - keeping only the latest payload when called twice for the same page

## Notes
I could not run the full workspace tests locally in this environment
because of existing test/build setup issues unrelated to this change
(missing `packages/twenty-front/tsconfig.spec.json` and
`temporal-polyfill` resolution in dependent tasks).

Co-authored-by: remi <[email protected]>
2026-02-11 09:55:19 +00:00
Paul RastoinandGitHub 41e413d7b1 Runner metadata events (#17841)
# Introduction
Followup https://github.com/twentyhq/twenty/pull/17622

Refactoring the actions handler to be returning a metadata event
It has to be done incrementally, as if not update metadata event would
be stale as depends on the incremental action execution order and
optimistic application

## What's next
- Builder should consume and regroup each metadata even in order to
batch emit them ( within a single metadata actions batch order matters )
=> refactoring https://github.com/twentyhq/twenty/pull/17622 in order to
consume runner returned metadata events
2026-02-11 09:09:54 +00:00
b0cb29c11c perf: cache ServerBlockNoteEditor instance in transformRichTextV2Value (#17844)
## Summary
- `transformRichTextV2Value` was calling `await
import('@blocknote/server-util')` and `ServerBlockNoteEditor.create()`
on **every single invocation**, adding ~90ms of overhead each time
(visible as the highest avg-duration frame in profiling at 93.49ms).
- Cache the `ServerBlockNoteEditor` instance at module level so the
dynamic import + creation only happens once for the lifetime of the
process.
- Also removes debug timing instrumentation (`performance.now()`,
`calculateInputSize`, `Logger`) that is no longer needed.

## Test plan
- [ ] Verify rich text fields (blocknote/markdown) still round-trip
correctly on create and update
- [ ] Confirm reduced CPU time for `transformRichTextV2Value` in
profiling


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-11 09:08:59 +00:00
Félix MalfaitGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
b4d957306e fix: show user-friendly error message when duplicate invite is sent (#17827)
Fixes #17822

Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-11 00:06:42 +01:00
9c984b137f Fix phone validation performance by using Set/Map instead of Array lookups (#17843)
## Summary
- **`isValidCountryCode`** was using `Array.includes()` on ~250 country
codes (O(n) per call). Replaced with a `Set.has()` lookup (O(1)).
- **`getCountryCodesForCallingCode`** was iterating all ~250 countries
and calling `getCountryCallingCode()` on each one **every invocation**.
Replaced with a precomputed `Map<callingCode, CountryCode[]>` built once
at module load (O(1) per call).

Both functions are called from `transformPhonesValue` on every phone
field mutation, causing cumulative overhead visible in profiling (p95
self-time ~20-35ms).

## Test plan
- [ ] Verify phone field creation/update still works correctly (country
code validation, calling code resolution)
- [ ] Verify spreadsheet import with phone fields still validates
properly
- [ ] Confirm no regression in `isValidCountryCode` or
`getCountryCodesForCallingCode` behavior


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-10 19:56:43 +00:00
05a6a96b13 i18n - translations (#17842)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-10 20:23:26 +01:00
16d414590b Lowercase email (#17775)
Fixes https://github.com/twentyhq/core-team-issues/issues/120 #16976
Partially related to #17711

Frontend check surprisingly was one-liner covering both email input and
import files

Migration script will be done in next commit

---------

Co-authored-by: Etienne <[email protected]>
2026-02-10 18:24:49 +00:00
bc72879c70 Fix commands order for v1.17.0 (#17839)
Order should be

1. We delete all the file records (from core.file table)
2. We add the foreign key file / applicationId (pg constraint)
3. We further update the table structure: fullPath is deleted; path is
created; unicity constraint between workspaceId/applicationId/path is
created (pg constraint)
4. we migrate the workflow steps (this will create files in core.file)
5. we backfill the application package (same)

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-10 18:47:59 +01:00
bb037e13dd Fix blocknote.map crash with generic field-level RICH_TEXT_V2 handler (#17834)
## Summary

Fixes #17667

- **Root cause**: `ActivityQueryResultGetterHandler` called
`JSON.parse()` on the `blocknote` field and assumed the result was
always an array. When the stored value was valid JSON but not an array
(e.g., `"{}"`), `blocknote.map()` crashed with `blocknote.map is not a
function`, breaking the entire notes page.
- **Fix**: Replaced the object-level `ActivityQueryResultGetterHandler`
(hardcoded for `note`/`task` only) with a generic field-level
`RichTextV2FieldQueryResultGetterHandler` that safely parses blocknote
JSON with `Array.isArray` validation and gracefully skips malformed
values instead of crashing.
- **Bonus**: The new handler works for **all** objects with
`RICH_TEXT_V2` fields (not just `note`/`task`), following the same
pattern as the existing `FilesFieldQueryResultGetterHandler`.

## Changes

| File | Change |
|------|--------|
| `rich-text-v2-field-query-result-getter.handler.ts` | New field-level
handler with safe blocknote parsing |
| `common-result-getters.service.ts` | Register new handler, remove
`note`/`task` object handlers |
| `activity-query-result-getter.handler.ts` | Deleted (replaced by
field-level handler) |
| `rich-text-v2-field-query-result-getter.handler.spec.ts` | 9 tests
covering all edge cases |

## Test plan

- [x] Unit tests pass (9 tests covering: null blocknote, non-string
blocknote, invalid JSON, non-array JSON like `"{}"`, no images, external
URLs, internal URLs, multiple fields)
- [x] Lint passes (`lint:diff-with-main`)
- [x] Typecheck passes


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
2026-02-10 17:07:49 +00:00
Thomas TrompetteandGitHub 17786a3298 SSO - Check if assertion is signed (#17837)
As title
2026-02-10 15:29:34 +00:00
8f91529153 i18n - translations (#17838)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-10 15:24:40 +01:00
e995e84621 [DASHBOARDS] chat agent improvements + new validation layer (#17722)
https://github.com/user-attachments/assets/09550210-76c5-4a40-83b6-9ab785ca10c3



https://github.com/user-attachments/assets/352427fc-0a2a-4f1b-86e9-db99daea0018

---------

Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
2026-02-10 13:43:36 +00:00
Paul RastoinandGitHub 148584c730 Migrate all remaining workspace migration create action to universal (#17836)
# Introduction
Completely finalize the universal migration at builder and runner
levels.
Which mean that from now on the builder only compares
`universalFlatEntity` and produces universal workspace migration that
the runner ingest

## What's done
Migrated all create metadata remaining for
- agent
- skill
- commandMenuItem
- navigationMenuItem
- fieldMetadata
- objectMetadata
- view
- viewField
- viewFilter
- viewGroup
- viewFilterGroup
- index
- logicFunction
- role
- roleTarget
- pageLayout
- pageLayoutTab
- pageLayoutWidget
- rowLevelPermissionPredicate
- rowLevelPermissionPredicateGroup
- frontComponent
2026-02-10 13:39:37 +00:00
Charles BochetandGitHub b7ff587b5e Query cache instead of database for event listener webhook, logicFunction, triggers (#17824)
## Fix
Replaced direct database queries with existing flat entity map caches
for the two that already have cache infrastructure:
- **CallDatabaseEventTriggerJobsJob** - now uses flatLogicFunctionMaps
cache via WorkspaceCacheService.getOrRecompute(), filtering in memory
for non-deleted logic functions with databaseEventTriggerSettings.
- **CallWebhookJobsJob** - now uses flatWebhookMaps cache via
WorkspaceCacheService.getOrRecompute(), filtering in memory for webhooks
matching the event's operations.
- Note: **WorkflowDatabaseEventTriggerListener** - left as-is since
WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity (not
SyncableEntity) and has no flat entity map cache infrastructure yet.
2026-02-10 13:14:06 +00:00
2e77a68daf i18n - translations (#17835)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-10 12:44:49 +01:00
BOHEUSandGitHub 382746dd52 Clarify forms usage (#17772)
Fixes https://github.com/twentyhq/core-team-issues/issues/1704
2026-02-10 12:35:37 +01:00
WeikoandGitHub 202a130ac8 revert releasing RLS (#17809) (#17832) 2026-02-10 11:48:59 +01:00
Thomas des FrancsandGitHub 24c6d3fef3 Fix row-level permissions 'Where' left spacing (#17831)
<img width="634" height="289" alt="image"
src="https://github.com/user-attachments/assets/c8fe85e1-ecac-4b57-be36-6c4a5e426c60"
/>
2026-02-10 11:48:32 +01:00
martmullandGitHub 52fe21c04b Fix ci + improvements (#17795)
as title
2026-02-10 11:48:10 +01:00
Paul RastoinandGitHub ee0474e287 Remove standard ids (#17833)
Deadcode
2026-02-10 10:37:55 +00:00
Thomas des FrancsandGitHub 3f202c5c6a Fix downgrade to Pro CTA icon and wording (#17829)
## Summary
- Fixes the downgrade CTA icon for `Switch to Pro` from an up arrow to a
down arrow.
- Fixes wording generation so no `undefined` suffix is concatenated in
downgrade/plan-switch confirmation copy.

## Before

<img width="717" height="535" alt="image"
src="https://github.com/user-attachments/assets/f934d130-c9fa-46bb-b677-88edf64bc623"
/>

<img width="508" height="325" alt="image"
src="https://github.com/user-attachments/assets/4c776922-a169-4543-b791-fcefc093fa7f"
/>

## Testing
- Not run locally in this environment.
2026-02-10 09:41:48 +00:00
a63b31931f Extract SecureHttpClientService into its own module (#17828)
## Summary

- **Extract `SecureHttpClientService`** from `tool` module into a
dedicated `core-modules/secure-http-client/` module with proper NestJS
module encapsulation
- **Fix module hygiene**: 12 modules that incorrectly listed
`SecureHttpClientService` as a direct provider now properly import
`SecureHttpClientModule`
- **Add structured logging** for outbound HTTP requests with
workspace/user context (for GuardDuty alert correlation)
- **Rename type files** to follow one-export-per-file convention
(`get-secure-axios-adapter.types.ts` ->
`secure-adapter-dependencies.type.ts`, new
`outbound-request-context.type.ts` / `outbound-request-source.type.ts`)

### Why

`SecureHttpClientService` is a cross-cutting concern (used by auth,
captcha, file upload, geo-map, telemetry, admin-panel, REST API, contact
creation, webhooks, and workflow tools) but was bundled inside the
`tool` module. Most consumers worked around this by listing it as a
direct provider instead of importing a module, which is fragile and not
idiomatic NestJS.

## Test plan

- [x] All 60 unit tests pass (`secure-http-client.service.spec.ts`,
`get-secure-axios-adapter.util.spec.ts`, `is-private-ip.util.spec.ts`)
- [x] Related module tests pass (admin-panel, contact-creation, tool)
- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx lint:diff-with-main twenty-server` passes
- [x] Server compiles and bootstraps successfully


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
2026-02-10 09:16:51 +00:00
3c2aec1894 i18n - translations (#17830)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-10 10:32:49 +01:00
Baptiste DevessierandGitHub b250de216e Remove the code of old show pages (#17811)
Closes https://github.com/twentyhq/core-team-issues/issues/2213
2026-02-10 09:10:00 +00:00
2d42b0a726 - Added accomodation of mobile navigation bar in command menu (#17419)
Fix for #16743 

Added styles to accommodate for mobile navigation bar for better
visibility and better access of the command menu buttons.

## CommandMenuAskAIPage.tsx (Not shown in the issue)
### Before
<img width="1100" height="1792" alt="screenshot-2026-01-24_17-22-45"
src="https://github.com/user-attachments/assets/8a337ffe-9dfc-4e52-b744-622cc6989ae4"
/>

### After 
Since the component here already had some padding on it the mobile
navigation bar offset was lesser than it is in other places.
<img width="1092" height="1778" alt="screenshot-2026-01-24_17-28-33"
src="https://github.com/user-attachments/assets/00a217f1-d98e-4303-bdc1-df112d71a553"
/>


## CommandMenuWorkflowEditStep.tsx
### Before
As mentioned in the issue

### After
<img width="755" height="1061" alt="image"
src="https://github.com/user-attachments/assets/487d0b20-f544-4e85-99d5-eb5add4b2cb4"
/>

## CommandMenuWorkflowRunViewStep.tsx
### Before
As shown in the issue

### After
<img width="1102" height="1784" alt="screenshot-2026-01-24_17-19-31"
src="https://github.com/user-attachments/assets/a95773e3-5f61-46d0-93cb-e678e087a42f"
/>

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-09 23:48:45 +01:00
Abdullah.andGitHub 7e5e800f63 fix: remove axios stale entry by ensuring transitive version of axios is also latest (#17823)
Last upgrade PR left stale entries for transitive import of Axios. 

Raising this PR to fix and ensure Axios 1.13.5 is the de-facto in
yarn.lock for consistency.
2026-02-09 23:23:06 +01:00
Baptiste DevessierandGitHub 8c70a875f8 fix: set a margin even for empty side-column widgets (#17806)
| | Read | Edit |
|--------|--------|--------|
| **Before** | <img width="762" height="2144" alt="CleanShot 2026-02-09
at 16 07 06@2x"
src="https://github.com/user-attachments/assets/d93b5928-2c7b-438a-b16a-d17a434276fc"
/> | <img width="762" height="2144" alt="CleanShot 2026-02-09 at 16 07
01@2x"
src="https://github.com/user-attachments/assets/c5fb94db-947d-4832-9bc2-7942faa290eb"
/> |
| **After** | <img width="762" height="2144" alt="CleanShot 2026-02-09
at 16 06 33@2x"
src="https://github.com/user-attachments/assets/761352f3-cf18-4f41-98f0-fb0b4367f486"
/> | <img width="762" height="2144" alt="CleanShot 2026-02-09 at 16 06
41@2x"
src="https://github.com/user-attachments/assets/d1780671-1043-4087-860c-020343fcda87"
/> |


Closes https://github.com/twentyhq/core-team-issues/issues/2193
2026-02-09 22:59:00 +01:00
Abdullah.andGitHub a40612ef9e fix: axios related dependabot alerts (#17821)
Resolves [Dependabot Alert
436](https://github.com/twentyhq/twenty/security/dependabot/436),
[Dependabot Alert
437](https://github.com/twentyhq/twenty/security/dependabot/437) and
[Dependabot Alert
438](https://github.com/twentyhq/twenty/security/dependabot/438).
2026-02-09 22:56:34 +01:00
neo773andGitHub 1979e013e9 Google OAuth check real permissions before creating channels (#17714) 2026-02-09 22:49:03 +01:00
3747005fd5 i18n - translations (#17820)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 22:43:06 +01:00
neo773andGitHub 268cc40d80 Show accounts with pending message channel configuration (#17770) 2026-02-09 22:29:27 +01:00
MarieGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Félix Malfait
11fd1a41f3 [Fix] fix timelineActivities on notes and tasks (#17814)
[Fixes
sentry](https://twenty-v7.sentry.io/issues/7238708876/?environment=prod&environment=prod-eu&project=4507072499810304&query=Field%20metadata%20for%20field&referrer=issue-stream&sort=date):
_Field metadata for field "targetTargetPersonId" is missing in object
metadata timelineActivity_

Regression caused by migration of note and task targets - fields were
renamed from personId to targetPersonId, impacting the event names,
while we still expected them in their previous shape.

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <[email protected]>
2026-02-09 20:47:43 +00:00
05eca08ad2 releasing RLS (#17809)
Co-authored-by: Charles Bochet <[email protected]>
2026-02-09 21:56:41 +01:00
Abdullah.andGitHub ef1464db73 Improve AI-chat design to match the one provided in Figma (#17816)
Fixed colors, gaps, component nesting, alignment to match Figma.
2026-02-09 18:28:25 +00:00
neo773andGitHub 5ccf18fdf5 Add handling for internal_failure in Gmail API error parser (#17718)
fixes TWENTY-SERVER-D3X
2026-02-09 18:02:09 +00:00
9e6f19d16e Fix RLS creation logic (#17815)
Fix after resolveEntityRelationUniversalIdentifiers introduction
```
FlatEntityMapsException [Error]: Could not find rowLevelPermissionPredicateGroup for given rowLevelPermissionPredicateGroupId
        at resolveEntityRelationUniversalIdentifiers
```

- Creating util for RLS flat entity creation to be aligned with other
entities
- Progressively update the flat entity maps as each new group is built,
following the same optimistic pattern used by
computeUniversalFlatEntityMapsFromTo in the validate build and run. The
updated maps are now passed to computePredicateOperations so predicates
can also resolve references to the newly created groups.
- Adding more coverage

Co-authored-by: Charles Bochet <[email protected]>
2026-02-09 19:19:23 +01:00
Charles BochetandGitHub 987ed845ac Release Twenty SDK 0.5.0 (#17818)
As per title + create-twenty-app
2026-02-09 19:16:24 +01:00
f51291704d [FRONT COMPONENTS] Retrieve the front components from the backend (#17813)
- Pass the auth token to worker
- Fetch the file from the rest API with the auth token
- Create the blob url
- Update the stories to pass a fake token which will be ignored

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-09 19:10:29 +01:00
Charles BochetandGitHub 37b9a55382 Migrate metrics to prometheus (#17810)
<img width="1316" height="611" alt="image"
src="https://github.com/user-attachments/assets/277a63ed-2a8b-41ff-be78-281de8891579"
/>
2026-02-09 19:09:13 +01:00
aa7973e5b8 i18n - translations (#17817)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 19:08:48 +01:00
8c951d3623 Migrate Views-xxx Index Field Object Skill to be fully universal ( all actions and metadata runner and builder ) + all metadata update actions runner (#17687)
# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels

## Universal and flat optimistic tooling

`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.

## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.

## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.


## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.

It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.

Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.

## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-09 19:02:38 +01:00
2a76e1791e Allow AI to update code steps (#17761)
https://github.com/user-attachments/assets/35ac5c23-4d5c-4c86-8233-7b58fbeb5a27

- add tool
- move resolver code into a service

---------

Co-authored-by: Félix Malfait <[email protected]>
2026-02-09 15:40:44 +00:00
Weiko 4ae375308f Revert "Releasing RLS"
This reverts commit c01853c349.
2026-02-09 16:52:58 +01:00
Weiko c01853c349 Releasing RLS 2026-02-09 16:52:12 +01:00
MarieandGitHub 21b2b65dbe [Fix] fix relations in apps (#17791)
When syncing an app with relation multiple times, it would fail because
the relation fields could not be identified due to universalIdentifier
being overwritten at field cretion
2026-02-09 15:22:14 +00:00
Raphaël BosiandGitHub c6d04ccced [FRONT COMPONENTS] Serialize events through the worker boundary (#17767)
- Introduces a SerializedEventData type that captures only serializable
properties from DOM/React events
- Updates `wrapEventHandler` in the host component registry to serialize
native events via serializeEvent() before passing them across the worker
boundary via postMessage, avoiding circular references and non-cloneable
DOM nodes
- Updates generated remote element event signatures to use
RemoteEvent<SerializedEventData> instead of bare RemoteEvent, giving
front-component authors typed access to event details
2026-02-09 16:22:54 +01:00
c18726d712 Fix captcha validation failing due to missing URL in secure axios adapter (#17807)
## Summary

- Fixes login failing with `Error: URL is required` when captcha (Google
reCAPTCHA or Turnstile) is enabled
- The secure axios adapter (`getSecureAxiosAdapter`) checked
`config.url` before `baseURL` resolution. In Axios, `baseURL + url`
combination happens inside the default HTTP adapter, not before custom
adapters are called. When captcha drivers configure a `baseURL` and call
`.post('', data)`, the empty string url was incorrectly treated as
missing.
- The adapter now resolves `baseURL` + `url` itself before validation,
matching the default Axios HTTP adapter behavior

## Test plan

- [x] Existing unit tests (23 tests) all pass
- [ ] Verify login works with captcha enabled (`CAPTCHA_DRIVER` set to
`google-recaptcha` or `turnstile`)


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-09 16:20:41 +01:00
WeikoandGitHub 96bc3594a3 Serve frontend components (#17798)
## Context
Ability to serve frontend component

See:
```typescript
curl -i 'http://localhost:3000/rest/front-components/35063b3f-bc4c-4358-8966-7762677802a3' \
--header 'Authorization: Bearer eyJhb...'
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/javascript
Date: Mon, 09 Feb 2026 10:28:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

// react-globals:react/jsx-runtime
var jsx = globalThis.jsx;
var jsxs = globalThis.jsxs;
var Fragment = globalThis.React.Fragment;

// src/front-components/test.tsx
var RemoteComponents = globalThis.RemoteComponents;
var Component = () => {
  return /* @__PURE__ */ jsxs(RemoteComponents.HtmlDiv, { style: { padding: "20px", fontFamily: "sans-serif" }, children: [
    /* @__PURE__ */ jsx(RemoteComponents.HtmlH1, { children: "My new component!" }),
    /* @__PURE__ */ jsx(RemoteComponents.HtmlP, { children: "This is your front component: test" })
  ] });
};
var test_default = globalThis.jsx(Component, {});
export {
  test_default as default
};
//# sourceMappingURL=test.mjs.map
```

readFile_v2 returns a Node.js Stream object (Readable). Here we are
using stream pipeline which connects the readable stream (file) to the
writable stream (HTTP response) which efficiently streams the file
content directly to the HTTP response without loading the entire file
into memory. (in chunks, handling backpressure and closing the
connection when the file is fully sent)
2026-02-09 14:52:14 +00:00
Baptiste DevessierandGitHub 617f634b6d Use backend types in Record Page Layouts' frontend (#17794) 2026-02-09 14:36:02 +00:00
8e977912e2 i18n - translations (#17804)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 15:44:40 +01:00
4cdb7d61b2 Redesign AI chat and add pre-existing prompts. (#17787)
Redesigned AI-chat based on the following provided design.

<p align="center">
<img
src="https://github.com/user-attachments/assets/f10ebbd2-9ee9-402f-b246-6e8f8cedbd53"
width="225" />
</p>

---------

Co-authored-by: Félix Malfait <[email protected]>
2026-02-09 15:29:32 +01:00
Raphaël BosiandGitHub 7fbd50e5a0 [FRONT COMPONENTS] Create Icon component in twenty ui (#17797)
This will be used in the remote dom. We need a component which can take
a icon name as a parameter and return an icon component.
2026-02-09 13:57:22 +00:00
Abdullah.andGitHub 0b4cebfe0e Resolve tar related dependabot alerts in the application-layer. (#17801)
Resolves [Dependabot Alert
424](https://github.com/twentyhq/twenty/security/dependabot/424),
[Dependabot Alert
425](https://github.com/twentyhq/twenty/security/dependabot/425) and
[Dependabot Alert
426](https://github.com/twentyhq/twenty/security/dependabot/426).
2026-02-09 14:41:26 +01:00
Charles BochetandGitHub d7c28d6455 Fix file constraint migration (#17796)
## Summary

- Wrap `AddFileEntityUniqueConstraint` migration in a SAVEPOINT
try-catch so it doesn't break when duplicate `(workspaceId,
applicationId, path)` rows exist in `core.file`
- Extend `DeleteFileRecordsCommand` upgrade command to add the unique
constraint after cleaning up file records
2026-02-09 14:40:35 +01:00
Raphaël BosiandGitHub 2b29918bf8 [FRONT COMPONENTS] Navigate from the remote (#17762)
## PR Description

This PR:
- Introduces a `FrontComponentHostCommunicationApi`, which allows us to
pass functions to be executed from the worker
- Exposes a navigate function from the host to the front component
remote workers, enabling SDK components to trigger in-app navigation
- Gets rid of `useSyncExternalStore` and makes the execution context
reactive without it
- Refactors and improves the `esbuild` plugin system

## Video


https://github.com/user-attachments/assets/7b26a1c2-f85f-4898-a71d-f60c70e61711
2026-02-09 14:38:35 +01:00
2106f46e9e i18n - translations (#17803)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 14:35:41 +01:00
MarieandGitHub 1edce5088c Flush cache before and after upgrade for self-hosts (#17800)
as per title
2026-02-09 14:35:13 +01:00
Félix MalfaitGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
3216b634a3 feat: improve AI chat - system prompt, tool output, context window display (#17769)
⚠️ **AI-generated PR — not ready for review** ⚠️

cc @FelixMalfait

---

## Changes

### System prompt improvements
- Explicit skill-before-tools workflow to prevent the model from calling
tools without loading the matching skill first
- Data efficiency guidance (default small limits, use filters)
- Pluralized `load_skill` → `load_skills` for consistency with
`load_tools`

### Token usage reduction
- Output serialization layer: strips null/undefined/empty values from
tool results
- Lowered default `find_*` limit from 100 → 10, max from 1000 → 100

### System object tool generation
- System objects (calendar events, messages, etc.) now generate AI tools
- Only workflow-related and favorite-related objects are excluded

### Context window display fix
- **Bug**: UI compared cumulative tokens (sum of all turns) against
single-request context window → showed 100% after a few turns
- **Fix**: Track `conversationSize` (last step's `inputTokens`) which
represents the actual conversation history size sent to the model
- New `conversationSize` column on thread entity with migration

### Workspace AI instructions
- Support for custom workspace-level AI instructions

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-09 14:26:02 +01:00
Abdullah.andGitHub 6c7c389785 fix: webpack related dependabot alerts (#17792)
Resolves [Dependabot Alert
422](https://github.com/twentyhq/twenty/security/dependabot/422) and
[Dependabot Alert
423](https://github.com/twentyhq/twenty/security/dependabot/423).
2026-02-09 13:12:05 +01:00
martmullandGitHub 9162685b2e Reorganize logic function files (#17766)
reorganize according to

<img width="1243" height="725" alt="Pasted Graphic"
src="https://github.com/user-attachments/assets/ba65dd10-8eec-4b13-ad49-9726edd3b79c"
/>

Not working yet
2026-02-09 12:36:39 +01:00
bf4c348c8b i18n - translations (#17790)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 11:18:24 +01:00
Félix MalfaitGitHubCursorclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Etienne
ece265c6e4 Harden local file storage driver path resolution (#17783)
## Summary

- Normalize all file paths with `path.resolve` instead of `join` to
properly handle `..` segments in file path inputs
- Add `assertPathIsWithinStorage` guard on all write, delete, move,
copy, and existence-check operations
- Introduce `ACCESS_DENIED` exception code with i18n-ready user-friendly
message
- Read path already had realpath-based validation; updated its error
code to `ACCESS_DENIED` for consistency

## Test plan

- [x] Typecheck passes
- [x] Lint passes
- [x] Manual: verify file upload/download still works with valid paths
- [x] Manual: verify `../` in file paths is rejected with ACCESS_DENIED


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Etienne <[email protected]>
2026-02-09 09:54:10 +00:00
15f09736b2 Fix importing people with mixed-case domain URL failing to match company (#17774)
Fix #17711 
### Reproduce steps
1. import this xlsx file
[test-import.xlsx](https://github.com/user-attachments/files/25156642/test-import.xlsx)

2. import company worksheet at first and then import people worksheet
3. you will see an error <img width="324" height="101"
alt="Snipaste_2026-02-07_19-22-04"
src="https://github.com/user-attachments/assets/bf2703da-a57a-4795-805b-6ddcc689c621"
/>

And I found neither the frontend nor the backend applies lowercase
normalization to the query value. Although the standard UI input always
displays company links in lowercase, user might mixed the case in their
xlsx/csv bulk import. So I did a simple check in frontend.

### Additional findings:
1. Email has the same case sensitivity issue when opportunities import.
You can test same as in test-import.xlsx file (I created a opportunities
worksheet. It will have same issue: <img width="330" height="101"
alt="email error"
src="https://github.com/user-attachments/assets/db36b19b-2abe-4c61-a661-f8d1eb357a5e"
/>

2. API also has: I also tested via the GraphQL API Playground and
confirmed that createOnePerson with a mixed-case URL fails to find an
existing company. <img width="1419" height="513"
alt="Snipaste_2026-02-07_23-33-56"
src="https://github.com/user-attachments/assets/c189f1fd-9793-42d5-a1a9-616cffd2592e"
/>

### Approach:
1. Only change it in frontend, and I will add email check later; (my
current commit)
2. Or backend: Normalize values in computeUniqueConstraintCondition in
twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts
(which handles connect.where queries, and this will cover xlsx import
and api import: createone, createmany, updatemany, updateone. And much
better for future extension.

(Personally, I'd prefer the backend approach. But I'd love to hear your
thoughts on which approach you'd prefer, and whether my analysis is on
the right track.

---------

Co-authored-by: Etienne <[email protected]>
2026-02-09 09:38:09 +00:00
Félix MalfaitGitHubCursorclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
bade5289b6 Fix Cloudflare webhook guard validation logic (#17780)
## Summary

- Restructures the `CloudflareSecretMatchGuard` to properly validate the
`cf-webhook-auth` header before performing the comparison — checks for
missing, empty, or mismatched-length values first
- Removes `@ts-expect-error` workarounds with explicit typed header
access
- Expands test coverage with additional edge cases (missing header,
wrong value, empty string, length mismatch)

## Test plan

- [x] Unit tests pass (6 tests covering matching, missing config,
missing header, wrong value, empty string, length mismatch)


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-09 08:23:57 +00:00
497230a052 i18n - translations (#17789)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-09 00:22:32 +01:00
WeikoandGitHub 9aa63f7ddc Add sync front component (#17748)
## Context
Allow twenty apps to sync front components
2026-02-08 23:00:23 +00:00
Félix MalfaitGitHubCursorclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
40d7e740ef Add token type validation and remove dead code in JWT verification (#17784)
## Summary

- Removes unreachable dead code in `verifyJwtToken` — a legacy API key
verification block where the condition (`!payload.type && type ===
API_KEY`) was logically impossible. Also removes the now-unused
`isLegacyApiKey` parameter and `generateAppSecretLegacy` method.
- Adds explicit token type validation after JWT decode in
`verifyLoginToken`, `verifyRefreshToken`, and `verifyTransientToken`.
Each function now rejects tokens whose `type` field doesn't match what's
expected (defense-in-depth — the HMAC secret already binds the type, but
this makes the contract explicit).

## Test plan

- [x] Updated existing specs for login-token, refresh-token, renew-token
services — all 16 tests passing
- [x] Lint clean

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-08 19:44:42 +00:00
7402edb887 Remove unused GraphQL throttler plugin and graphql-rate-limit dependency (#17785)
## Summary

- Deletes `use-throttler.ts` — an envelop plugin that was never
registered in the GraphQL config plugin chain (dead code since
introduction)
- Removes the `graphql-rate-limit` dependency from `package.json` (only
consumer was the deleted file)

API rate limiting continues to work via `ThrottlerService` in the query
runner layer (for API key auth). Global coverage should be handled at
the infrastructure level (Cloudflare).

## Test plan

- [x] Confirmed no imports of `useThrottler`, `ThrottlerPluginOptions`,
or `UnauthenticatedError` from this file exist anywhere in the codebase
- [x] `graphql-rate-limit` has no other consumers

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-08 19:52:59 +01:00
Félix MalfaitGitHubCursorclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
d7132b35d3 Centralize outbound HTTP requests through SecureHttpClientService (#17779)
## Summary

- Migrates all direct `axios` and `@nestjs/axios` `HttpService` usages
across the server to go through `SecureHttpClientService`, which
conditionally applies SSRF protection based on the
`OUTBOUND_HTTP_SAFE_MODE_ENABLED` config flag
- `SecureHttpClientService.getHttpClient()` now accepts optional
`AxiosRequestConfig` (e.g., `baseURL`) so callers can configure their
client while still getting protection
- Adds `getInternalHttpClient()` for trusted same-server requests (e.g.,
REST-to-GraphQL proxy, code-interpreter downloading internal files)
- Renames `getSecureAdapter` to `getSecureAxiosAdapter` for clarity
- Captcha drivers now receive a pre-configured `AxiosInstance` from the
module factory instead of creating their own

## Migrated services

| Service | Previous | Risk level |
|---------|----------|-----------|
| `file-upload.service` | `HttpService` | High (user-provided image
URLs) |
| `code-interpreter-tool` | `HttpService` + direct adapter | High
(user-provided file URLs) |
| `search-help-center-tool` | `axios.post()` | Low (hardcoded endpoints)
|
| `http-tool` | Already migrated | High (user-provided URLs) |
| `admin-panel.service` | `axios.get()` | Low (Docker Hub API) |
| `sign-in-up.service` | `HttpService` | Medium (logo URL validation) |
| `google-apis-scopes` | `HttpService` | Low (Google API) |
| `geo-map.service` | `HttpService` | Low (Google Maps API) |
| `telemetry.service` | `HttpService` | Low (telemetry endpoint) |
| `rest-api.service` | `HttpService` | Internal (uses
`getInternalHttpClient`) |
| `create-company.service` | `axios.create()` | Low (Twenty companies
API) |
| `google-recaptcha.driver` | `axios.create()` | Low (Google reCAPTCHA)
|
| `turnstile.driver` | `axios.create()` | Low (Cloudflare Turnstile) |

## Test plan

- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx lint:diff-with-main twenty-server` passes
- [x] Admin panel unit tests pass
- [x] Secure adapter unit tests pass

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-08 19:52:35 +01:00
bc6268bb29 Rename REFRESH_TOKEN_COOL_DOWN to REFRESH_TOKEN_REUSE_GRACE_PERIOD and anchor the grace window (#17782)
## Summary

- Renames `REFRESH_TOKEN_COOL_DOWN` to
`REFRESH_TOKEN_REUSE_GRACE_PERIOD` — the old name was misleading and
suggested a security mechanism rather than what it actually is: a grace
period for concurrent refresh token use (e.g. two browser tabs
refreshing simultaneously).
- Makes the token revocation in `renew-token.service.ts` conditional
(`revokedAt: IsNull()`), so if the token was already revoked by a
concurrent request, the original `revokedAt` timestamp is preserved and
the grace window stays anchored.
- Updates comments and config description to clarify intent.

## Test plan

- [x] Existing unit tests updated and passing
(`refresh-token.service.spec.ts`, `renew-token.service.spec.ts`)
- [x] Lint clean

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <[email protected]>
2026-02-08 18:15:37 +01:00
Félix MalfaitGitHubCursorclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
c44d61f324 Validate timezone input in group-by date queries (#17777)
## Summary

- Validates that the timezone parameter in group-by date expressions is
a recognized IANA timezone
- Adds SQL string literal escaping as a defense-in-depth measure for the
timezone value interpolated into SQL expressions
- Moves the `IANA_TIME_ZONES` constant to `twenty-shared` so it can be
reused across frontend and server packages
- Adds `INVALID_TIMEZONE` error code mapped to 400 Bad Request in both
GraphQL and REST API exception handlers

## Test plan

- [x] Unit tests for `validateIanaTimeZone` (valid IANA zones, fixed
offsets, rejects invalid strings)
- [x] Unit tests for `escapeSqlStringLiteral`
- [x] Integration tests for `getGroupByExpression` covering timezone
validation and granularity handling


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-08 16:06:19 +00:00
WeikoandGitHub cc86b9acae Fix record page layout BE (#17758)
## Context
- Add missing conditional display
- Set default tab as null for most of the page layout, letting the FE
handle that
- Fix duplicate "Note" widget in both task and note pages
- Simplify typing, removing redundant layoutName
2026-02-06 18:34:35 +00:00
30cbf0e40e i18n - translations (#17768)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-06 18:27:52 +01:00
Baptiste DevessierandGitHub 4acca5b10d Store record page layouts in a global state (#17759) 2026-02-06 17:12:00 +00:00
Paul RastoinandGitHub 3dc5b162c7 Spread in parent and requires FlatEntity.__universal (#17753)
# Introduction
Requiring the spreaded `__universal` record that aggregates all the
universal identifier ( relations fk and aggregators ) of an entity to
its root

It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be
finalized because if we don't we would have to migrated all related
entities at once in order for them to always have the universal
properties

## `resolveEntityRelationUniversalIdentifiers`
Introduced `resolveEntityRelationUniversalIdentifiers` a centralized
utility that resolves foreign key IDs to universal identifiers using
ALL_METADATA_RELATIONS metadata. It provides strict typing for both
input (foreign keys) and output (universal identifiers), with
nullability dynamically inferred from entity relation types.
Strictly and dynamically typed for both output and input

To do so added a new type and const/runtime grain to
ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from
the entity relation property types. And fixed incorrectly typed typeorm
entities

### Usage
```ts
  const {
    availabilityObjectMetadataUniversalIdentifier,
    frontComponentUniversalIdentifier,
  } = resolveEntityRelationUniversalIdentifiers({
    metadataName: 'commandMenuItem',
    foreignKeyValues: {
      availabilityObjectMetadataId:
        createCommandMenuItemInput.availabilityObjectMetadataId,
      frontComponentId: createCommandMenuItemInput.frontComponentId,
    },
    flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps },
  });
```
2026-02-06 17:02:02 +00:00
Thomas TrompetteandGitHub a494a7a902 Fix iterator creation (#17763)
On iterator creation, we get an error "Bad configuration". Looks like a
race condition with generation that happens before apollo cache gets
updated. Adding defensive checks
2026-02-06 15:25:09 +00:00
f6beb06364 Migrate favorites to navigation menu items (1.17 upgrade) (#17477)
**What this PR does:**
- Migrates existing `Favorite` and `FavoriteFolder` entities to the new
`NavigationMenuItem` structure
- Preserves user-level vs workspace-level ownership
- Preserves folder structure, positions, and relationships
- Handles both view-based favorites (linked to views) and record-based
favorites (linked to records)
- Soft-deletes original favorites and folders after successful migration
- Enables the `IS_NAVIGATION_MENU_ITEM_ENABLED` feature flag
post-migration
- Skips migration if the feature flag is already enabled
- Idempotent: checks for existing navigation menu items to prevent
duplicates

**Migration flow:**
1. Migrate favorite folders first (creates folder mapping)
2. Migrate favorites
3. Soft-delete migrated favorites and folders
4. Enable feature flag

**What's next:**
- After all workspaces are migrated and the navigation menu item feature
is fully rolled out, we can:
- Remove the old `Favorite` and `FavoriteFolder` entities and related
code
- Remove the feature flag check and make navigation menu items the
default
  - Clean up any deprecated favorites-related code paths in the frontend

---------

Co-authored-by: Etienne <[email protected]>
2026-02-06 13:42:20 +00:00
7074d9ce1b Fix CSV preview duplicate key warning (#10920) (#17754)
Fix issue #10920
<img width="1264" height="630" alt="image"
src="https://github.com/user-attachments/assets/d7a00e4f-85cb-49e1-aa38-4c807f1e1b69"
/>


The root cause is that `@cyntler/react-doc-viewer`'s CSV renderer uses
**cell values as React keys** instead of indices.

### There are two approaches:
1. **patch-package** — Directly fix the key usage in the library's
source code and pull a request to the author of
`@cyntler/react-doc-viewer` to fix it.

<img width="880" height="469" alt="image"
src="https://github.com/user-attachments/assets/819e5b6a-21ae-4333-87f5-3b7f6d7e2738"
/>

<img width="1753" height="568" alt="image"
src="https://github.com/user-attachments/assets/c8a0ee49-9bf4-4002-aad2-65914ac10254"
/>



2. Custom CSV renderer (My current code commit)

### Changes
- `fetchCsvPreview.ts` — Fetches CSV and parses it into headers and rows
- `DocumentViewer.tsx` — Renders CSV with a custom table instead of
passing it to DocViewer

---------

Co-authored-by: Etienne <[email protected]>
2026-02-06 13:35:47 +00:00
WeikoandGitHub b3c95744ef Fix twenty-emails build (#17760)
## Context
I've seen errors in twenty-emails build where I18n from @lingui/core was
resolved to two different declaration files
```typescript
src/components/BaseEmail.tsx:20:19 - error TS2719: Type 'import("/Users/weiko/Projects/twenty/node_modules/@lingui/core/dist/index").I18n' is not assignable to type 'import("/Users/weiko/Projects/twenty/node_modules/@lingui/core/dist/index").I18n'. Two different types with this name exist, but they are unrelated.
  Types have separate declarations of a private property '_locale'.

20     <I18nProvider i18n={i18nInstance}>
                     ~~~~

  node_modules/@lingui/react/dist/shared/react.b2b749a9.d.ts:42:5
    42     i18n: I18n;
           ~~~~
    The expected type comes from property 'i18n' which is declared here on type 'IntrinsicAttributes & Omit<I18nContext, "_"> & { children?: ReactNode; }'
 ```
Seems to be related to https://github.com/twentyhq/twenty/pull/17380
The tsconfig simplification changed how vite plugin resolves types during build. With the inherited moduleResolution: "node", the plugin and source code resolved @lingui/react's types differently, creating two incompatible I18n types from the same package.

## Fix
Add moduleResolution: "bundler" to packages/twenty-emails/tsconfig.json, aligning with twenty-front, twenty-ui, twenty-shared should fix the issue
2026-02-06 11:17:11 +00:00
Lakshay ManchandaandGitHub 10de51fcbd - fixed coloring of item type tag in dark mode (#17745)
Fixed issue #17694 

<img width="963" height="1021" alt="image"
src="https://github.com/user-attachments/assets/a28948a1-126d-4f3c-8a53-c39adbb7f52d"
/>
2026-02-06 10:28:04 +00:00
Félix MalfaitandGitHub d537144ab1 fix: add command to fix morph relation field name mismatches (#17757)
## Summary

When a custom object is renamed after creation, the relation fields on
`noteTarget`, `taskTarget`, `attachment`, and `timelineActivity` keep
their old names but point to the renamed object.

For example:
- User creates custom object `solution`
- System creates field `solution` on NoteTarget → pointing to Solution
- User renames object from `solution` to `productCatalog`
- Field name stays as `solution` but points to `productCatalog`
- Migration runs: `solution` → `targetSolution`
- Now `targetSolution` points to `productCatalog` 

This causes the morph name computation to fail:
- `getMorphNameFromMorphFieldMetadataName("targetSolution",
"productCatalog")`
- Tries to remove `ProductCatalog` from `targetSolution` → no match
- Name stays as `targetSolution` instead of becoming `target`
- Frontend computes: `targetSolution` + `Company` =
`targetSolutionCompany` 💥

## The Fix

This command finds and fixes all such mismatches by:
1. Finding MORPH_RELATION fields where `field.name !=
target${capitalize(targetObject.nameSingular)}`
2. Renaming the column in the workspace schema
3. Updating the field metadata (name + joinColumnName in settings)
4. Invalidating the cache

## Usage

**Dry run (to see what would be fixed):**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names -- --workspace-id <id> --dry-run
```

**Fix a specific workspace:**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names -- --workspace-id <id>
```

**Fix all workspaces:**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names
```

## SQL to find affected workspaces

```sql
SELECT 
  fm."workspaceId",
  COUNT(*) AS mismatched_fields
FROM core."fieldMetadata" fm
JOIN core."objectMetadata" target_om ON fm."relationTargetObjectMetadataId" = target_om.id
JOIN core."objectMetadata" source_om ON fm."objectMetadataId" = source_om.id
WHERE fm.type = 'MORPH_RELATION'
  AND fm.name LIKE 'target%'
  AND source_om."nameSingular" IN ('noteTarget', 'taskTarget', 'attachment', 'timelineActivity')
GROUP BY fm."workspaceId"
HAVING COUNT(*) FILTER (
  WHERE fm.name != 'target' || initcap(replace(target_om."nameSingular", '_', ''))
) > 0;
```
2026-02-06 11:38:50 +01:00
Baptiste DevessierandGitHub 955c1b4916 Allow filtering by page layout type and fetch more fields (#17750)
<img width="3456" height="2160" alt="CleanShot 2026-02-05 at 18 01
28@2x"
src="https://github.com/user-attachments/assets/d8914bdd-e44c-459d-a87f-1b00dd4c29c2"
/>
2026-02-05 17:34:28 +00:00
e505eba978 i18n - translations (#17751)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-05 18:14:47 +01:00
EtienneandGitHub 77d15356e1 Files v2 - Use Files field in attachment (#17707)
- Add FILES field on attachment
- Adapt Attachment logic in front to use new resolver/controller
- Update files-field logic to infer applicationId from fieldMetadataId +
ask for fieldMetadataId in upload resolver
- Design update


To do in next PR : 
- Adapt activity files logic
2026-02-05 16:42:19 +00:00
Félix MalfaitandGitHub e382641ac3 fix: use real founder headshots in seed data (#17749)
## Summary
Updates the avatar URLs for seeded founders to use properly named images
instead of generic numbered placeholder images.

## Changes
Updates `prefill-people.ts` to reference new image paths in
`twentyhq/placeholder-images/founders/`:

| Person | Company | New Image Path |
|--------|---------|----------------|
| Brian Chesky | Airbnb | `founders/brian-chesky.jpg` |
| Dario Amodei | Anthropic | `founders/dario-amodei.jpg` |
| Patrick Collison | Stripe | `founders/patrick-collison.jpg` |
| Dylan Field | Figma | `founders/dylan-field.jpg` |
| Ivan Zhao | Notion | `founders/ivan-zhao.jpg` |

## Related
Requires a corresponding PR on `twentyhq/placeholder-images` to add the
actual founder headshot images to the `founders/` directory.

## Why
The previous placeholder images were generic numbered images that didn't
represent the actual people. This creates a single source of truth for
founder images that all workspaces can reference.
2026-02-05 17:49:43 +01:00
Thomas TrompetteandGitHub 04b8f2aaf5 Add gauge for awaiting jobs (#17747)
<img width="774" height="333" alt="Capture d’écran 2026-02-05 à 16 18
06"
src="https://github.com/user-attachments/assets/ec938a39-801d-4d71-a2de-7ced1795a0bd"
/>
2026-02-05 15:47:53 +00:00
martmullandGitHub 23df33172a Fix twenty sdk build 5 (#17744)
use prepublish instead of prepack
2026-02-05 15:54:06 +01:00
5b701f5ba4 Never display broken relations in record page layouts (#17738)
We generate Field widgets for relations on-the-fly, when a record page
layout is first requested by the user. When the user changed their data
model and then returned to a record page layout, the Field widgets
weren't updated. **This PR ensures Field widgets are recomputed when
relations change.**

Future subjects:

- Now that we will fetch the configuration from the backend and start
storing updates, we will have to think about how we deal with these
generated relation Field widgets.

## Before


https://github.com/user-attachments/assets/99d53b19-b231-435f-b14f-4473ba269ad2

## After


https://github.com/user-attachments/assets/357d956d-8b3b-448c-a983-82569a7dd0a0

---------

Co-authored-by: Félix Malfait <[email protected]>
2026-02-05 14:07:04 +00:00
Thomas TrompetteandGitHub df516a904b Add lock on version creation (#17740)
Should avoid duplicates we have sometimes
2026-02-05 14:01:21 +00:00
c0fd4c7fed i18n - translations (#17743)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-05 15:07:16 +01:00
Thomas TrompetteandGitHub c521406bf8 Improve workflow crons (#17720)
Issue 1: no info to debug cron trigger. Stop catching exception + using
logs instead of throwing for now

Issue 2: sentry often send timeouts errors for workflow crons. Probably
not real ones, it sends it if the job takes more than 5 minutes to run.
To fix, on each workflow cron we do:
- loop over active workspaces
- perform a query check that workspace is relevant, using count for
performances
- send a job if relevant
2026-02-05 13:36:32 +00:00
Félix MalfaitandGitHub 27da9d60eb fix: set isActive=true in morph migration & add system fields toggle (#17736)
## Summary

Three fixes in this PR:

### 1. Migration commands now set `isActive = true` and use generic
'Target' label

When converting relation fields to `MORPH_RELATION` type, the migration
commands now:
- Set `isActive = true` to prevent inactive fields from being selected
as the representative morph field
- Update all field labels to generic **'Target'** instead of keeping
individual labels like 'Company', 'Person'

This ensures the UI shows a coherent label ('Target') alongside 'X
Objects' for the type.

**Files changed:**
- `1-17-migrate-note-target-to-morph-relations.command.ts`
- `1-17-migrate-task-target-to-morph-relations.command.ts`

### 2. Added system fields/relations toggles in Settings

The fields and relations tables in Settings > Data Model were filtering
out system fields with no way to view them. Added a "System fields" /
"System relations" toggle (visible in advanced mode) to allow viewing
these fields.

**Files changed:**
- `SettingsObjectFieldTable.tsx`
- `SettingsObjectRelationsTable.tsx`

This matches the existing behavior on the Objects table which already
has a "System objects" toggle.
2026-02-05 13:35:46 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah
0473efcef0 Bump drizzle-kit from 0.31.5 to 0.31.8 (#17725)
Bumps [drizzle-kit](https://github.com/drizzle-team/drizzle-orm) from
0.31.5 to 0.31.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/drizzle-team/drizzle-orm/releases">drizzle-kit's
releases</a>.</em></p>
<blockquote>
<h2>[email protected].8</h2>
<h3>Bug fixes</h3>
<ul>
<li>Fixed <code>algorythm</code> =&gt; <code>algorithm</code> typo.</li>
<li>Fixed external dependencies in build configuration.</li>
</ul>
<h2>[email protected].6</h2>
<h3>Bug fixes</h3>
<ul>
<li><a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/2853">[BUG]:
Importing drizzle-kit/api fails in ESM modules</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/c445637df39366bcf47b12601896ce851771c1c2"><code>c445637</code></a>
Merge pull request <a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/5095">#5095</a>
from drizzle-team/main-workflows</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/e7b3aaa26456b88cd23a7843ebc95b3bddde1ba4"><code>e7b3aaa</code></a>
Merge branch 'main' into main-workflows</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/0d885a54ddafd8717f8610cf3d2899f3eef61e65"><code>0d885a5</code></a>
refactor: Update condition for run-feature job to improve clarity and
functio...</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/45a1ffbcbfdd96772d0aba7d9e43744db2dce471"><code>45a1ffb</code></a>
Merge pull request <a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/5087">#5087</a>
from drizzle-team/main-workflows</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/6357645bd33b1f444e1d081769dd4b71c3de31f8"><code>6357645</code></a>
chore: Comment out NEON_HTTP_CONNECTION_STRING requirement in release
workflows</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/53dec98a936f549d0cc2e668f19db3a2df842f51"><code>53dec98</code></a>
refactor: Simplify release router workflow by removing unnecessary
switch job...</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/ce88a181e03d8b9b3fd0b62c93cc1faa05b0e000"><code>ce88a18</code></a>
Merge remote-tracking branch 'origin/ext-deps-kit' into
main-workflows</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/5c8a4c508b36813599e6de891166a6888720a307"><code>5c8a4c5</code></a>
+</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/73e2ea486f6781bc7bfd2c287590d9c96e319b51"><code>73e2ea4</code></a>
feat: Add release router workflow to manage feature and latest
releases</li>
<li><a
href="https://github.com/drizzle-team/drizzle-orm/commit/378b0432d549441fa61de200589a790f1171b6fe"><code>378b043</code></a>
Merge pull request <a
href="https://redirect.github.com/drizzle-team/drizzle-orm/issues/5002">#5002</a>
from drizzle-team/main-next-pack</li>
<li>Additional commits viewable in <a
href="https://github.com/drizzle-team/drizzle-orm/compare/[email protected]@0.31.8">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by [GitHub Actions](<a
href="https://www.npmjs.com/~GitHub">https://www.npmjs.com/~GitHub</a>
Actions), a new releaser for drizzle-kit since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=drizzle-kit&package-manager=npm_and_yarn&previous-version=0.31.5&new-version=0.31.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <[email protected]>
2026-02-05 13:02:33 +00:00
martmullandGitHub de5764ede0 Fix twenty sdk build (#17729)
- remove worksapce:* dependencies from published packages
- Use common tsconfig.ts in create-twenty-app
- Increase version to 0.4.4
2026-02-05 14:25:49 +01:00
Félix MalfaitandGitHub f899804db3 fix: update lockfile for twenty CLI bin path change (#17739)
## Summary

The lockfile had a stale reference to `dist/cli/index.cjs` but the
twenty CLI bin was changed to `dist/cli.cjs`.

This was causing CI to fail with:
```
YN0028: -    twenty: dist/cli/index.cjs
YN0028: +    twenty: dist/cli.cjs
YN0028: The lockfile would have been modified by this install, which is explicitly forbidden.
```

This PR updates the lockfile to match the new path.
2026-02-05 14:12:04 +01:00
Lucas BordeauandGitHub 66b87c641c Fixed SSE connection retry infinite loop (#17723)
This bug was cause by a combination of an expired token stored inside
SSE client on the front end, and a server restart or a cache flush, we
ended up with an SSE client that tries to reconnect infinitely with an
expired token.

The fix is to improve the connection retry handler in the frontend to
detect an expired token and recreate an SSE client.
2026-02-05 13:19:43 +01:00
Raphaël BosiandGitHub 309785f7ff Refactor twenty-sdk folder stucture (#17733)
Only three barrels: `root`, `ui` and `front-component`
2026-02-05 13:14:27 +01:00
Abdullah.andGitHub 60f2a74bf0 fix: fast-xml-parser has rangeerror dos numeric entities bug (#17732)
Resolves [Dependabot Alert
412](https://github.com/twentyhq/twenty/security/dependabot/412).

AWS SDK minor-releases are backward compatible.
2026-02-05 13:09:47 +01:00
Félix MalfaitandGitHub 6851085143 Fix note/task target creation to support morph relations (#17734)
## Overview
Fixes the `unknown fields opportunityId in objectMetadataItem
noteTarget` error when creating notes/tasks from record pages (like the
Notes/Tasks tab on an opportunity).

## Root Cause
The `useOpenCreateActivityDrawer` hook was not handling `MORPH_RELATION`
field types:

1. Code only checked `field.relation` (which is populated for `RELATION`
type fields)
2. When `field.relation` was undefined (because the field is
`MORPH_RELATION`), it fell back to the old naming convention (e.g.,
`opportunityId`)
3. But after the morph migration, the columns are named differently
(e.g., `targetOpportunityId`)

This caused the error on both new workspaces (which are created with
morph relations from the start) and existing workspaces that ran the
migration.

## Solution
Use the existing `findTargetFieldInfo()` utility which properly handles
both `RELATION` and `MORPH_RELATION` field types by:
- Checking `morphRelations` for morph fields and computing the correct
field name
- Checking `relation` for regular relation fields
- Returning the correct `joinColumnName` for each case

This utility is already used elsewhere in the codebase (e.g., in
junction field handling) and is the cleanest solution.
2026-02-05 11:52:23 +00:00
9604dbe78a Front components communication between host and remote (#17716)
Co-authored-by: Charles Bochet <[email protected]>
2026-02-05 11:44:02 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f733f30b62 Bump @cyntler/react-doc-viewer from 1.17.0 to 1.17.1 (#17727)
Bumps
[@cyntler/react-doc-viewer](https://github.com/cyntler/react-doc-viewer)
from 1.17.0 to 1.17.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cyntler/react-doc-viewer/releases"><code>@​cyntler/react-doc-viewer</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v1.17.1</h2>
<ul>
<li>style: prettier fix tr.json (2392605)</li>
<li>Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/305">#305</a>
from wangyinyuan/main (36029ea)</li>
<li>Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/297">#297</a>
from ysaribulut/feature/turkish-translation (fa591ca)</li>
<li>fix: correct Chinese and special characters display in HTML renderer
(c358c9d)</li>
<li>Update README.md (e6fe4e0)</li>
<li>feat: add Turkish translations (4066963)</li>
<li>Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/290">#290</a>
from Pespiri/main (c6ded83)</li>
<li>fix styled components prop pollution (5bd551a)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/955795bca8bb5c81f76674321a019ef4f838f307"><code>955795b</code></a>
Release 1.17.1</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/239260507647a0ac282a45960ac8d7e01b35bbaf"><code>2392605</code></a>
style: prettier fix tr.json</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/36029ea39c28b2a90adc605d46a45265f14ddc16"><code>36029ea</code></a>
Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/305">#305</a>
from wangyinyuan/main</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/fa591caf45e819f125efe979b34ec753362176a3"><code>fa591ca</code></a>
Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/297">#297</a>
from ysaribulut/feature/turkish-translation</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/c358c9d103beaafb1dded42fd142f2ede9482598"><code>c358c9d</code></a>
fix: correct Chinese and special characters display in HTML
renderer</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/e6fe4e089ba869f5e2abdee9b308baa42ff7b1c5"><code>e6fe4e0</code></a>
Update README.md</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/4066963b06f306af9e5029ee29bef89c815c7e4b"><code>4066963</code></a>
feat: add Turkish translations</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/c6ded83f9032f4ed2a44a947e854976e2dce8398"><code>c6ded83</code></a>
Merge pull request <a
href="https://redirect.github.com/cyntler/react-doc-viewer/issues/290">#290</a>
from Pespiri/main</li>
<li><a
href="https://github.com/cyntler/react-doc-viewer/commit/5bd551a7382b636c371733c2e34e1e89e2129daf"><code>5bd551a</code></a>
fix styled components prop pollution</li>
<li>See full diff in <a
href="https://github.com/cyntler/react-doc-viewer/compare/v1.17.0...v1.17.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@cyntler/react-doc-viewer&package-manager=npm_and_yarn&previous-version=1.17.0&new-version=1.17.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-05 10:07:38 +01:00
1d6dc04cda i18n - translations (#17724)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-05 02:30:12 +01:00
Charles BochetandGitHub 67a98f77e3 Refactor workflow-logic-function-interaction (#17699)
# Refactor workflow–logic function interaction

## Why

Workflow code steps and standalone logic functions shared the same build
layer and DB layer, which blurred two use cases: code steps belong to a
workflow version; standalone functions are deployable units. That made
workflow code steps harder to own and evolve.

## Goal

Treat code steps as **workflow-owned**: build and run them in workflow
context, and expose workflow-scoped APIs so the editor can load, test,
and save code step source without going through the generic
logic-function layer.
2026-02-05 00:46:55 +00:00
neo773andGitHub 752359335e exclude Gmail category labels only for system folders (#17640)
Previous code in `MESSAGING_GMAIL_DEFAULT_NOT_SYNCED_LABELS` was
problematic as we grouped `category` labels along with `system folders`
labels together.

This fixes partially missing emails issue by splitting it and not
applying category exclusion when querying for a singular custom label.

Also removes old approach of getting message label_id's association from
additional network call overhead to local utility
`filterGmailMessagesByFolderPolicy`
2026-02-04 17:57:48 +00:00
WeikoGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Weiko
64700a00b0 Fix typeorm internal query builder missing twenty internal context (#17719)
## Context
Fix TypeError: Cannot read properties of undefined (reading
'coreDataSource') when updating records with RLS predicates enabled

## Implementation
Use lazy initialization for FilesFieldSync and RelationNestedQueries in
workspace query builder

## Technical details
When Row-Level Security (RLS) predicates are applied during an update
operation, TypeORM internally creates sub-query builders to evaluate
Brackets in WHERE clauses. TypeORM's createQueryBuilder() method calls
new this.constructor(connection, queryRunner) with only 2 arguments, but
WorkspaceUpdateQueryBuilder expects 6 arguments including
internalContext.
This caused FilesFieldSync to be instantiated with undefined context,
resulting in the error when accessing internalContext.coreDataSource.

Converting filesFieldSync and relationNestedQueries from eager
initialization in the constructor to lazy getters. This way:
TypeORM internal sub-query builders work fine (they never access these
dependencies)
Real query builders create dependencies on-demand when execute() runs

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Weiko <[email protected]>
2026-02-04 16:57:42 +00:00
WeikoandGitHub 8674cf973e Display "Not shared" indicator for RLS-restricted relation fields (#17713)
## Context
Previously, if for example a Person had a companyId but the company
relation was null due to RLS, the company column appeared empty. Now it
displays ForbiddenFieldDisplay to indicate the relation exists but is
inaccessible.

When RLS restricts access to a related record, the frontend now shows a
"Not shared" indicator (with lock icon) instead of an empty field.

<img width="1275" height="399" alt="Screenshot 2026-02-04 at 15 37 53"
src="https://github.com/user-attachments/assets/870306f8-f811-4dc0-b23b-a33e89043a37"
/>
2026-02-04 16:49:39 +00:00
Félix MalfaitandGitHub 96c37b30c5 fix: use TwentyConfigService instead of ConfigService for ENTERPRISE_KEY (#17721)
## Summary

Replace NestJS `ConfigService` with `TwentyConfigService` for
`ENTERPRISE_KEY` access in row-level permission services.

## Changes

- `row-level-permission-predicate.service.ts`: Updated to use
`TwentyConfigService`
- `row-level-permission-predicate-group.service.ts`: Updated to use
`TwentyConfigService`

## Why

`TwentyConfigService` also pulls config values from the database, not
just environment variables. This ensures consistent config access across
the codebase.
2026-02-04 16:35:53 +00:00
martmullandGitHub bef643970b Fix twenty sdk build 3 (#17715)
final final fix
2026-02-04 15:21:17 +00:00
MarieandGitHub 6a5974e06c [fix] Some fixes (#17674)
- fixes
[sentry](https://twenty-v7.sentry.io/issues/7239112455/?environment=prod&environment=prod-eu&project=4507072563183616&query=anything%20else&referrer=issue-stream&sort=date)
- adapt useClearField logic to morph relations
- adapt useClearField logic to one-to-many relations (early return)
- fix creation of objects without "name" field from specific parts of
the product (which involved a "name" field)
2026-02-04 14:51:33 +00:00
MarieandGitHub d4303469ff Improve qualification of rest errors (#17629)
Will help close
[sentry](https://twenty-v7.sentry.io/issues/6827612895/?environment=prod&environment=prod-eu&project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D%20timesSeen%3A%3E10&referrer=issue-stream&sort=date):
a 400 error (attempt to filter on a `null` id) qualified as a 500 error
from rest api.

I don't know what was our long term plan on validation for rest api, I
suppose it's not a priority, making it acceptable to interceipt some
postgres errors based on their messages. Ideally we would have a
validation earlier, at args parsing level.

before
<img width="1286" height="460" alt="Capture d’écran 2026-02-02 à 14 08
53"
src="https://github.com/user-attachments/assets/bf3d34f9-1436-4739-8f8e-95b18f59045b"
/>

after
<img width="1222" height="438" alt="Capture d’écran 2026-02-02 à 14 09
01"
src="https://github.com/user-attachments/assets/e817eaff-1ab9-49fb-869d-ddfd00671fbf"
/>
2026-02-04 14:33:30 +00:00
4629fb90be i18n - translations (#17712)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-04 15:24:23 +01:00
Paul RastoinandGitHub 48c8fa6809 Refactor workspace migration update action (#17701)
# Introduction
Removing:
- `from` property from actions definition, as it's a legitimate source
of truth. The stored comparison might have been compromised since action
generation. If from is needed it should be computed from the optimistic
cache at runner lvl
- Removed the `FlatEntityPropertyUpdates` Array complexity in favor of

From
```ts
export type PropertyUpdate<T, P extends keyof T> = {
  property: P;
} & FromTo<T[P]>;
```

To
```ts
export type FlatEntityUpdate<T extends AllMetadataName> = Partial<
  Pick<
    MetadataFlatEntity<T>,
    Extract<FlatEntityPropertiesToCompare<T>, keyof MetadataFlatEntity<T>>
  >
>;
```

## New interactions
From
```ts
    const positionUpdate = findFlatEntityPropertyUpdate({
      flatEntityUpdates,
      property: 'position',
    });

    if (
      isDefined(positionUpdate) &&
      (!Number.isInteger(positionUpdate.to) || positionUpdate.to < 0)
    ) {


   const toFlatNavigationMenuItem = {
      ...fromFlatNavigationMenuItem,
      ...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
        updates: flatEntityUpdates,
      }),
    };
```

To
```ts
    const positionUpdate = flatEntityUpdate.position;
    if (
      isDefined(positionUpdate) &&
      (!Number.isInteger(positionUpdate) || positionUpdate < 0)
    ) {

    const toFlatNavigationMenuItem = {
      ...fromFlatNavigationMenuItem,
      ...flatEntityUpdate,
    };
```

## `SanitizeFlatEntityUpdate`
Enforcing the `flatEntityUpdate` to only contains comparable properties
per flat entity by striping out all unexpected keys
In the future we will also move the whole validation at runner lvl at
some point

```ts
export const sanitizeFlatEntityUpdate = <T extends AllMetadataName>({
  flatEntityUpdate,
  metadataName,
}: {
  flatEntityUpdate: FlatEntityUpdate<T>;
  metadataName: T;
}): FlatEntityUpdate<T> => {
  const { propertiesToCompare } =
    ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName];

  const initialAccumulator: FlatEntityUpdate<T> = {};

  return propertiesToCompare.reduce((accumulator, property) => {
    const updatedValue =
      flatEntityUpdate[property as MetadataFlatEntityComparableProperties<T>];

    if (updatedValue === undefined) {
      return accumulator;
    }

    return {
      ...accumulator,
      [property]: updatedValue,
    };
  }, initialAccumulator);
};
```
2026-02-04 13:50:46 +00:00
Thomas TrompetteandGitHub b03e3772e6 Add real time image (#17710)
As title
2026-02-04 14:43:42 +01:00
nitinandGitHub 660a15b721 [FRONT COMPONENT] Stories in twenty-sdk for front component generation and upon render interactivity (#17675)
closes https://github.com/twentyhq/core-team-issues/issues/2179
2026-02-04 12:39:54 +00:00
Thomas TrompetteandGitHub 59c36736d4 Put SSE in lab (#17709)
as title
2026-02-04 13:57:25 +01:00
92359603c1 i18n - translations (#17708)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-04 13:44:21 +01:00
Félix MalfaitandGitHub 177cae8c53 feat: audit Logs (#17660) 2026-02-04 13:30:55 +01:00
martmullandGitHub 6407474461 Fix build without nx build (#17700)
as title
2026-02-04 11:14:28 +00:00
bcfacdead9 i18n - translations (#17706)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-04 12:23:29 +01:00
EtienneandGitHub 4fcc424e24 Files field - add files field display and input + filtering (#17637)
This PR introduces a new FILES field type for Twenty CRM, allowing users
to attach multiple files to any record.

- Files field display
- Files field preview
- Files field input
- Files field filtering

To test : 
1/ need to activate feature flag `IS_FILES_FIELD_ENABLED` + create a new
FILES field

- display in read only, edit, inline, table
- edit
- filter
- export


closes https://github.com/twentyhq/core-team-issues/issues/2154


<img width="354" height="134" alt="Screenshot 2026-02-02 at 19 11 01"
src="https://github.com/user-attachments/assets/3c3de89e-f6b6-4526-b710-e4c7ee1a6d30"
/>
<img width="1081" height="933" alt="Screenshot 2026-02-02 at 19 10 41"
src="https://github.com/user-attachments/assets/7c9a7278-edb7-4d4a-882c-f7404689d011"
/>
<img width="1073" height="719" alt="Screenshot 2026-02-02 at 19 10 33"
src="https://github.com/user-attachments/assets/79cc372b-56a2-4cbf-b4fe-023adc4753a8"
/>
2026-02-04 10:55:48 +00:00
Raphaël BosiandGitHub 9b8eacb7f7 [FRONT COMPONENTS] Expose twenty UI in twenty sdk (#17645)
- An app will declare its `twenty-sdk` version in the manifest.
- `twenty-sdk` will be served by a CDN to be imported in front component
host.
- When we render the host we will load twenty-ui through the correct
version of the sdk

We will proceed this way because twenty-ui is not mature enough yet to
be deployed as its own package. So instead, since the sdk is already
deployed as its own package, we will serve the ui with it. It allows us
to have versioning to handle breaking changes in twenty-ui.
2026-02-04 10:43:08 +00:00
EtienneandGitHub f6b210dd0d Files v2 - File table migration + data migration command (#17661)
To add also in 1.16 patch
2026-02-04 10:28:55 +00:00
EtienneandGitHub 50ef231704 Billing - Fix inactivity (#17703)
- Add suspendedAt column on workspace table
- Update suspendedAt field when workspace suspended or re-activated
- Compute inactivity on suspendedAt



Tested : 
- Cancel subscription
- Cancel subscription + Subscribe again
- Cleaning command
2026-02-04 10:17:27 +00:00
aa06dab920 i18n - translations (#17704)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-04 11:11:10 +01:00
Félix MalfaitGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
414f68fb63 fix(workspace-cache): memory leak in deleteFromLocalCache (#17686)
## Problem

The `deleteFromLocalCache` method was only setting `lastHashCheckedAt=0`
instead of actually deleting the cache entry. This caused old versions
to accumulate in the local cache when `invalidateAndRecompute` was
called.

### Flow that causes the leak:

1. `invalidateAndRecompute()` is called
2. `flush()` → `deleteFromLocalCache()` — **only sets
`lastHashCheckedAt=0`, keeps old data**
3. `recomputeDataFromProvider()` → `setInLocalCache()` — **adds new
version with new hash**
4. `cleanupStaleVersions()` — **never called** (only triggered from
`getFromLocalCache` path)

Result: Each `invalidateAndRecompute` call adds a new version to
`entry.versions` without removing the old one.

### Impact

For migration commands (like
`1-17-migrate-attachment-to-morph-relations`) that process thousands of
workspaces, this caused significant memory growth:
- Each workspace calls `getOrRecompute` (version 1)
- Then calls `invalidateAndRecompute` (version 2 added, version 1 stays)
- Memory accumulates as the command processes more workspaces

## Solution

Actually delete the local cache entry in `deleteFromLocalCache`, so
`recomputeDataFromProvider` starts fresh.

```typescript
// Before (memory leak)
if (isDefined(entry)) {
  entry.lastHashCheckedAt = 0;
}

// After (proper cleanup)
this.localCache.delete(localKey);
```

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-04 09:47:45 +00:00
Félix MalfaitGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
0edc3a385c fix: prevent anonymous users from bypassing workspace creation restriction (#17635)
When `IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS` is true, anonymous
users could still create workspaces because
`checkWorkspaceCreationIsAllowedOrThrow` checked per-user workspace
count (always 0 for new users) instead of system-wide workspace count.

The bootstrap bypass now only applies when no workspaces exist in the
entire system. Also adds the same check in `signUpOnNewWorkspace` to
guard the `signInUp` code path.

Fixes #17631

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

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-04 09:47:24 +00:00
Baptiste DevessierandGitHub 29093fdbf1 Fetch soft-deleted records in Record Page Layouts (#17698)
## Before


https://github.com/user-attachments/assets/35a69015-8d3a-498f-8cb3-07bbf3f4d307

## After


https://github.com/user-attachments/assets/1232b41f-50a1-4f7d-afbe-f1fe57bd7de0
2026-02-04 09:07:00 +00:00
Charles BochetandGitHub 402d149ee1 Remove logic function layer (#17697)
## Remove logic function layer

Package.json and yarn.lock are now on the application entity, so the
logic function layer is no longer used except as a legacy source for the
1.17 backfill. This PR removes all layer usage outside of that migration
and keeps only the entity for backfill.

### Summary

- **Kept:** `LogicFunctionLayerEntity` and its table, only used by the
1.17 backfill command to read legacy layer data and backfill application
package files.
- **Removed:** All other layer logic: CRUD, cache, resolvers, services,
DTOs, and frontend types. Logic functions now depend only on the
application for package/dependency context.


### Why Dependencies instead of Source for package files

Package.json and yarn.lock are the application’s dependency set and are
stored under the application in **FileFolder.Dependencies**. The build
service and drivers now read them only from Dependencies; nothing is
written to Source for these files.
2026-02-04 10:17:27 +01:00
martmullandGitHub e10e0b337e Fix twenty sdk build (#17696)
- add twenty-ui in dist/vendor folder
- fix ts issue due to react version mismatch
2026-02-04 08:46:13 +00:00
Abdullah.andGitHub 7867617385 Migrate noteTarget and taskTarget to Morph. (#17476)
This PR migrates `noteTarget` and `taskTarget` to morph relations behind
separate feature flags, following the Attachment/TimelineActivity
pattern.

It introduces the `IS_NOTE_TARGET_MIGRATED` and
`IS_TASK_TARGET_MIGRATED` flags, updates standard field metadata and
indexes to use morph relations, and adds two **1.17 workspace
migrations** that:
- rename `noteTarget.*Id` / `taskTarget.*Id` columns to `target*Id`
- convert the corresponding field metadata to `MORPH_RELATION` with a
shared `morphId`

On the frontend, note/task target read and write paths switch to
`target*Id` when the respective flag is enabled. Deleted targets are
filtered on reload to prevent reappearing relations.
2026-02-04 02:53:06 +00:00
9aba2f62e1 i18n - docs translations (#17692)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-04 00:22:17 +01:00
neo773andGitHub 4ae5732483 Migrate gmail fetch by batch to library (#17514)
Replace manual HTTP batch implementation with
`@jrmdayn/googleapis-batcher` library (we already use this for fetching
message list)

Initial real testing works fine but do not merge yet needs more
extensive real test runs
2026-02-04 00:21:56 +01:00
Charles BochetandGitHub 867b03393b Backfill package json for custom and standard app (#17681)
# Backfill application package files for custom and standard apps

- Backfill `package.json` / `yarn.lock` (and related fields) for
existing workspaces; new standard/custom apps get default dependency
files.
- Default package files under
`application/constants/default-package-files/`; util with hardcoded
checksums (comment on how to regenerate).
- New **FileFolder.Dependencies** for app dependency files;
**writeFile_v2** accepts optional `queryRunner` for transactional
writes.

Usages:
- **Upgrade command** `upgrade:1-17:backfill-application-package-files`:
standard/custom apps → default files; other apps → from logic function
layer.
- **Workspace creation**: create workspace with
`workspaceCustomApplicationId` first, then create application (enables
same-transaction insert). Migration makes workspace/application/file FKs
deferrable.
-  dev seeder
2026-02-03 21:23:29 +01:00
60b48339b9 i18n - docs translations (#17689)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-03 20:14:58 +01:00
Thomas TrompetteandGitHub c18f574aee Trigger refetch workflow on version creation (#17685)
https://github.com/user-attachments/assets/ec156aba-3a67-4eef-addf-86158c3f1837
2026-02-03 17:52:30 +00:00
fdd3e6d245 i18n - translations (#17688)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-03 19:01:52 +01:00
neo773andGitHub 29a2635164 Fix message/calendar channels stuck with null syncStageStartedAt (#17684)
PR #17492 fixed `markAsMessagesListFetchOngoing` to set
`syncStageStartedAt`, but also changed `isSyncStale` to return `false`
for null values. This prevented the stale recovery job from recovering
channels that were already stuck before the fix was deployed.

Changing `isSyncStale` to return `true` for null/undefined allows the
stale job to properly reset stuck channels back to PENDING state.
2026-02-03 17:24:00 +00:00
martmullandGitHub b53dfa0533 Publish twenty packages (#17676)
- removes code editor in settings
- update readmes and docs
2026-02-03 17:16:54 +00:00
Baptiste DevessierandGitHub ccd40e7633 feat: make feature flag image optional (#17679)
Prevent layout shift when no image is provided for a feature flag

## Before


https://github.com/user-attachments/assets/b1a98da9-1208-46f7-9fc6-9c34a9012c02

## After 


https://github.com/user-attachments/assets/ed12df0c-1651-4fc9-a0e7-60f5fb1533a4
2026-02-03 17:03:37 +00:00
3db961c038 i18n - translations (#17682)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-03 18:04:07 +01:00
Paul RastoinandGitHub 476bdf764c Refactor flat entity maps to be universal oriented (#17665)
# Introduction
In preparation of the workspace agnostic builder, we're migrating
`FlatEntityMaps` to be universal identifier oriented and based
As in the builder context there're won't be any ids at all
Please also note that the FlatEntity is a UniversalFlatEntity superset

From
```ts
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';

export type FlatEntityMaps<T extends SyncableFlatEntity> = {
  byId: Partial<Record<string, T>>;
  idByUniversalIdentifier: Partial<Record<string, string>>;
  universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
```

To
```ts
export type FlatEntityMaps<
  T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
  byUniversalIdentifier: Partial<Record<string, T>>;
  universalIdentifierById: Partial<Record<string, string>>;
  universalIdentifiersByApplicationId: Partial<Record<string, string[]>>; // this might make more sense to be migrated to universalIdentifiersByApplicationUniversalIdentifier but it's the main topic of this PR
};
```

## Low level maps tools
Had to refactor find | create | delete | replace | find-many | get-sub
tools ( through mutations and or throw equivalent )
2026-02-03 16:16:02 +00:00
43042d55b2 Scaffold Fields widget edition (#17548)
https://github.com/user-attachments/assets/960b8b0d-10e8-42a1-bf61-e875359ea302

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Devessier <[email protected]>
Co-authored-by: Devessier <[email protected]>
2026-02-03 15:56:41 +00:00
Raphaël BosiandGitHub 66d7182d02 Remote dom twenty UI POC (#17652)
Create a proof of concept which allows us to use twenty-ui in the
remote-dom.
For now only the button is allowed.


https://github.com/user-attachments/assets/e5441d2c-eb63-4b99-931b-86ee14621393

Known limitation: The icons are not yet available
2026-02-03 15:54:18 +00:00
WeikoandGitHub 5c91a96e84 Add position to page layout widgets (#17643)
## Context
Introducing a new position column in PageLayoutWidget. This is because
we already have gridPosition but all widgets are not supposed to fit in
a grid (see pageLayoutTab layoutMode (GRID, VERTICAL_LIST, CANVAS...),
position will now support the different layoutMode

## Implementation
- Adding the new column (not renaming to avoid breaking changes in the
dashboards)
- Add proper validation for each layout mode
- Update standard page layout to not always use GRID but rely on the tab
layout mode

<details>

<summary>Graphql Query</summary>

```graphql
{
  "data": {
    "getPageLayouts": [
      {
        "tabs": [
          {
            "title": "Tab 1",
            "layoutMode": "GRID",
            "widgets": [
              {
                "title": "Untitled Rich Text",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 0,
                  "rowSpan": 6
                }
              },
              {
                "title": "Deals by Company",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 0,
                  "rowSpan": 6
                }
              },
              {
                "title": "Pipeline Value by Stage",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 6,
                  "rowSpan": 6
                }
              },
              {
                "title": "Revenue Timeline",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 6,
                  "rowSpan": 6
                }
              },
              {
                "title": "Opportunities by Owner",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 12,
                  "rowSpan": 6
                }
              },
              {
                "title": "Stock market (Iframe)",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 12,
                  "rowSpan": 8
                }
              },
              {
                "title": "Deals created this month",
                "position": {
                  "column": 0,
                  "columnSpan": 3,
                  "layoutMode": "GRID",
                  "row": 18,
                  "rowSpan": 2
                }
              },
              {
                "title": "Deal value created this month",
                "position": {
                  "column": 3,
                  "columnSpan": 3,
                  "layoutMode": "GRID",
                  "row": 18,
                  "rowSpan": 2
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              },
              {
                "title": "Note",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 1
                }
              }
            ]
          },
          {
            "title": "Note",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Note",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              },
              {
                "title": "Note",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 1
                }
              }
            ]
          },
          {
            "title": "Note",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Note",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}
```

</details>
2026-02-03 15:54:01 +00:00
f009913cbe i18n - translations (#17678)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-03 17:01:31 +01:00
Charles BochetandGitHub 97867c11e6 Upgrade application model (#17673) 2026-02-03 16:44:00 +01:00
1a39cd6019 Disable initial animation in Toggle component (#17648)
## Before


https://github.com/user-attachments/assets/1d915d04-9917-4999-83ea-5fd2e97867e6

## After



https://github.com/user-attachments/assets/091c7e59-f545-43d7-b077-c502b9509799

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Devessier <[email protected]>
Co-authored-by: Baptiste Devessier <[email protected]>
2026-02-03 15:15:11 +00:00
nitinandGitHub c31ffef510 [Dashboard] fix rich text widget bugs + text selection in non edit mode (#17624)
closes -
https://discord.com/channels/1130383047699738754/1467809736715141211 and
https://discord.com/channels/1130383047699738754/1467810112453345333
 
before - 

text selection -- didn't work - 


https://github.com/user-attachments/assets/a12c5301-5eb1-4641-8386-87e534675c67

tripple click color picker bug - 


https://github.com/user-attachments/assets/8f2ebdd9-53c1-4635-8b28-6c82110b88a0


immediate cancel (before 300ms) - draft persist -- have to refresh to
get correct oncancel data



https://github.com/user-attachments/assets/d56e5738-d42f-4ba8-a9d3-b83acb8546ca



after - 
text selection - 


https://github.com/user-attachments/assets/e358035a-1424-45c5-a062-bb2ae6b6ca47

tripple click color picker bug - 


https://github.com/user-attachments/assets/d70947b0-b68b-4a85-93d4-8bed50e308fd



immediate cancel (before 300ms) - 


https://github.com/user-attachments/assets/f3cfb013-1d48-4f2d-b535-4b8bc5bb1c50



immediate save (before 300ms) - 



https://github.com/user-attachments/assets/3b017755-8523-429e-a511-f65732edec34
2026-02-03 15:10:34 +00:00
nitinandGitHub 19b56c5d3a [FRONT COMPONENTS] Frontend component widget creation (#17653)
closes https://github.com/twentyhq/core-team-issues/issues/2182



https://github.com/user-attachments/assets/babf154c-9cda-40ff-b2e8-5053e447c35f
2026-02-03 15:09:52 +00:00
Félix MalfaitandGitHub a1f26cda0d fix: increase findByText timeout for lazy-loaded DateTimePicker tests (#17672)
## Root Cause

The `DateTimePicker` component lazy-loads `react-datepicker` using
React's `lazy()` with a `Suspense` fallback that shows skeleton loaders:

```typescript
const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
  import('react-datepicker').then(...)
);

// In render:
<Suspense fallback={<SkeletonLoader />}>
  <ReactDatePicker ... />
</Suspense>
```

On slower CI runners (GitHub Actions vs depot.dev), the lazy load takes
longer, causing tests to timeout while still showing skeletons instead
of the actual date picker content.

## Fix

Increased `findByText` timeout from the default 1000ms to 10000ms for
tests that wait for the date picker to load:

- `DateTimeFieldInput.stories.tsx` - 4 stories fixed
- `InternalDatePicker.stories.tsx` - 2 stories fixed

## Why This Wasn't Flaky Before

depot.dev runners have faster I/O and more consistent performance, so
the lazy load completed quickly. GitHub Actions runners have more
variable performance, causing the load to sometimes exceed the 1000ms
default timeout.
2026-02-03 15:44:58 +01:00
Thomas TrompetteandGitHub f2284579f0 Fix code container display (#17666)
Display is broken on code step
2026-02-03 14:12:37 +00:00
Charles BochetandGitHub 10ba8b9a97 Improve cache-clear behavior (#17662)
## Cache flush: scope and options

**Scope**
- `cache:flush` only flushes the **cache** Redis (REDIS_URL). It no
longer touches the queue Redis.

**Options**
- **`--namespace`** (optional): Flush one namespace or omit to flush
all. Valid: `module:messaging`, `module:calendar`, `module:workflow`,
`engine:workspace`, `engine:lock`, `engine:health`,
`engine:subscriptions`.
- **`--pattern`** (optional, default `*`): Key pattern inside the chosen
namespace(s).

**Troubleshooting**
- **`cache:flush:verify`**: Inserts keys inside and outside
`engine:workspace`, flushes, and checks only namespace keys are removed
(confirms flush scope).

**Usage**
- `yarn command:prod cache:flush` — flush all **KNOWN** namespaces
- `yarn command:prod cache:flush -n engine:workspace` — flush one
namespace
- `yarn command:prod cache:flush -n engine:workspace -p
"feature-flag:*"` — flush keys matching pattern in given namespace

FYI, existing namespaces:
```
export enum CacheStorageNamespace {
  ModuleMessaging = 'module:messaging',
  ModuleCalendar = 'module:calendar',
  ModuleWorkflow = 'module:workflow',
  EngineWorkspace = 'engine:workspace',
  EngineLock = 'engine:lock',
  EngineHealth = 'engine:health',
  EngineSubscriptions = 'engine:subscriptions',
}
```
2026-02-03 15:13:23 +01:00
Félix MalfaitandGitHub 9b063af7ab fix: increase RelationToOneFieldDisplay perf threshold to 0.4ms (#17671)
## Summary

Follow-up to #17656 - the `RelationToOneFieldDisplay` performance test
is still failing on GitHub Actions runners.

**Failure:** 0.313ms actual vs 0.3ms threshold

**Fix:** Increased threshold from 0.3ms → 0.4ms to provide more headroom
for runner variability.
2026-02-03 15:06:33 +01:00
Thomas TrompetteandGitHub 99aab9f40d Fix spaces in code step + suggestion overflow (#17664)
- fixedOverflowWidgets fixes the suggestions being overflow outside the
editor
- on focus, stop event propagation to avoid catching spaces as document
level events
2026-02-03 13:36:51 +01:00
martmullandGitHub 65678ed99f Upload files instead ofsources (#17608) 2026-02-03 12:11:10 +01:00
MarieandGitHub cdaa8f1d9d Allow twenty standard app to access messaging items (#17659) 2026-02-03 11:40:21 +01:00
Thomas des FrancsGitHubclaude[bot] <41898282+claude[bot]@users.noreply.github.com>Thomas des Francs
963066c7e3 fix: use red3 for dark mode danger background color (#17658)
## Summary

Fixes the dark mode error chip background color issue by changing
`background.danger` from `red12` to `red3` in the dark theme constants.

Fixes #17657

Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Thomas des Francs <[email protected]>
2026-02-03 11:00:28 +01:00
Félix MalfaitandGitHub 2826540bb1 fix: increase performance test thresholds for GitHub Actions runners (#17656)
## Summary

After migrating from depot.dev runners to GitHub Actions runners, three
performance tests are failing due to slower single-threaded performance
on the new infrastructure (even with 16 cores).

## Problem

The performance tests use React's `<Profiler>` API to measure component
render times. depot.dev runners have better single-core performance and
lower memory latency compared to GitHub Actions runners, even larger
ones.

Failed tests:
| Test | Threshold | Actual | Overage |
|------|-----------|--------|---------|
| `DateTimeFieldDisplay` | 0.2ms | 0.281ms | +41% |
| `ChipFieldDisplay` | 0.2ms | 0.222ms | +11% |
| `RelationToOneFieldDisplay` | 0.22ms | 0.237ms | +8% |

## Solution

Increased thresholds to account for GitHub Actions runner performance:

- `DateTimeFieldDisplay`: 0.2ms → 0.35ms
- `ChipFieldDisplay`: 0.2ms → 0.3ms  
- `RelationToOneFieldDisplay`: 0.22ms → 0.3ms

## Future Considerations

For a more robust long-term solution, we could implement baseline
comparison that:
1. Saves performance results on main branch builds
2. Compares PR results against baseline with tolerance (±25%)
3. Self-calibrates to actual runner performance

This would catch real regressions without being sensitive to
infrastructure differences.
2026-02-03 10:03:38 +01:00
Paul RastoinandGitHub d35d5c0463 [BREAKING_CHANGE] Deprecate remaining entities standardId (#17639)
# Introduction
Following https://github.com/twentyhq/twenty/pull/17632 and
https://github.com/twentyhq/twenty/pull/17572
This PR deprecates the agent, skill, field metadata and role
`standardId` in favor of the `universalIdentifier` usage

## Note
- Removed previous standard ids declaration modules
- Twenty-sdk now re-exports the `STANDARD_OBJECTS` universalIdentifier
hashmap constant
- deleted some sync-metadata deadcode too ( mainly types )
2026-02-03 09:06:24 +01:00
cfdcc0065c Migrate workflow serverless to logic (#17646)
Migrations commands

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-02-02 19:39:20 +01:00
Thomas TrompetteandGitHub fd47d5a1a9 Silent errors on code step build (#17651)
As title. Temporary to unblock push to prod
2026-02-02 19:23:14 +01:00
Raphaël BosiandGitHub 7dd8d573ed Fix build docker image error (#17649)
The docker image build is failing after
https://github.com/twentyhq/twenty/pull/17587.
This error happens because now twenty-front now depends on twenty-sdk.
2026-02-02 17:43:46 +00:00
5f0ec8ed9e i18n - translations (#17650)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-02 18:45:38 +01:00
MarieandGitHub 7b48efb5d6 Fix creation of objects with acronym names (e.g. "O&J") (#17633)
Fixes https://github.com/twentyhq/twenty/issues/17544

**Problem**
When users create custom objects with short acronym names like "O&J",
the system generates an object name oJ. When creating relation fields,
the morph field name was built using string concatenation:
const morphFieldName = `target${capitalize("oJ")}`; // → "targetOJ"
This produced "targetOJ", which failed validation because the camelCase
check performed in `validateFlatFieldMetadataName` (camelCase(name) ===
name) returns "targetOj" for "targetOJ". The issue comes from
consecutive camelCase() operations.

**Solution**
Actually, the `camelCase(name) === name` check is questionnable. 
What we want to check is that a name is in camelCase format, not that it
corresponds to the camelCase version of a given string, while that's we
are doing here. lodash does not provide camelCase validator, only
camelCase convertor, so we used it as a way to validate the format of
the name.
We may feel like `camelCase(name) === name` checks whether a name is
camel-cased, but in addition to that it is also checking for a camel
case "idempotency" we don't necessarily have and do not need: for
instance if an object's name is "iOS" (which could be inferred from a
label "I O S"), it won't pass the check: camelCase("iOS") is "ios" and
"ios" !== "iOS".

The existing check with
`STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX`
acts as a camel case validator, so we don't need that camelCase() check.
2026-02-02 17:11:20 +00:00
d9995157bf fix: increase Claude max turns to 200 and add missing bash tools (#17642)
## Summary
- **Increase `--max-turns` from 50 to 200** — Claude was hitting the
turn limit before getting to `gh pr create`, resulting in branches with
no PR
- **Add common bash commands to `--allowedTools`** — `rm`, `find`,
`grep`, `cat`, `ls`, `head`, `tail`, `wc`, `sort`, `uniq`, `mkdir`,
`cp`, `mv`, `touch`, `chmod`, `echo`, `curl`, `cd`, `pwd`, `diff`,
`xargs`, `awk`, `cut`, `tee`, `tr` — denied commands were wasting turns
on retries

## Context
Both [run
21594509983](https://github.com/twentyhq/twenty/actions/runs/21594509983)
and [run
21595364188](https://github.com/twentyhq/twenty/actions/runs/21595364188)
failed with `error_max_turns` after exhausting 50 turns. Permission
denials on `rm` and `find` contributed to wasted turns.

## Test plan
- [ ] Tag `@claude` on an issue with a code change request — verify it
completes and creates a PR

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

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 17:23:24 +01:00
6faef19f64 chore: replace depot.dev runners with GitHub-hosted runners (#17641)
## Summary
- Replace all `depot-ubuntu-24.04-8` runner references with the
equivalent GitHub-hosted `ubuntu-latest-8-cores` runner
- Updated across 4 workflow files: `ci-front.yaml`, `ci-server.yaml`,
`ci-emails.yaml`, `ci-sdk.yaml`
- Also updated cache key names in `ci-front.yaml` that referenced the
depot runner name

## Test plan
- [ ] Verify CI workflows run successfully on the new GitHub-hosted
larger runners
- [ ] Confirm cache keys work correctly with the updated naming

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

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 16:30:13 +01:00
Paul RastoinandGitHub 75921e79bf [FIXES_MAIN] Remove objectMetadata standardId (#17632)
# Introduction
In this PR we're deprecating the object metadata standard id and
replacing it to the universalIdentifier usage
As we've totally removed its insertion for both new field and object in
https://github.com/twentyhq/twenty/pull/17572

## Note
- Removed upgrade commands before `1.17`
2026-02-02 14:26:27 +00:00
48b4127afa i18n - docs translations (#17634)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-02 15:40:19 +01:00
28763428f6 Differentiate edit mode behavior for record pages vs dashboards (#17550)
Temporary disable tabs edition for record page layouts


https://github.com/user-attachments/assets/c1f5d7fd-f125-4fb0-bf9c-96fadec14cd2

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Devessier <[email protected]>
Co-authored-by: Baptiste Devessier <[email protected]>
2026-02-02 13:28:51 +00:00
86c15551ce fix: Claude workflow — prevent bot self-cancellation and add CI permissions (#17628)
## Summary
- **Set `cancel-in-progress: false`** — Claude's own comments (e.g.
error reports) were triggering new workflow runs via `issue_comment`,
which cancelled the in-progress Claude run before the new run's `if`
condition could skip it. Disabling cancel-in-progress prevents this.
- **Filter out Bot users in `if` conditions** — Added
`github.event.comment.user.type != 'Bot'` so Claude's own comments don't
start job evaluation at all.
- **Add back `additional_permissions: actions: read`** — Lets Claude
read CI failure logs to help debug broken builds.
- **Remove `discussion_comment` trigger and job** — `claude-code-action`
doesn't support this event type yet (throws `Unsupported event type:
discussion_comment`). Listed as planned in their security.md.

## Context
Claude runs were consistently failing with `The operation was canceled`
because:
1. Claude posts an error/status comment back to the issue
2. That comment triggers a new `issue_comment` workflow run on the same
issue number
3. The concurrency group cancels the in-progress run before the new run
evaluates its `if` and skips

## Test plan
- [ ] Tag `@claude` on an issue — should run to completion without being
cancelled
- [ ] Verify Claude's own reply comments don't trigger new workflow runs

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

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 14:36:10 +01:00
nitinandGitHub b643fbe425 [Dashboards] Auto focus effect on standalone widget (#17623)
closes
https://discord.com/channels/1130383047699738754/1467809255771078719

before - 



https://github.com/user-attachments/assets/3af6dbfd-358c-44fd-9419-338791fd5cfc


after - 



https://github.com/user-attachments/assets/2e8f89f8-5989-4e62-9bc0-6ddd47c0d421
2026-02-02 13:14:27 +00:00
Thomas des FrancsandGitHub 7f33286274 Update discord link (#17627) 2026-02-02 13:03:13 +00:00
492847a693 i18n - translations (#17626)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-02-02 14:12:14 +01:00
ce7b4838e1 fix: move Claude allowed tools to claude_args (#17625)
## Summary
- `allowed_tools` is not a valid input for `claude-code-action@v1` — it
was silently ignored (visible as a warning in CI logs)
- This caused Claude to lack file write permissions, preventing it from
making actual code changes
- Move the tools list into `claude_args` as `--allowedTools` where it's
actually parsed

## Test plan
- [ ] Trigger `@claude` on a cross-repo issue requesting a code change —
verify it can edit files and create a PR

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

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 13:51:33 +01:00
Paul RastoinandGitHub bd9688421f ObjectMetadata and FieldMetadata agnostic workspace migration runner (#17572)
# Introduction
Important note: This PR officially deprecates the `standardId`, about to
drop col and entity property after this has been merged

Important note2: Haven't updated the optimistic tool to also update the
universal identifier aggregators only the ids one, they should not be
consumed in the runner context -> need to improve typing or either the
optimistic tooling

In this PR we're introducing all the devxp allowing future metadata
incremental universal migration -> this has an impact on all existing
metadata actions handler ( explaining its size )
This PR also introduce workspace agnostic create update actions runner
for both field and object metadata in order to battle test the described
above devxp

Noting that these two metadata are the most complex to handle

Notes:
- A workspace migration is now highly bind to a
`applicationUniversalIdentifier`. Though we don't strictly validate
application scope for the moment

## Next
Migrate both object and field builder to universal comparison

## Universal Actions vs Flat Actions Architecture

### Concept

The migration system uses a two-phase action model:

1. **Universal Actions** - Actions defined using `universalIdentifier`
(stable, portable identifiers like `standardId` + `applicationId`)
2. **Flat Actions** - Actions defined using database `entityId` (UUIDs
specific to a workspace)

### Why This Separation?

- **Universal actions are portable**: They can be serialized, stored,
and replayed across different workspaces
- **Flat actions are executable**: They contain the actual database IDs
needed to perform operations
- **Decoupling**: The builder produces universal actions; the runner
transpiles them to flat actions at execution time

### Transpiler Pattern

Each action handler must implement
`transpileUniversalActionToFlatAction()`:

```typescript
@Injectable()
export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
  'create',
  'fieldMetadata',
) {
  override async transpileUniversalActionToFlatAction(
    context: WorkspaceMigrationActionRunnerArgs<UniversalCreateFieldAction>,
  ): Promise<FlatCreateFieldAction> {
    // Resolve universal identifiers to database IDs
    const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
      flatEntityMaps: allFlatEntityMaps.flatObjectMetadataMaps,
      universalIdentifier: action.objectMetadataUniversalIdentifier,
    });
    
    return {
      type: action.type,
      metadataName: action.metadataName,
      objectMetadataId: flatObjectMetadata.id, // Resolved ID
      flatFieldMetadatas: /* ... transpiled entities ... */,
    };
  }
}
```

### Action Handler Base Class

`BaseWorkspaceMigrationRunnerActionHandlerService<TActionType,
TMetadataName>` provides:

- **`transpileUniversalActionToFlatAction()`** - Abstract method each
handler must implement
- **`transpileUniversalDeleteActionToFlatDeleteAction()`** - Shared
helper for delete actions

## FlatEntityMaps custom properties
Introduced a `TWithCustomMapsProperties` generic parameter to control
whether custom indexing structures are included:

- **`false` (default)**: Returns `FlatEntityMaps<MetadataFlatEntity<T>>`
- used in builder/runner contexts
- **`true`**: Returns the full maps type with custom properties (e.g.,
`byUserWorkspaceIdAndFolderId`) - used in cache contexts

## Create Field Actions Refactor

Refactored create-field actions to support relation field pairs
bundling.

**Problem:** Relation fields (e.g., `Attachment.targetTask` ↔
`Task.attachments`) couldn't resolve each other's IDs during
transpilation because they were in separate actions with independent
`fieldIdByUniversalIdentifier` maps.

**Solution:** 
- Removed `objectMetadataUniversalIdentifier` from
`UniversalCreateFieldAction` and `objectMetadataId` from
`FlatCreateFieldAction` - each field now carries its own
- Runner groups fields by object internally and processes each table
separately
- Split aggregator into two focused utilities:
- `aggregateNonRelationFieldsIntoObjectActions` - merges non-relation
fields into object actions
- `aggregateRelationFieldPairs` - bundles relation pairs with shared
`fieldIdByUniversalIdentifier`
2026-02-02 12:22:38 +00:00
Raphaël BosiandGitHub f0bc9fcb43 [FRONT COMPONENTS] Move to twenty-sdk (#17587)
Move front components from twenty-shared to twenty-sdk
2026-02-02 12:21:53 +00:00
Félix MalfaitandClaude Opus 4.5 b2218cbbf2 fix: add file editing and git tools to Claude allowed_tools
Claude was blocked from writing files, using git/gh, and fetching
web content because only a few Bash patterns were pre-approved.
Add Edit, Write, WebFetch, git, gh, sed, and python3 to the
allowed_tools list.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
2026-02-02 13:30:25 +01:00
Félix MalfaitandClaude Opus 4.5 84deb8067d fix: add missing permissions block to claude-cross-repo job
The claude-code-action needs id-token: write to fetch an OIDC token.
The claude-cross-repo job was missing its permissions block entirely,
causing it to fail with "Could not fetch an OIDC token".

Co-Authored-By: Claude Opus 4.5 <[email protected]>
2026-02-02 13:00:36 +01:00
749bda92e3 feat: lazy dev env setup for Claude CI workflow (#17621)
## Summary
- Move build/env/DB setup out of the GitHub Actions workflow into an
on-demand script (`packages/twenty-utils/setup-dev-env.sh`) that Claude
invokes only when needed
- Add Nx/build cache restore/save steps to speed up repeated runs
- Add `allowed_tools` so Claude can run the setup script and common dev
commands (nx, jest, yarn)
- Add `PG_DATABASE_URL` env var via `settings` for the postgres MCP
server
- Remove unnecessary `actions: read` permission grant
- Document the lazy setup pattern in CLAUDE.md

This makes read-only tasks (code review, architecture questions, docs)
fast by skipping the ~2min build/setup overhead, while still letting
Claude run tests and builds when it needs to.

## Test plan
- [ ] Comment `@claude what is the architecture of the backend?` —
should answer fast without running setup
- [ ] Comment `@claude run the twenty-server unit tests` — should call
setup script first, then run tests
- [ ] Verify cross-repo dispatch still works

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

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 12:43:47 +01:00
Lucas BordeauandGitHub f6e182ac23 Handle SSE event stream reconnection (#17523)
This PR handles SSE event stream edge cases. 

- When a pod restarts, the front clients have to reconnect to SSE
- When the dev server restarts or is hot reloaded, the front client has
to reconnect to SSE
- When redis server restarts or the redis key is cleared for any reason,
the server has to recreate the event stream in redis, this can happen
when navigating for example.
- Log in / log out flow

With this PR we avoid error messages in the front end due to TTL or pod
crash, we implement a resilient way of reconnecting silently.

To avoid DDoSing our servers if pods crash or a full restart of the
cluster is made, we evenly space retry attempts to reconnect from all
the clients, to avoid n clients reconnection at the same time, we use a
random wait time between 0 and a constant max wait time (set to 2 mins
for now). This is the cheapest and most effective solution, clients who
want to force reconnect have to refresh or navigate to another page.

Fixes https://github.com/twentyhq/core-team-issues/issues/2045
2026-02-02 11:16:28 +00:00
82619d9e66 i18n - translations (#17620)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-02-02 12:25:29 +01:00
MarieandGitHub 54cf857a1a Revert "fix: prevent default object re-selection in relation field fo… (#17619)
I suggest to revert [this
PR](https://github.com/twentyhq/twenty/pull/17313) as it had already
been fixed [in this
PR](https://github.com/twentyhq/twenty/pull/17209/changes) (which fixes
more than it says in its title). Issue was tracked by [this
ticket](https://github.com/twentyhq/core-team-issues/issues/2049) not
very explicit - sorry, my fault.
Code added in the PR does not add value
2026-02-02 10:51:11 +00:00
Abdul RahmanandGitHub c9d5f48ecc Fix: Favorite records not showing in sidebar (#17614)
## Problem
Favorite records (`NavigationMenuItems`) were not showing in the sidebar
under Favorites as the BE was returning `null` for
`targetRecordIdentifier`.
## Root cause
The `targetRecordIdentifier` resolver used `context.req`, which does not
have the typed `WorkspaceAuthContext` shape.
## Fix
Use `getWorkspaceAuthContext()` instead of `context.req` so the resolver
receives the typed auth context.
2026-02-02 10:33:50 +00:00
63c5c54a39 fix: force Claude Code to use Opus model (#17618)
## Summary
- Add `--model opus` to `claude_args` in both the direct and cross-repo
Claude jobs

## Test plan
- [ ] Merge and trigger @claude — verify it reports running Opus 4.5

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

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 11:38:01 +01:00
7dc53fee8c feat: add cross-repo Claude dispatch from core-team-issues (#17616)
## Summary
- Add `repository_dispatch` trigger to `claude.yml` so `@claude`
mentions in `twentyhq/core-team-issues` get forwarded here
- New `claude-cross-repo` job: builds a prompt from the dispatch
payload, runs Claude Code against the twenty codebase, and posts a link
back to the source issue
- Switch both jobs to use `claude_code_oauth_token` instead of
`anthropic_api_key`

A companion workflow (`claude-dispatch.yml`) was pushed to
`twentyhq/core-team-issues` to send the dispatches.

## Test plan
- [x] Dispatch workflow in core-team-issues triggers successfully
(tested via issue #2194)
- [ ] After merge, verify `repository_dispatch` triggers the
`claude-cross-repo` job in twenty
- [ ] Verify Claude responds and posts a link back to the source issue

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

Co-authored-by: Claude Opus 4.5 <[email protected]>
2026-02-02 11:31:11 +01:00
4bfa777893 Add Claude Code GitHub Workflow (#17615)
## 🤖 Installing Claude Code GitHub App

This PR adds a GitHub Actions workflow that enables Claude Code
integration in our repository.

### What is Claude Code?

[Claude Code](https://claude.com/claude-code) is an AI coding agent that
can help with:
- Bug fixes and improvements  
- Documentation updates
- Implementing new features
- Code reviews and suggestions
- Writing tests
- And more!

### How it works

Once this PR is merged, we'll be able to interact with Claude by
mentioning @claude in a pull request or issue comment.
Once the workflow is triggered, Claude will analyze the comment and
surrounding context, and execute on the request in a GitHub action.

### Important Notes

- **This workflow won't take effect until this PR is merged**
- **@claude mentions won't work until after the merge is complete**
- The workflow runs automatically whenever Claude is mentioned in PR or
issue comments
- Claude gets access to the entire PR or issue context including files,
diffs, and previous comments

### Security

- Our Anthropic API key is securely stored as a GitHub Actions secret
- Only users with write access to the repository can trigger the
workflow
- All Claude runs are stored in the GitHub Actions run history
- Claude's default tools are limited to reading/writing files and
interacting with our repo by creating comments, branches, and commits.
- We can add more allowed tools by adding them to the workflow file
like:

```
allowed_tools: Bash(npm install),Bash(npm run build),Bash(npm run lint),Bash(npm run test)
```

There's more information in the [Claude Code action
repo](https://github.com/anthropics/claude-code-action).

After merging this PR, let's try mentioning @claude in a comment on any
PR to get started!

---------

Co-authored-by: Claude Sonnet 4.5 <[email protected]>
2026-02-02 09:52:35 +01:00
fb97c40cad [Dashboards] Replace nivo bar chart with custom canvas bar chart (#17441)
before - 


https://github.com/user-attachments/assets/01d1ce73-1732-4516-bde0-d43c1bbcb734

after - 
I got rid of line chart in the after clip -- because now its the line
chart thats more laggy :) -- but now could be easily migrated away from
nivo


https://github.com/user-attachments/assets/430f4697-68cd-47be-b63e-f8df34a2ee0e

stress test - 

before - 



https://github.com/user-attachments/assets/c3d3e05c-943e-48dc-9429-a2ecf41cd4fe



after - 




https://github.com/user-attachments/assets/98b35a43-f918-4a46-9b66-3bc8deabfdb8

---------

Co-authored-by: bosiraphael <[email protected]>
2026-02-02 07:42:20 +00:00
1976ad58c4 i18n - docs translations (#17599)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-31 14:22:36 +01:00
be8629d8f6 i18n - docs translations (#17597)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 22:07:41 +01:00
Charles BochetandGitHub 46d28509b9 Keep simplifying logic functions (#17595)
## Summary

Refactors the `LogicFunctionService` API by consolidating v1 and v2
services:

In metadata-module (presentation layer module)
- **Renamed methods**: `deleteOneLogicFunction` → `destroyOne`,
`updateOneLogicFunction` → `updateOne`, `createOneLogicFunction` →
`createOne`
- **Added duplicate methods**: `duplicateLogicFunction`,
`createLogicFunctionFromExistingLogicFunctionById`
- **Removed soft delete/restore** functionality - only hard delete
(`destroyOne`) is supported

In core-module (lower level module)
- **Moved execution methods** to `LogicFunctionExecutorService` which is
lower level: `executeOneLogicFunction`, `getAvailablePackages`,
`getLogicFunctionSourceCode`
2026-01-30 20:30:52 +01:00
db31b83a86 fix: allow board column tag to shrink so aggregate value remains visible (#17372)
## Summary
When a select option label is long, it was pushing the aggregate value
(e.g., '12.6m') out of view because the tag had `flex-shrink: 0`.

Changed to `min-width: 0` and `overflow: hidden` to allow the tag to
shrink and truncate with ellipsis, keeping the aggregate value visible.

## Changes
- Modified `StyledTag` in
[RecordBoardColumnHeader.tsx](cci:7://file:///root/74/bronze/twenty/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx:0:0-0:0)
to allow shrinking

The
[Tag](cci:1://file:///root/74/bronze/twenty/packages/twenty-ui/src/components/tag/Tag.tsx:100:0-141:2)
component already has built-in text truncation with
`OverflowingTextWithTooltip`, so this change enables that functionality
to work properly.

Fixes #17350
```**

---------

Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-30 18:35:52 +00:00
394b6f5717 i18n - translations (#17594)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 19:43:46 +01:00
a97ca14289 i18n - docs translations (#17593)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 19:28:32 +01:00
Charles BochetandGitHub 4a770eafa1 Rework logic function module (#17588)
core-modules/logic-function/
├── logic-function.module.ts
├── logic-function-executor/
│   ├── logic-function-executor.module.ts
│   ├── commands/
│   │   └── add-packages.command.ts
│   ├── constants/
│   │   └── logic-function-executor.constants.ts
│   ├── factories/
│   │   └── logic-function-module.factory.ts
│   ├── interfaces/
│   │   └── logic-function-executor.interface.ts
│   └── services/
│       └── logic-function-executor.service.ts
├── logic-function-build/
│   ├── logic-function-build.module.ts
│   ├── services/
│   │   └── logic-function-build.service.ts
│   └── utils/
│       └── get-logic-function-base-folder-path.util.ts
├── logic-function-drivers/
│   ├── logic-function-drivers.module.ts
│   ├── constants/
│   │   └── ...
│   ├── drivers/
│   │   ├── disabled.driver.ts
│   │   ├── lambda.driver.ts
│   │   └── local.driver.ts
│   ├── interfaces/
│   │   └── logic-function-executor-driver.interface.ts
│   ├── layers/
│   │   └── ...
│   └── utils/
│       └── ...
├── logic-function-layer/
│   ├── logic-function-layer.module.ts
│   └── services/
│       └── logic-function-layer.service.ts
└── logic-function-trigger/
    ├── logic-function-trigger.module.ts
    ├── jobs/
    │   └── logic-function-trigger.job.ts
    └── triggers/
        ├── cron/
        ├── database-event/
        └── route/
            ├── exceptions/
            ├── services/
            │   └── route-trigger.service.ts
            └── utils/
2026-01-30 19:28:20 +01:00
cb5e5e4622 i18n - translations (#17592)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 19:09:43 +01:00
MarieandGitHub 0001c2e7d0 [Apps] Apps marketplace (first draft) (#17562)
https://github.com/user-attachments/assets/c4e63edb-6e98-44bc-841f-ee110ae712d4

How it works
- `manifest.json` files are now committed when apps are published to our
repo
- to display available apps, from the server, we read into our github
repo, using a pod-scoped cache
- feature flagged
- app installation will be behind permission gate MARKETPLACE_APPS

Limitations and what is yet to develop
- content and settings tabs
- installed apps tab
- app installation
- test and potentially fix reading from .manifest.json once [Reshape
manifest
structure](https://github.com/twentyhq/core-team-issues/issues/2183) is
done. additional work is expected on assets notably. (couldnt properly
do it here as manifest.json will only be committed after this pr)
- we only read in community/ folder for now - we may want tochange that
- the cache is rather artisanal for now and scoped by pod - we may want
to change that
2026-01-30 17:32:32 +00:00
36cf388c81 i18n - translations (#17591)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 18:39:54 +01:00
b8d85e0b26 fix: prevent default object re-selection in relation field form (#17313)
Fixes #17111

### Problem
When creating a Relation field, after deselecting the pre-selected
default object (e.g., Company) and selecting a different object (e.g.,
Opportunity), both objects would end up being selected, showing "2
Objects" instead of just the newly selected one.

### Root Cause
The `initialMorphRelationsObjectMetadataIds` array was being recreated
on every component render, causing react-hook-form's `Controller` to
treat the `defaultValue` prop as a new value and re-apply it after user
changes.

### Solution
1. **Memoized the initial value**: Used `useMemo` to ensure
`initialMorphRelationsObjectMetadataIds` has a stable reference across
renders
2. **Added initialization guard**: Used `useEffect` with a `useRef` flag
to ensure the default value is only set once during component mount
3. **Maintained Controller defaultValue**: Kept the `defaultValue` prop
on the Controller, which now works correctly with the stable memoized
value

### Changes
- `SettingsDataModelFieldRelationForm.tsx`: Added memoization and
initialization guard to prevent default value re-application

---------

Co-authored-by: Etienne <[email protected]>
2026-01-30 17:12:43 +00:00
Thomas TrompetteandGitHub 1fea3f8228 Fix SSE for workflow show page (#17582)
- Add SSE to workflow show page. Listens at workflow versions
- Add a tool for creating trigger
- Ensure step id is not used in params
- Make AI adding step linked to the last node by default



https://github.com/user-attachments/assets/41ee1835-9716-450e-82e8-54e1b63bd1ef
2026-01-30 18:22:07 +01:00
MarieandGitHub 15b053de46 [GroupBy] Fix order by date granularity (#17573)
https://discord.com/channels/1130383047699738754/1466472357496623165/1466472357496623165

before
<img width="1262" height="598" alt="image"
src="https://github.com/user-attachments/assets/e6fb9a13-58c0-408d-b3e9-a486ec04c33c"
/>

after
<img width="670" height="267" alt="image"
src="https://github.com/user-attachments/assets/b4b5ab19-1144-4300-9801-b8220ce928f9"
/>
2026-01-30 16:40:36 +00:00
ebc7125b20 i18n - translations (#17590)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 17:52:54 +01:00
Abdul RahmanandGitHub 51a2a90b0a feat: CommandMenuItem entity and FrontComponent support in command menu (#17555)
https://github.com/user-attachments/assets/6f172ffc-d43d-42fd-a26b-94f591fb767e
2026-01-30 16:35:06 +00:00
6462ff6c5f i18n - docs translations (#17586)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 17:25:52 +01:00
febab2760a i18n - translations (#17585)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 17:17:49 +01:00
9eabfc628a i18n - translations (#17584)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 17:03:58 +01:00
BOHEUSandGitHub 9767c418ee Improve email onboarding step (#17427)
Related to https://github.com/twentyhq/core-team-issues/issues/1477
2026-01-30 16:58:42 +01:00
2bcae523ac Added option to prevent sending requests to twenty-icons (#16723)
Solution for #15891


https://github.com/user-attachments/assets/82ce5c4b-0a78-45a8-8196-413473887820

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-01-30 16:56:22 +01:00
neo773andGitHub cc1ca630fd Workflow Send Email Node Multiple Recipients Support (#17458)
This PR adds support sending emails to multiple recipients

Figma Reference:

https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=88868-88963&t=Ya0csmNlN4xxczvV-11

Demo:


https://github.com/user-attachments/assets/ecaeaaec-fe42-4fb5-96d3-a91d08b30148
2026-01-30 16:54:21 +01:00
Paul RastoinandGitHub 5791bd2943 Unit test back vscode task (#17583)
Jest extension being flaky it's sometimes a pain to run opened test only
2026-01-30 16:48:49 +01:00
martmullandGitHub f46da3eefd Update manifest structure (#17547)
Move all sync entities in an `entities` key. Rename functions to
logicFunctions

```json
{
  application: {
    ...
  },
  entities: {
    objects: [],
    logicFunctions: [],
    ...
  }
}
```
2026-01-30 16:26:45 +01:00
Larron ArmsteadandGitHub bc022f82cb Patch the postgres db url while using an external resource enabling a… (#17431)
…n existing secret and password key
2026-01-30 14:32:23 +00:00
Charles BochetandGitHub 5996d0fc03 Refactor workflow to use new functions (#17552)
Removes the versioning system for logic functions (`latestVersion`,
`publishedVersions`) and simplifies the file storage structure.

### Changes
- Remove `publishOneLogicFunctionOrFail` and publishing logic from
workflow status updates
- Add `createLogicFunctionFromExistingLogicFunction` to duplicate logic
functions when creating draft workflow versions
- Update `createDraftStep` to create a new logic function copy instead
of referencing the same one
- Migrate file storage to v2 endpoints with
`applicationUniversalIdentifier`
- Unify path structure: source files at `source/workflow/{id}/`, built
files at `built-logic-function/workflow/{id}/`
- Store full paths in `sourceHandlerPath` and `builtHandlerPath` entity
fields
2026-01-30 15:42:36 +01:00
Raphaël BosiandGitHub d624652e36 [FRONT COMPONENTS] Build front components from twenty apps for remote dom (#17566)
Modify the front components build to match the expected format for
remote dom execution in a worker.
2026-01-30 14:09:08 +00:00
ffdbb57eaa i18n - translations (#17579)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 15:20:13 +01:00
9c55313590 i18n - translations (#17578)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 15:13:45 +01:00
BOHEUSandGitHub dffb61e437 Add border bottom to display organization plan features (#17422)
Related to https://github.com/twentyhq/core-team-issues/issues/1014
2026-01-30 13:45:26 +00:00
EtienneandGitHub 4b66ae5320 Files Field - Add new controller (#17561)
Add new files field controller to fetch files from FILES field
2026-01-30 13:44:11 +00:00
EtienneandGitHub 9439afde22 Files - Delete command (#17576) 2026-01-30 13:38:30 +00:00
Abdullah.andGitHub 4090837de5 fix: replacement of a substring with itself (#17497)
Resolves [Code Scanning Alert
218](https://github.com/twentyhq/twenty/security/code-scanning/218).
2026-01-30 13:32:17 +00:00
neo773andGitHub 945bedb7c7 handle HTTP 400, 500, 502, 504 in parseMicrosoftMessagesImportError (#17509)
Resolves sentry issue TWENTY-SERVER-D3X
2026-01-30 13:26:34 +00:00
fc833a4afe i18n - translations (#17577)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-30 14:36:56 +01:00
Charles BochetandGitHub 6eebf6f23a Remove versions from logicFunction (#17540)
## Summary

- Remove `latestVersion` and `publishedVersions` columns from
`LogicFunction` entity
- Remove version parameter from logic function execution, build, and
source code retrieval
- Update all related services, DTOs, utilities, and tests
- Add database migration to drop the version columns
2026-01-30 14:30:49 +01:00
Abdullah.andGitHub 077cfeffca fix: lodash has prototype pollution vulnerability in _.unset and _.omit functions (#17551)
Resolves [Dependabot Alert
399](https://github.com/twentyhq/twenty/security/dependabot/399).
2026-01-30 13:04:04 +00:00
4ee9e23ad9 i18n - docs translations (#17549)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-30 13:51:45 +01:00
WeikoandGitHub 446afcbc30 Add standard record page layout (#17567) 2026-01-30 11:40:36 +00:00
Thomas TrompetteGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
f7adf1698e Improve API + set functions in state (#17560)
Prefetch functions and set these in state

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-01-30 09:24:07 +00:00
69c53c7dff i18n - translations (#17568)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-29 19:43:49 +01:00
Paul RastoinandGitHub fbbd8fe967 [REQUIRES_CACHE_FLUSH_FOR_FIELD_AND_OBJECT]FlatFieldMetadata and FlatObjectMetadata required universal (#17557)
# Introduction
In this PR we're migrating both the `field` and `object` metadata to be
using the new `FlatEntityFromV2` that requires all the
`UniversalFlatEntityExtraProperties` to be spread at the flat entity
root.

This means that we have to update all of their flat declaration

This type swap allows to isole a specific entity migration into his own
type scope and avoid to have everything handled at once

```ts
/**
 * Currently under migration but aims to replace FlatEntity afterwards
 */
export type FlatEntityFromV2<
  TEntity,
  TMetadataName extends AllMetadataName | undefined = undefined,
  TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom<
    TEntity,
    TMetadataName
  >,
> = Omit<TInnerFlatEntity, '__universal'> & TInnerFlatEntity['__universal'];

```

## Impact
Both object and field:
- Create input transpilation utils
- from entity to flat tools
- mocks

## Note
Removed from the universal extra properties the jsonb properties that do
not contain a serialized

## Next
Next step is to incrementally make the builder and runner expect
`UniversalFlatEntity` for both of these metadata
This way we will be able to fully migrate an entity e2e typesafely
2026-01-29 18:11:19 +00:00
e91457a47e i18n - translations (#17565)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-29 18:43:36 +01:00
Baptiste DevessierandGitHub 7006c53bd9 Remove redundant label (#17563)
## Before

<img width="3456" height="2234" alt="CleanShot 2026-01-29 at 17 59
55@2x"
src="https://github.com/user-attachments/assets/9225b037-2394-44e7-8513-a4edbf889eb6"
/>

## After

<img width="3456" height="2234" alt="CleanShot 2026-01-29 at 17 59
49@2x"
src="https://github.com/user-attachments/assets/9abdded0-5f17-4ed4-925c-fc6a46b81e87"
/>
2026-01-29 17:16:20 +00:00
b99582c665 Fix React warnings for custom props on styled DOM elements (#17553)
React was warning that custom props like `isExpanded` and `allMatched`
were being passed to DOM elements (SVG icons and divs), which only
accept standard HTML/SVG attributes.

## Changes

Added `shouldForwardProp` configuration to styled components to filter
out custom props before they reach the DOM:

```typescript
import isPropValid from '@emotion/is-prop-valid';

const StyledChevronIcon = styled(IconChevronDown, {
  shouldForwardProp: (prop) =>
    isPropValid(prop) && prop !== 'isExpanded',
})<{ isExpanded: boolean }>`
  transform: ${({ isExpanded }) => isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'};
`;
```

This pattern is already used in other components (e.g.,
`NavigationDrawerItem`, `TableRow`) and ensures custom props are
available for styling logic but don't leak to the underlying DOM
element.

## Files Modified

- `FieldsWidgetSectionContainer.tsx` - `isExpanded` on icon component
- `UnmatchColumnBanner.tsx` - `isExpanded`, `allMatched` on icon, div,
and Banner components

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Devessier <[email protected]>
Co-authored-by: Devessier <[email protected]>
2026-01-29 14:02:16 +00:00
efbbfe5570 i18n - translations (#17558)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-29 15:03:18 +01:00
WeikoandGitHub 7d69000ab7 Update pageLayout* data models for backend recordPageLayout refactor (#17446)
<img width="814" height="356" alt="Screenshot 2026-01-26 at 16 09 27"
src="https://github.com/user-attachments/assets/91d95a1d-cc33-4bf0-a63e-4d1815030983"
/>
2026-01-29 13:39:46 +00:00
EtienneandGitHub 09de762285 Files v2 - FILES field update/create in common API (#17400)
## FILES Field update interface

```
mutation {
  updateCompany(
    id: "company-uuid"
    data: {
      filesfield: [
          { fileId: "new-file-uuid", label: "new-document.pdf" }
        ]
    }
  ) {
    id
    filesfield {
      fileId
      label
      extension
    }
  }
}
```
2026-01-29 13:33:26 +00:00
3dfb5ffc02 i18n - translations (#17546)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-29 12:40:34 +01:00
Paul RastoinandGitHub 9b7bd1a6aa Refactor delete objectMetadata action type and handler to be workspace agnostic (#17530)
# Introduction
Refactoring the delete and update field to use cached
flatEntitty.__universal.objectMetadataUniversalIdentifier to infer
related object
2026-01-29 11:19:19 +00:00
Baptiste DevessierandGitHub b15d092abd Record page layout edition frontend (#17519)
Hidden behind a new feature flag:
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED`


https://github.com/user-attachments/assets/112f5a8d-0dd0-4b9f-8825-9980db35fcbd
2026-01-29 11:13:09 +00:00
Paul RastoinandGitHub 97a37604bd Fix UpdateTaskOnDeleteActionCommand order and simplify (#17527)
Already introduced in 1.16.5 for self host
2026-01-29 10:55:04 +00:00
245111dea9 i18n - translations (#17545)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-29 12:12:40 +01:00
c9a826aba1 i18n - docs translations (#17542)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-29 12:12:27 +01:00
MarieandGitHub 06e803d18b [Fix] Various typeError fixes (#17508)
Fixes
[sentry](https://twenty-v7.sentry.io/issues/6539621673/?environment=prod&environment=prod-eu&project=4507072563183616&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20timesSeen%3A%3E3%20TypeError&referrer=issue-stream&sort=date):
fieldValue.map on potentially null fieldValue

Fixes
[sentry](https://twenty-v7.sentry.io/issues/6996079360/?environment=prod&environment=prod-eu&project=4507072563183616&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20timesSeen%3A%3E3%20TypeError&referrer=issue-stream&sort=date):
note.id on potentially null note due to recent useActivities change

Fixes sentry
([1](https://twenty-v7.sentry.io/issues/6707703562/?environment=prod&environment=prod-eu&project=4507072563183616&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20timesSeen%3A%3E3%20TypeError&referrer=issue-stream&sort=date),
[2](https://twenty-v7.sentry.io/issues/6707703563/?environment=prod&environment=prod-eu&project=4507072563183616&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20timesSeen%3A%3E3%20TypeError&referrer=issue-stream&sort=date)
- same issue): selectableListHotKey issue

Fixes
[sentry](https://twenty-v7.sentry.io/issues/7087281049/?environment=prod&environment=prod-eu&project=4507072563183616&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20timesSeen%3A%3E3%20TypeError&referrer=issue-stream&sort=date):
issue when reordering fields (putting field in last position)
2026-01-29 10:50:28 +00:00
Thomas TrompetteGitHubcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
3ad7a9ac94 App logic function as step (#17525)
- Add logic function as step
- Rename previous one as Code

<img width="494" height="573" alt="Capture d’écran 2026-01-28 à 16 15
29"
src="https://github.com/user-attachments/assets/82f7e720-20da-4926-b5bc-94a33cd1f53a"
/>
<img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15
39"
src="https://github.com/user-attachments/assets/a0444ae9-8c50-418d-8c5f-68c4da08fce7"
/>
<img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15
58"
src="https://github.com/user-attachments/assets/3025db09-4e59-421e-8dc5-f3a7a7326554"
/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-01-29 11:59:24 +01:00
EtienneandGitHub 22c7e207e3 File storage refactor - Switch to applicationUniversalIdentifier (#17541) 2026-01-29 10:39:19 +00:00
PraiseGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>Etienne
3188fb5295 fix: standardize billing price display to integers when necessary (#17445)
**Fixes #17420** 

### Problem
The billing/pricing display showed inconsistencies:
- "Get Started" / trial signup flow: simple `$9` (no decimals)
- Pricing page: `$9.00` (with decimals)

For yearly plans (with discount), the effective monthly price might not
always be an integer, leading to messy floats in UI (e.g., 7.5 showing
as 7.499999 or inconsistent formatting).

### Solution
Added a helper function `formatYearlyPriceIfNeccessary` that:
- Only applies to yearly subscriptions (`SubscriptionInterval.Year`)
- Calculates effective monthly price (`price / 12`)
- Returns an integer if the result is whole (`Number.isInteger`)
- Otherwise formats to exactly 2 decimal places (`toFixed(2)` → Number)

This ensures clean, consistent display:
- Whole numbers: no decimals (e.g., $9 → 9)
- Fractional: precise 2 decimals (e.g., $81 yearly → 6.75)

### Changes
- Added `formatYearlyPriceIfNeccessary` function (likely in a
utils/pricing file — specify the file path if you know it, e.g.
`src/utils/pricing.ts`)
- Applied it where yearly effective prices are rendered (e.g., in
BillingPlan component, Pricing page, Signup flow — list the
files/components you changed)

### Before / After
- Before (yearly example with discount): Might show 7.5 or 7.499999
- After: Always 7.5 (or 7 if integer)

### Testing
- Ran locally with `yarn dev` 
- Checked signup flow and pricing page: prices now consistent and clean
- Verified non-yearly plans unchanged

Let me know if there's a preferred formatting style (e.g., always show
.00, or use Intl.NumberFormat) or if this should be applied in more
places!

Thanks for the great project, my first contribution btw

Closes #17420

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Etienne <[email protected]>
2026-01-29 09:39:14 +00:00
63bf46703d i18n - translations (#17538)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-29 10:26:32 +01:00
martmullandGitHub 3412992e99 2162 Add asset watcher in twenty-sdk dev mode (#17513)
- assets are pushed in .twenty/output
- assets are uploaded in FileFolder.Assets
- not handled yet by the sync-manifest endpoint
2026-01-29 09:08:44 +00:00
Eniola OsabiyaGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>Etienne
1cc08b84c4 feat: Add bulk input mode for select options #5539 (#17459)
- Implemented bulk input mode in SettingsDataModelFieldSelectForm to
allow users to input multiple options at once.
- Added convertBulkTextToOptions and convertOptionsToBulkText utility
functions for handling bulk text conversion.
- Created tests for both conversion functions to ensure correct
functionality.

Screenshot:
<img width="601" height="752" alt="image"
src="https://github.com/user-attachments/assets/95932860-090e-4743-9bd9-7aa6747ad04f"
/>
<img width="543" height="362" alt="image"
src="https://github.com/user-attachments/assets/84490808-2e0f-4126-951e-167b35f1b0d0"
/>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Etienne <[email protected]>
2026-01-29 08:49:38 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9ee6917c86 Bump psl from 1.9.0 to 1.15.0 (#17535)
Bumps [psl](https://github.com/lupomontero/psl) from 1.9.0 to 1.15.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/lupomontero/psl/releases">psl's
releases</a>.</em></p>
<blockquote>
<h2>v1.15.0</h2>
<h2> Highlights</h2>
<h3>🆙 Updates public suffix list</h3>
<p>Updates rules to up to <a
href="https://github.com/publicsuffix/list/tree/70df5fb64d3337f49a1cb5e2e9d3bf3aa7a20f0f">publicsuffix/list#70df5fb</a>
(2 Dic 2024).</p>
<h2>Changelog</h2>
<ul>
<li>67e21de chore(dist): Updates dist files</li>
<li>4e073c0 doc(readme): Fix CDN URLs in readme</li>
<li>a3eb7aa refactor(index): Replaces var with let/const and cleans up
style. Closes <a
href="https://redirect.github.com/lupomontero/psl/issues/329">#329</a></li>
<li>4e1bba4 chore(dist): Updates rules and dist files</li>
<li>9d1afc0 chore(deps): Updates dev dependencies</li>
<li>0053742 doc(security): Adds security policy</li>
<li>b7d4290 chore(funding): Adds funding URL to package.json</li>
<li>81d2864 chore(funding): Create FUNDING.yml</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/lupomontero/psl/compare/v1.14.0...v1.15.0">https://github.com/lupomontero/psl/compare/v1.14.0...v1.15.0</a></p>
<h2>v1.14.0</h2>
<h2> Highlights</h2>
<h3> Over 100x performance improvement</h3>
<p>This release finally includes performance improvements originally
proposed by <a href="https://github.com/gugu"><code>@​gugu</code></a> in
<a
href="https://redirect.github.com/lupomontero/psl/issues/301">#301</a>
and <a
href="https://redirect.github.com/lupomontero/psl/issues/302">#302</a>,
which were finally added in <a
href="https://redirect.github.com/lupomontero/psl/issues/334">#334</a>
in collaboration with <a
href="https://github.com/lupomontero"><code>@​lupomontero</code></a> and
<a href="https://github.com/mfdebian"><code>@​mfdebian</code></a>.</p>
<p>The change basically switches from an <code>Array</code> to a
<code>Map</code> to store the rules (the parsed public suffic list)
indexed by <em>punySuffix</em> so we no longer need to iterate over
thousands of items to find a match every time.</p>
<h3>🔧 Allow underscores in domain fragments</h3>
<p>Underscores (<code>_</code>), aka <em>low dashes</em>, are now
allowed in domain names. This had been raised a few times in issues like
<a href="https://redirect.github.com/lupomontero/psl/issues/26">#26</a>
and <a
href="https://redirect.github.com/lupomontero/psl/issues/185">#185</a>
and fixes had been proposed by <a
href="https://github.com/taoyuan"><code>@​taoyuan</code></a> in <a
href="https://redirect.github.com/lupomontero/psl/issues/46">#46</a>
(the one that got merged) and <a
href="https://github.com/tasinet"><code>@​tasinet</code></a> in <a
href="https://redirect.github.com/lupomontero/psl/issues/286">#286</a>.</p>
<h3>🆙 Updates public suffix list</h3>
<p>Updates rules to up to <a
href="https://github.com/publicsuffix/list/tree/03c6a36304d4de698aff16f6fa33b9371af58786">publicsuffix/list#03c6a36</a>
(28 Nov 2024).</p>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gugu"><code>@​gugu</code></a> made their
first contribution in 23265fd97bede836f91957846b2ee0fb0f705390 (original
PR <a
href="https://redirect.github.com/lupomontero/psl/pull/302">lupomontero/psl#302</a>
was closed but changes were added in <a
href="https://redirect.github.com/lupomontero/psl/pull/334">lupomontero/psl#334</a>)</li>
<li><a href="https://github.com/taoyuan"><code>@​taoyuan</code></a> made
their first contribution in <a
href="https://redirect.github.com/lupomontero/psl/pull/46">lupomontero/psl#46</a></li>
</ul>
<h2>Changelog</h2>
<ul>
<li>06d9f02 chore(dist): Updates dist files</li>
<li><code>publicsuffix/list#03</code></li>
<li>57214ec chore(deps): Updates depencies</li>
<li>18a6d33 doc(readme): Updates install info and tested Node.js
versions</li>
<li>1fd3665 chore(dist): Updates dist files</li>
<li>a096d29 chore(index): Removes unused functions internals.reverse and
internals.endsWith</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/lupomontero/psl/commit/22b64785690b9bacb4066c849b32d956fa55360e"><code>22b6478</code></a>
chore(release): Bump to 1.15.0</li>
<li><a
href="https://github.com/lupomontero/psl/commit/67e21dee319859edd7610e613a8cde596d8ca6eb"><code>67e21de</code></a>
chore(dist): Updates dist files</li>
<li><a
href="https://github.com/lupomontero/psl/commit/4e073c06f77ee4f79bf80e455ce6092bdb213f19"><code>4e073c0</code></a>
doc(readme): Fix CDN URLs in readme</li>
<li><a
href="https://github.com/lupomontero/psl/commit/a3eb7aa00d7b567b4bf49efc8c10b57685a6353a"><code>a3eb7aa</code></a>
refactor(index): Replaces var with let/const and cleans up style</li>
<li><a
href="https://github.com/lupomontero/psl/commit/4e1bba4900dc8ff5d95acc4bdc10b504ad7a42e8"><code>4e1bba4</code></a>
chore(dist): Updates rules and dist files</li>
<li><a
href="https://github.com/lupomontero/psl/commit/9d1afc01b226755a081a3cc387a323b128fccb6c"><code>9d1afc0</code></a>
chore(deps): Updates dev dependencies</li>
<li><a
href="https://github.com/lupomontero/psl/commit/0053742017f5fe54e2e06caff122a673d1507ed7"><code>0053742</code></a>
doc(security): Adds security policy</li>
<li><a
href="https://github.com/lupomontero/psl/commit/b7d429043d581d8e5566a1b7fcbb4fe7605a15df"><code>b7d4290</code></a>
chore(funding): Adds funding URL to package.json</li>
<li><a
href="https://github.com/lupomontero/psl/commit/81d28643f3e3ca487fa7e0c9b584dec837bdbe43"><code>81d2864</code></a>
chore(funding): Create FUNDING.yml</li>
<li><a
href="https://github.com/lupomontero/psl/commit/168a5a166f20b795effafa396fb892e78243a387"><code>168a5a1</code></a>
chore(release): Bump to 1.14.0</li>
<li>Additional commits viewable in <a
href="https://github.com/lupomontero/psl/compare/v1.9.0...v1.15.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=psl&package-manager=npm_and_yarn&previous-version=1.9.0&new-version=1.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 10:00:37 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
07c48c076b Bump @prettier/sync from 0.5.3 to 0.5.5 (#17536)
Bumps
[@prettier/sync](https://github.com/prettier/prettier-synchronized) from
0.5.3 to 0.5.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier-synchronized/releases"><code>@​prettier/sync</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v0.5.5</h2>
<p>Changelog:</p>
<ul>
<li>Update <code>make-synchronized</code> to v0.4 (85747ae)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/prettier-synchronized/compare/v0.5.4...v0.5.5">https://github.com/prettier/prettier-synchronized/compare/v0.5.4...v0.5.5</a></p>
<h2>v0.5.4</h2>
<p>Changelog:</p>
<ul>
<li>Update <code>make-synchronized</code> to v0.3 (18c1f1b)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/prettier-synchronized/compare/v0.5.3...v0.5.4">https://github.com/prettier/prettier-synchronized/compare/v0.5.3...v0.5.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/919140dd6f7cfde6057b38b1a7901c78df018916"><code>919140d</code></a>
Release 0.5.5</li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/85747ae473ad44c53446172f6a300fdb3561e129"><code>85747ae</code></a>
Update <code>make-synchronized</code></li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/1038406e9297fdfc4ffc2e5710894752c80296ef"><code>1038406</code></a>
Update badge</li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/a28e6cbdd4221137cc659bff4b88f35e2a86bc8f"><code>a28e6cb</code></a>
Release 0.5.4</li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/fcdeb9439623661cd5cd4b3349a9fca499f81764"><code>fcdeb94</code></a>
Linting</li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/18c1f1bf509d072524e938019b4200d353c648bc"><code>18c1f1b</code></a>
Update <code>make-synchronized</code> to v0.3</li>
<li><a
href="https://github.com/prettier/prettier-synchronized/commit/91b579f18dda3bb41b631300466316eb596c895c"><code>91b579f</code></a>
Add one more example</li>
<li>See full diff in <a
href="https://github.com/prettier/prettier-synchronized/compare/v0.5.3...v0.5.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@prettier/sync&package-manager=npm_and_yarn&previous-version=0.5.3&new-version=0.5.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-29 09:59:53 +01:00
WeikoandGitHub fc908e9d87 Refactor WorkspaceAuthContext to use discriminated union types (#17491)
## Context
The previous WorkspaceAuthContext was a single interface with many
optional fields, making it unclear which fields are available in
different authentication scenarios. This made the code harder to reason
about and required runtime checks scattered throughout the codebase.

## Changes
- Introduced a discriminated union type for WorkspaceAuthContext with
four specific variants:
-> UserWorkspaceAuthContext - for authenticated users
-> ApiKeyWorkspaceAuthContext - for API key authentication
-> ApplicationWorkspaceAuthContext - for application-based auth
-> SystemWorkspaceAuthContext - for system/internal operations
- Added type guard functions (isUserAuthContext, isApiKeyAuthContext,
etc.) for safe type narrowing
- Added builder utilities (buildUserAuthContext, buildApiKeyAuthContext,
etc.) to construct each context variant with proper type safety
- Refactored WorkspaceAuthContextMiddleware to use the new builders
instead of constructing a loosely-typed object
- Moved the type definition from twenty-orm/interfaces/ to
core-modules/auth/types/ for better organization
- Updated all consumers across query runners, tool providers, and
modules to use the new type location


## Notes
- I had to query User and WorkspaceMember in some parts of tool module
that were expecting userWorkspaceId but not the rest of
UserWorkspaceAuthContext (that should be required with the new proper
type otherwise it would break a lot of logic and mostly permissions with
the newly added RLS -> This is what we expect from
UserWorkspaceAuthContext and how it's done in the "normal" path in HTTP
middleware)
- WorkspaceMember is in the cache already but ideally we should move
User (And Workspace?) in the cache as well to avoid querying the DB
after each request (this is also valid for HTTP middleware when we
hydrate the Request object btw)
2026-01-28 17:50:42 +00:00
59f7582463 Fix: time format issue in datepicker mask (#16922)
Fixes: #16872 

## Summary

Fixes the DateTimePicker to respect user's 12H/24H time format
preference. Previously, the time display at the top of the
DateTimePicker always showed 24-hour format (e.g., "14:30") regardless
of the user's time format setting.

## Screenshots

_After:_ Time respects user preference, showing 12H with AM/PM (e.g.,
"03:28 PM") or 24H format
<img width="357" height="491" alt="image"
src="https://github.com/user-attachments/assets/4a03f195-a52b-4f74-84ba-c5eb2fc6c8c1"
/>


## Implementation Details

### Changes Made:

1. **TimeMask.ts** - Added `getTimeMask()` to return appropriate mask
pattern based on time format
2. **TimeBlocks.ts** - Restored as constant file per linting
requirements
3. **getTimeBlocks.ts** (new) - Dynamic block generator supporting 12H
(1-12 + AM/PM) and 24H (0-23)
4. **DateTimeBlocks.ts** - Simplified to static constant
5. **getDateTimeMask.ts** - Updated to accept and use `timeFormat`
parameter
6. **DateTimePickerInput.tsx** - Dynamically generates mask and blocks
based on user's time format preference, increased input width to
accommodate AM/PM
7. **useParseJSDateToIMaskDateTimeInputString.ts** - Formats dates with
correct time pattern
8. **useParseDateTimeInputStringToJSDate.ts** - Parses dates with
correct time pattern
9. **date-utils.ts** - Added `getTimePattern()` helper (returns 'hh:mm
a' for 12H, 'HH:mm' for 24H)
10. **parseDateTimeToString.ts** - Added optional `timeFormat` parameter
for consistency

### Technical Approach:

- Leverages existing `useDateTimeFormat()` hook to get user's
`timeFormat` preference
- Supports `TimeFormat.SYSTEM`, `TimeFormat.HOUR_12`, and
`TimeFormat.HOUR_24`
- Uses IMask blocks with appropriate ranges: 1-12 for 12H, 0-23 for 24H
- Adds 'aa' (AM/PM) block for 12-hour format with enum validation

---------

Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-28 17:43:48 +00:00
Thomas TrompetteandGitHub 9672ca09a2 [Bug] Fix broken variables for database event (#17526)
Fix https://github.com/twentyhq/twenty/issues/17524

Recent update meant to support variable with spaces and dots broken the
database event variables. Inserting a variable `My Name` will be
inserted as `{{trigger.[My Name]}}`

For example, for a database event, we send `trigger.properties.after.id`
instead of `id`.
With the new code, we will consider `properties.after.id` as a variable
to wrap in `[]`, giving `trigger.[properties.after.id]` which cannot be
resolve.

Let's only support variable with spaces. Removing the logic to wrap
variable with dots.
2026-01-28 17:05:59 +00:00
3e9dda6761 i18n - translations (#17528)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-28 18:16:49 +01:00
5c1c998b97 Front component rendering (#17482)
Render front components in a widget.



https://github.com/user-attachments/assets/1ae130a4-070d-498e-88f7-80cee847e2fa

---------

Co-authored-by: Charles Bochet <[email protected]>
2026-01-28 16:52:10 +00:00
Paul RastoinandGitHub fe9d6f34ff [REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type

## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do

## Example
Also strictly type
```ts
    "bbb019ea-6205-498c-aea5-67bc53bce8a9": {
      "workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
      "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
      "applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
      "id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
      "pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
      "title": "Deals by Company",
      "type": "GRAPH",
      "objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
      "gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
      "configuration": {
        "color": "orange",
        "orderBy": "FIELD_ASC",
        "timezone": "UTC",
        "displayLegend": true,
        "displayDataLabel": false,
        "showCenterMetric": true,
        "configurationType": "PIE_CHART",
        "firstDayOfTheWeek": 0,
        "aggregateOperation": "COUNT",
        "groupBySubFieldName": "name",
        "groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
        "aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
      },
      "createdAt": "2026-01-28T14:08:52.140Z",
      "updatedAt": "2026-01-28T14:08:52.140Z",
      "deletedAt": null,
      "__universal": {
        "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
        "applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
        "pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
        "objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
        "gridPosition": {
          "row": 0,
          "column": 6,
          "rowSpan": 6,
          "columnSpan": 6
        },
        "configuration": {
          "color": "orange",
          "orderBy": "FIELD_ASC",
          "timezone": "UTC",
          "displayLegend": true,
          "displayDataLabel": false,
          "showCenterMetric": true,
          "configurationType": "PIE_CHART",
          "firstDayOfTheWeek": 0,
          "aggregateOperation": "COUNT",
          "groupBySubFieldName": "name",
          "aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
          "groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
        }
      }
    },
```
2026-01-28 16:46:34 +00:00
81eaee81a8 Fix AI chat infinite loading shimmer on empty workspace (#17521)
## Summary

Fixes an issue where the AI chat would show loading shimmers
indefinitely when opened on a workspace with no conversation history.

**Root cause:** The `isLoading` state in `useAgentChat` included
`!currentAIChatThread`. On workspaces with no chat threads,
`currentAIChatThread` remained `null`, causing `isLoading` to be
permanently `true`.

**Changes:**
- Remove `!currentAIChatThread` from `isLoading` calculation in
`useAgentChat` - this state should only reflect streaming/file selection
status
- Auto-create a chat thread in `useAgentChatData` when the threads query
returns empty, ensuring a valid thread exists for the `useChat` hook to
initialize properly
- Add primary font color to empty state title for better visibility

## Test plan

1. Create a new workspace or use a workspace with no AI chat history
2. Open the AI chat
3. Verify the empty state shows (not infinite loading shimmer)
4. Send a message and verify it works correctly

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <[email protected]>
2026-01-28 17:36:36 +01:00
15de09caeb Display Fields widgets title (#17518)
<img width="3456" height="2160" alt="CleanShot 2026-01-28 at 15 40
42@2x"
src="https://github.com/user-attachments/assets/43e39082-b82e-4802-bab6-897016132d44"
/>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Devessier <[email protected]>
2026-01-28 15:09:19 +00:00
f7908de4c1 i18n - docs translations (#17502)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-28 14:17:07 +01:00
Baptiste DevessierandGitHub f33b8e12f4 Fix error "No widget found in canvas layout" (#17512)
Fix https://github.com/twentyhq/twenty/issues/17507

The canvas layout mode is expected to render a single widget.
Previously, we threw an error when trying to render a canvas layout with
no widgets at all. This worked fine when we immediately rendered a
widget that used a single-widget canvas layout.

However, the active tab ID is shared across all page layouts with the
same ID, which doesn’t work well with conditional display. The available
tabs may depend on conditions such as the device displaying the page.
For example, when displaying a Note on the show page, the Note widget
appears on a separate tab. On mobile and in the side panel, however, it
is moved to the Home tab.

After that, if the user opens a Note in the side panel, the default
active tab might be the Note tab, which uses a _canvas_ layout mode and
would have no widgets at all when rendered in the side panel. This leads
to the error mentioned above.

The simple workaround is to wait one tick. An `Effect` component then
detects that the active tab doesn’t exist in the list of allowed tabs
and switches to another tab by default.

This feels more like a workaround than a proper solution, but I prefer
to fix it this way for now. We can later revisit this and design a
better separation for the selected tab ID state.

## Before


https://github.com/user-attachments/assets/500e332c-1623-48e5-b69b-5a4fa4e5e28d

## After


https://github.com/user-attachments/assets/b39dcf96-1852-42e4-a8cc-a79bf9036579
2026-01-28 11:03:42 +00:00
Paul RastoinandGitHub a1ff13ee6a self host 1.16 logs debug (#17510) 2026-01-28 10:43:23 +00:00
2ac8c9e38b i18n - translations (#17501)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-28 01:50:22 +01:00
9fbd05ead7 i18n - docs translations (#17500)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-28 01:46:56 +01:00
neo773andGitHub 7d64ee85ac Clean up and enhance logging for messaging and calendar (#17498)
This PR reduces noise to signal ratio for messaging and calendar logging
in production
Impact would be faster queries and debugging
2026-01-28 01:46:25 +01:00
Charles BochetandGitHub da6f1bbef3 Rename serverlessFunction to logicFunction (#17494)
## Summary

Rename "Serverless Function" to "Logic Function" across the codebase for
clearer naming.

### Environment Variable Changes

| Old | New |
|-----|-----|
| `SERVERLESS_TYPE` | `LOGIC_FUNCTION_TYPE` |
| `SERVERLESS_LAMBDA_REGION` | `LOGIC_FUNCTION_LAMBDA_REGION` |
| `SERVERLESS_LAMBDA_ROLE` | `LOGIC_FUNCTION_LAMBDA_ROLE` |
| `SERVERLESS_LAMBDA_SUBHOSTING_URL` |
`LOGIC_FUNCTION_LAMBDA_SUBHOSTING_URL` |
| `SERVERLESS_LAMBDA_ACCESS_KEY_ID` |
`LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID` |
| `SERVERLESS_LAMBDA_SECRET_ACCESS_KEY` |
`LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY` |

### Breaking Changes

- Environment variables must be updated in production deployments
- Database migration renames `serverlessFunction` → `logicFunction`
tables
2026-01-28 01:42:19 +01:00
59d123d2b1 i18n - docs translations (#17499)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-27 23:33:44 +01:00
Lucas BordeauandGitHub 2ffe2d8aa6 Add delete and restore event handling for table and board (#17489)
This PR adds what is required to handle soft-delete and restore SSE
events in virtualized table and board.

Restore is not handled in board for now as it requires respecting sorts
when inserting record ids.

Since virtualized table is refetching small chunks, we just refetch for
now.

The long term goal is to handle event handling without refetching in all
main components, and also handle SSE events that have the same origin
that the current tab. But for now we implement what is easily doable.

# QA

Delete between table and board (delete only) :


https://github.com/user-attachments/assets/715dd44a-007a-44ab-bf49-5ef039cd57c3

Delete and restore between table and table : 


https://github.com/user-attachments/assets/f2122519-e969-491f-b71c-018d0f85bd86
2026-01-27 21:09:20 +00:00
martmullandGitHub 6b38686d87 Fix internal app (#17496)
as title
2026-01-27 20:50:19 +00:00
martmullandGitHub b9586769b9 2081 extensibility publish cli tools and update doc with recent changes (#17495)
- increase to 0.4.0
- update READMEs and doc
2026-01-27 20:49:33 +00:00
Abdullah.andGitHub 8fe02c5c19 fix: use universalIdentifier to identify the field in migrate-attachment-to-morph-relations (#17444)
Made changes to the command based on suggestions
[here](https://github.com/twentyhq/twenty/pull/17381).
2026-01-27 20:27:23 +00:00
neo773andGitHub 8fb8b5d2f1 fix message channels stuck in ONGOING (#17492)
`markAsMessagesListFetchOngoing()` was missing `syncStageStartedAt`
causing stuck channels to be never recovered.

Regression was caused by commit
[68a9ef57f0](https://github.com/twentyhq/twenty/commit/68a9ef57f0)
2026-01-27 20:23:29 +00:00
51c1fa0137 i18n - translations (#17493)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-27 21:01:08 +01:00
Charles BochetandGitHub a4499d21bb Migrate cron, databaseEventTrigger, httpRoute triggers to serverless functions (#17488)
## Summary

Migrates trigger entities (`CronTriggerEntity`,
`DatabaseEventTriggerEntity`, `RouteTriggerEntity`) into
`ServerlessFunctionEntity` by storing trigger settings as JSONB columns
directly on the serverless function. This simplifies the architecture
since these relationships were effectively one-to-one.

## Changes

### Schema Changes
- Added three new nullable JSONB columns to `ServerlessFunctionEntity`:
  - `cronTriggerSettings` - stores cron pattern
- `databaseEventTriggerSettings` - stores event name and updated fields
filter
- `httpRouteTriggerSettings` - stores path, HTTP method, auth
requirements, and forwarded headers

### Core Logic Updates
- `CronTriggerCronJob` - now queries `ServerlessFunctionEntity` directly
instead of `CronTriggerEntity`
- `CallDatabaseEventTriggerJobsJob` - now queries
`ServerlessFunctionEntity` directly
- `RouteTriggerService` - now queries `ServerlessFunctionEntity`
directly
- `ApplicationSyncService` - extracts trigger settings from manifest and
writes to serverless function
2026-01-27 20:54:09 +01:00
Paul RastoinandGitHub e1f92bd951 Nested serialized relation property (#17490)
## Introduction
Handling nested serializedRelation references mapping
Removed the brand signature omit for the moment
I want to determine if it's really problematic later in the devx 
## Motivation

```ts
@ObjectType('RatioAggregateConfig')
export class RatioAggregateConfigDTO {
  @Field(() => UUIDScalarType)
  @IsUUID()
  @IsNotEmpty()
  fieldMetadataId: SerializedRelation;

  @Field(() => String)
  @IsString()
  @IsNotEmpty()
  optionValue: string;
}


@ObjectType('AggregateChartConfiguration')
export class AggregateChartConfigurationDTO
  implements PageLayoutWidgetConfigurationBase
{
// ...
  @Field(() => RatioAggregateConfigDTO, { nullable: true })
  @ValidateNested()
  @Type(() => RatioAggregateConfigDTO)
  @IsOptional()
  ratioAggregateConfig?: RatioAggregateConfigDTO;
}

```

Blocking https://github.com/twentyhq/twenty/pull/17452
2026-01-27 18:11:57 +00:00
9162e62664 i18n - docs translations (#17481)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-27 18:41:52 +01:00
WeikoandGitHub 2daebc6d0f Add WorkspaceAuthContextMiddleware (#17487)
## Context
Introduces a middleware that automatically sets the workspace auth
context in AsyncLocalStorage for HTTP requests, making it available
throughout the request lifecycle without explicit parameter passing.

The motivation behind this change is to reduce boilerplate and simplify
the developer experience when working with workspace data in HTTP
request handlers.

The Problem (Before)
Every HTTP request handler that needed to access workspace data had to:
- Extract auth-related info from decorators (@AuthWorkspace(),
@AuthUserWorkspaceId(), etc.) in controller/resolver and pass down to
services
- Build or pass the authContext explicitly (sometimes with type
assertion which was flaky)
Then call executeInWorkspaceContext(authContext, async () => { ... })

## Changes
- Add WorkspaceAuthContextMiddleware that extracts auth context from the
request and stores it in AsyncLocalStorage
- Register middleware for GraphQL, metadata, and REST routes (runs after
hydration middlewares)
- Simplify executeInWorkspaceContext signature: fn is now the first
parameter, authContext is optional second
- If authContext is not provided, it's automatically retrieved from the
storage (set by middleware)
- Update all callers (~120 files) to use the new parameter order


- Fixes a bug in search where system auth context was used, bypassing
RLS feature.
2026-01-27 17:24:51 +00:00
MarieandGitHub dd98146c99 [Fix] display current object label in morph relation picker after rename 2/2 (#17484)
Following https://github.com/twentyhq/twenty/pull/17209

The goal is to display the updated label. For
SingleRecordPickerMenuItemsWithSearch, we did it in two parts to avoid
the breaking change by calling `objectLabelSingular` after its addition
has been deployed to prod
<img width="410" height="295" alt="image"
src="https://github.com/user-attachments/assets/d6014391-b943-4fdb-a15f-e62717463d74"
/>
2026-01-27 16:26:14 +00:00
Charles BochetandGitHub ddf1c43bb1 Backfill webhooks universal and application (#17486) 2026-01-27 17:20:08 +01:00
MarieandGitHub 7e3d9cd85a Encrypt/decrypt app secret variables (#17394)
Closes https://github.com/twentyhq/core-team-issues/issues/1724
From PR https://github.com/twentyhq/twenty/pull/15283, followed same
implementation
- Introduce EnvironmentModule to provide type safety for env-only
variables
- Encrypt secret variables
- When querying app secret variable, display up to first 5 characters
(depending on secret length) then `******`
2026-01-27 15:59:37 +00:00
a913ac7452 i18n - translations (#17485)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-27 16:51:07 +01:00
martmullandGitHub f4ca69a474 Implement dev mode nice UI (#17471)
Implement a nice terminal UI for dev mode using INK

<img width="1512" height="721" alt="image"
src="https://github.com/user-attachments/assets/79a71f37-1b31-4761-9e8d-718ef029ceb8"
/>
2026-01-27 15:34:28 +00:00
Charles BochetandGitHub bc7791871f Introduce webhook v2 (#17456)
Migrate webhook to v2 entity
2026-01-27 15:32:33 +00:00
nitinandGitHub 27e5b9797b [Dashboards] Fix page layout navigation edit mode sync (#17478)
navigating away - 

before - 


https://github.com/user-attachments/assets/ffd4c592-cf6d-403c-8aa2-9f55692fac65


after - 


https://github.com/user-attachments/assets/fc298e20-21ac-4f2f-b52a-8ecfdfd4eb38

new dashbaord - 

before - 


https://github.com/user-attachments/assets/f9a2d7ab-6b5b-41c3-b993-33745ab61683


after -


https://github.com/user-attachments/assets/cad68c35-6dfa-4fa2-ab9b-890900be3444




empty page layout - 

before - 



https://github.com/user-attachments/assets/27a5b61b-cd0c-4786-a461-e28f1e348822



after - 


https://github.com/user-attachments/assets/213e5242-493b-45de-a622-cf1463bb6380
2026-01-27 15:25:41 +00:00
Lucas BordeauandGitHub 3a43e714bb Patch formatResult to pass Date object through (#17483)
This PR reverts the change that was made to turn `Date` objects into ISO
string, because right now there are too many places in the code that use
both, either Date or ISO string from an entity returned by the querying
layer.

Unifying everything with ISO string is clearly the long term goal, but
right now it is too difficult, we should first refactor each part to ISO
string, then at the end remove Date from the codebase.
2026-01-27 15:19:41 +00:00
EtienneandGitHub f532a51467 UpdateTaskOnDeleteActionCommand - Add logs (#17479) 2026-01-27 14:03:12 +00:00
Baptiste DevessierandGitHub 856dc8be56 Activate Record Page Layouts flag by default (#17467) 2026-01-27 13:30:38 +00:00
Lucas BordeauandGitHub 326585157c Fixed plain object in field value for workflow (#17470)
This PR fixes a bug that arises in `formatResult` following up the
recent refactor for DATE_TIME :
https://github.com/twentyhq/twenty/pull/17407

In the case of workflows, we pass a plain object to `formatResult` : 

```ts
{
  before: null, 
  after: '2026-01-27T10:59:15.525Z'
}
```

So a case has been added to handle this. 

@Weiko are we ok with this more restrictive else-if part in this util ?
We could also just put a `continue` for unknown shapes.
2026-01-27 13:21:56 +00:00
MarieandGitHub c5953c9d50 Fix "Level: Error serverlessFunctionPayloads.map is not a function" error (#17474)
Fixes
[sentry](https://twenty-v7.sentry.io/issues/7123326406/?environment=prod&environment=prod-eu&project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D%20timesSeen%3A%3E10&referrer=issue-stream&sort=date)

<img width="840" height="540" alt="image"
src="https://github.com/user-attachments/assets/40b70edf-48b0-4b49-a3eb-d4c7f726245e"
/>
2026-01-27 13:03:46 +00:00
f06caae098 i18n - docs translations (#17473)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-27 13:28:55 +01:00
a98418c6d8 docs: add example output after creating postgres role (#17451)
Added example output showing what the roles table should look like after
creating the postgres role

Co-authored-by: Tom Larson <[email protected]>
2026-01-27 13:16:03 +01:00
43c9a75402 i18n - translations (#17472)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-27 13:15:07 +01:00
WeikoandGitHub c36d112ab4 Add upgrade to org plan card to RLS (#17455)
## Context

<img width="564" height="270" alt="Screenshot 2026-01-26 at 18 19 39"
src="https://github.com/user-attachments/assets/9a6bdd69-2fa1-449b-9985-bfc7a401780e"
/>
2026-01-27 11:51:08 +00:00
d6a7dbb12b i18n - translations (#17469)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-27 12:03:13 +01:00
Paul RastoinandGitHub 16826224cd Invalidate and flush cache post 1.16 upgrade (#17465)
# Motivation
Breaking change requires cache flush
Centralized invalidation cache logic

## Logs
```
[Nest] 42871  - 01/27/2026, 11:39:33 AM     LOG [FlushV2CacheAndIncrementMetadataVersionCommand] Flushing v2 cache and incrementing metadata version for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db
[Runner] Cache invalidation flatFieldMetadataMaps,flatObjectMetadataMaps,flatViewMaps,flatViewFieldMaps,flatViewGroupMaps,flatRowLevelPermissionPredicateMaps,flatRowLevelPermissionPredicateGroupMaps,flatViewFilterGroupMaps,flatIndexMaps,flatServerlessFunctionMaps,flatCronTriggerMaps,flatDatabaseEventTriggerMaps,flatRouteTriggerMaps,flatViewFilterMaps,flatRoleMaps,flatRoleTargetMaps,flatAgentMaps,flatSkillMaps,flatPageLayoutMaps,flatPageLayoutWidgetMaps,flatPageLayoutTabMaps,flatCommandMenuItemMaps,flatNavigationMenuItemMaps,flatFrontComponentMaps: 81.41ms
```
2026-01-27 10:49:31 +00:00
Abdul RahmanGitHubFélix MalfaitAman RajFélix MalfaitPaul Rastoingithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>github-actions
2a8f834377 Integrate NavigationMenuItem with feature flag support (#17268)
## Implement Navigation Menu Items Frontend

Implements the frontend for navigation menu items, the new system
replacing favorites.

### Changes
- Added GraphQL fragments and queries for navigation menu items
- Added hooks for managing navigation menu items (create, update,
delete, sorting, filtering)
- Updated components to use navigation menu items instead of favorites
- Added test coverage for utility functions

### Migration Note
The favorites and navigation menu item modules currently exist in
parallel. The favorites code will be removed once all data has been
migrated to navigation menu items.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Replaces Favorites with feature-flagged `NavigationMenuItem` across
frontend and backend, while keeping Favorites as fallback until
migration completes.
> 
> - UI: new `navigation-menu-item` components (folders, orphan items,
drag provider/droppable, icons, skeleton), dispatcher components to
switch from Favorites, and updated “Add to favorites” action to create
`NavigationMenuItem` when `IS_NAVIGATION_MENU_ITEM_ENABLED`
> - DnD: shared `validateAndExtractFolderId` and droppable id utils
moved to `ui/layout/draggable-list`; favorites DnD updated to use shared
utils
> - GraphQL (client): add fragments, queries, mutations, hooks
(create/update/delete/find), and generated types; added
`RecordIdentifier` and `targetRecordIdentifier` on `NavigationMenuItem`
> - Prefetch: new prefetch state/effect for navigation menu items; skip
favorites prefetch when flag enabled
> - Backend: add DTOs (`NavigationMenuItem`, `RecordIdentifier`),
resolver `targetRecordIdentifier` field, service logic to fetch record
identifiers with permission-aware access and image signing,
`getRecordImageIdentifier` util, entity relation to `view`, and
migration adding FK on `viewId`
> - Feature flags & seeding: add `IS_NAVIGATION_MENU_ITEM_ENABLED` to
enums, dev seeder enables it; standard app seeds workspace navigation
menu items instead of favorites when flag on
> - Tests: add unit tests for sorting/labels/folder id and related utils
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c99746f08b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Aman Raj <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Paul Rastoin <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <[email protected]>
2026-01-27 10:42:27 +00:00
c79f633f17 i18n - translations (#17468)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-27 11:41:03 +01:00
Thomas des FrancsandGitHub 784b05cf45 fix: update calendar setup CTA to 'Finish Setup' with floppy icon (#17464)
## Today

CTA is confusing

<img width="1442" height="947" alt="image"
src="https://github.com/user-attachments/assets/d5dd6229-e78d-4068-acbc-b5766b725b14"
/>

## Summary
- Replace the "Add account" button text with "Finish Setup" at the end
of the calendar account configuration flow
- Change the button icon from a plus sign (`IconPlus`) to a floppy disk
(`IconDeviceFloppy`) to better reflect saving/completing the setup
2026-01-27 10:15:27 +00:00
martmullandGitHub 41d470e687 Implement sync in dev mode (#17405)
implement orchestrator to sync application in dev mode

Log example when starting dev mode, delete and add back functions

```bash
👩‍💻 Workspace - default
[init] 🚀 Starting Twenty Application Development Mode
[init] 📁 App Path: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto

[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.gitignore
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.nvmrc
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarnrc.yml
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/README.md
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/eslint.config.mjs
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/package.json
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/tsconfig.json
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/yarn.lock
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarn/install-state.gz
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/ooo.front-component.tsx
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/tata.object.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/application.config.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/default-function.role.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.front-component.tsx
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/myObject.object.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/utils/toto.ts
[dev-mode] Building manifest...
[dev-mode] Successfully built manifest
[dev-mode] Syncing...
[dev-mode] ✓ Successfully built src/ooo.front-component.tsx
[dev-mode] Uploading .twenty/output/src/ooo.front-component.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world.front-component.tsx
[dev-mode] Uploading .twenty/output/src/app/hello-world.front-component.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs...
[dev-mode] Successfully uploaded .twenty/output/src/ooo.front-component.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.front-component.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs
[dev-mode] ✓ Synced

[dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs...
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts
[dev-mode] Building manifest...
[dev-mode] Successfully built manifest
[dev-mode] Syncing...
[dev-mode] ✓ Synced
[dev-mode] Build failed:
[dev-mode]   Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts"
[dev-mode]   Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts"
[dev-mode]   Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts"
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts
[dev-mode] Building manifest...
[dev-mode] ⚠ No functions defined
[dev-mode] Successfully built manifest
[dev-mode] Syncing...
  🗑️  Removed src/app/hello-world-2.function.mjs
  🗑️  Removed src/app/hello-world-3.function.mjs
  🗑️  Removed src/app/hello-world.function.mjs
[dev-mode] ✓ Synced
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts
[dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts
[dev-mode] Building manifest...
[dev-mode] Successfully built manifest
[dev-mode] Syncing...
[dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs...
[dev-mode] ✓ Successfully built src/app/hello-world.function.ts
[dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs...
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs
[dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs
[dev-mode] ✓ Synced
```
2026-01-26 19:32:13 +00:00
Thomas TrompetteandGitHub 570f83ea76 Use pipeline to count event stream (#17450)
As title. So we only send one TCP call per batch
2026-01-26 18:03:59 +00:00
neo773andGitHub ae1151cf5d IMAP fix edge case of nested folder filtering of unwanted folders (#17428)
Fixes this edge case I saw on a customer's account, where INBOX was the
parent folder of standard folders

<img width="580" height="634" alt="edge case folders"
src="https://github.com/user-attachments/assets/c715e5c6-8d37-45a8-a962-16da2bf38ced"
/>
2026-01-26 17:14:26 +00:00
8931f4681a fix isCalendarFieldReadOnly function to check if calender field is re… (#17319)
This PR fixes the inconsistency between Calendar and Table views when
trying to edit createdAt date field.

Previously, calendar cards could be dragged even though the createdAt
field are UI read-only, resulting in the confusing and inconsistent
behavior compared to the Table view where the field is not editable.

The issue was caused by the drag logic checking only user permissions
and not the field’s UI read-only metadata. This change updates the
calendar drag logic to also check for isUIReadOnly, ensuring that
records are not draggable when the selected calendar date field is
read-only.

---------

Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-26 16:28:53 +00:00
Paul RastoinandGitHub 9f811d3f8e Backfill owner standard field check colliding joinColumnName (#17449)
# Introduction
Previously the command would have been `old` renaming only any `owner`
field on `opportunity` object
Now we also search for any colliding `joinColumnName` with `ownerId`
which is also introduced by the new standard owner field

In a nutshell: previously gracefully handling existing custom `owner`
field collision but not for RELATION types that has an additional
collision surface: `joinColumnName`

Related
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3799872079
2026-01-26 15:35:12 +00:00
9c9c0a7595 fix: prevent record title reset on focus for notes and tasks (#17439)
Fixes issue where focusing the record title input would reset the title
to empty. This ensures the draft value is properly initialized from the
field value if it's undefined or empty when the input is focused.

Fixes #17437

---------

Co-authored-by: Daniel <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-26 15:35:06 +00:00
Paul RastoinandGitHub 44202668fd [TYPES] UniversalEntity JsonbProperty and SerializedRelation (#17396)
# Introduction

In this PR we're introducing mainly two branded type signatures for both
`JsonbProperty` entities properties and `SerializedRelation` (jsonb
serialized property storing another entity id).

Allowing to dynamically map over them later in order to build universal
`jsonb` `serialized` relations.

## `JsonbProperty`

A branded wrapper type that marks entity properties stored as PostgreSQL
JSONB columns. It adds a phantom brand `__JsonbPropertyBrand__` to
object types while leaving primitives unchanged. The branded key is
optional and typed as never, also omitted when transpiled to
`UniversalFlat`

**Should be used at entities lvl only:**
```typescript
@Column({ type: 'jsonb', nullable: false })
gridPosition: JsonbProperty<GridPosition>;

@Column({ nullable: false, type: 'jsonb', default: [] })
publishedVersions: JsonbProperty<string[]>;
```

## `SerializedRelation`

A branded string type that marks foreign key IDs stored inside JSONB
objects. These are entity references serialized within a JSONB column
rather than being a regular database foreign key.

**Usage in jsonb property generic***
```ts
type FieldMetadataRelationSettings = {
  relationType: RelationType;
  onDelete?: RelationOnDeleteAction;
  joinColumnName?: string | null;
  junctionTargetFieldId?: SerializedRelation;
};
```

## `FormatJsonbSerializedRelation<T>`

A transformation type that processes JSONB properties for universal
entity mapping. It:
1. Detects properties with the `JsonbProperty` brand
2. Finds `SerializedRelation` properties
3. Renames them from `*Id` to `*UniversalIdentifier`
4. Removes the brand from the output type ( optional though )

```typescript
// Input: JsonbProperty<{ targetFieldMetadataId: SerializedRelation }>
// Output: { targetFieldMetadataUniversalIdentifier: SerializedRelation }
```

## Result
An example of the dynamic type mapping, through a type-test example
```ts
type SettingsTestCase = UniversalFlatFieldMetadata<
    | FieldMetadataType.RELATION
    | FieldMetadataType.NUMBER
    | FieldMetadataType.TEXT
  >['settings']

type SettingsExpectedResult =
  | {
      relationType: RelationType;
      onDelete?: RelationOnDeleteAction | undefined;
      joinColumnName?: string | null | undefined;
      junctionTargetFieldUniversalIdentifier?: SerializedRelation | undefined;
    }
  | {
      dataType?: NumberDataType | undefined;
      decimals?: number | undefined;
      type?: FieldNumberVariant | undefined;
    }
  | {
      displayedMaxRows?: number | undefined;
    }
  | null;

type Assertions = [
  Expect<Equal<SettingsTestCase, SettingsExpectedResult>>,
]
```

## Remarks

- Removed duplicated twenty-server and twenty-shared typed
- Removed class validator instances for default value that were not used
at runtime, we will refactor that to add validation across all entities
following a same pattern
2026-01-26 14:25:43 +00:00
Paul RastoinandGitHub d0bc9a94c0 [OBJECT_CACHE_FLUSH_REQUIRED_WHEN_RELEASED] Remove FlatObjectMetadata custom fieldMetadataIds fk aggregator property (#17438)
# Introduction
Currently refactoring `flatEntity` typing, encountering some tsc errors
due to this fk aggregator custom override
It shall now follow generic pattern leading to be named `fieldIds`

Needs to flush object cache when released
2026-01-26 13:33:07 +00:00
4a5ffcc9d2 [Fix] Bug with one to many update in table #16340 (#17416)
fixes #16340 

we are updating the cache partially to prevent the flow of the corrupted
fields into the cache

the fields get corrupted during the mutation process potentially
overriding the recoil cache as we are passing the `newRecordCache`
directly in `upsertRecordsInStore`, the newRecordCache is thin and hence
the recoil wipes out the fields that are undefined or empty



we prevent this by extracting the partial data ( only the data which is
being updated in the field ) and passing it to `upsertRecordsInStore`,
as it only touched the specific fields and updates the data, leaving the
other fields untouched.


https://github.com/user-attachments/assets/5256bef7-70c3-47b3-b2ce-dd02ee1a2de8

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-26 13:27:56 +00:00
Paul RastoinandGitHub 406e269cf1 Invalidate flat cache command (#17442)
# Introduction
A command allowing to invalidate flat cache entries. Extends the active
or suspended workspace coverage.

## Args
### --metadataName
If provided will invalidate metadata and related metadata cache entry (
can be repeated see usage )

### --all-metadata
Will invalidate all metadata entries

## Usage example
```
npx nx command twenty-server cache:flat-cache-invalidate --metadataName viewFilter --metadataName objectMetadata
```

```
npx nx command twenty-server cache:flat-cache-invalidate --all-metadata -w 0000-0000-0000-0000
```
2026-01-26 13:08:31 +00:00
90a496bbe8 i18n - translations (#17443)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-26 14:08:39 +01:00
Raphaël BosiandGitHub 7e7a535af0 Add front component widget (#17440)
Modified the data model and introduced a seed to introduce front
components widgets.
2026-01-26 12:45:58 +00:00
e0d4492013 i18n - docs translations (#17434)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-26 09:06:17 +01:00
2353bc62cc i18n - docs translations (#17433)
Created by Github action

Co-authored-by: github-actions <[email protected]>
2026-01-26 07:11:01 +01:00
c8c821a692 i18n - docs translations (#17426)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-25 21:51:11 +01:00
Paul RastoinandGitHub 3be05641fa Fix upgrade command order backfillStandardPageLayoutsCommand (#17430)
# Introduction
`backfillStandardPageLayoutsCommand` expects field and object metadata
to have been identified in prior to be run
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3796933563
2026-01-25 18:48:53 +00:00
23b1b7914a i18n - translations (#17424)
Created by Github action

---------

Co-authored-by: github-actions <[email protected]>
2026-01-25 13:43:32 +01:00
Félix MalfaitandGitHub 161689be18 feat: fix junction toggle persistence and add type-safe documentation paths (#17421)
## Summary

- **Fix junction relation toggle not being saved**: The form schema
wasn't tracking the `settings` field, so changes to
`junctionTargetFieldId` weren't marked as dirty
- **Add type-safe documentation paths**: Generate TypeScript constants
from `base-structure.json` to prevent broken documentation links
- **Create many-to-many relations documentation**: Step-by-step guide
for building many-to-many relations using junction objects
- **Update `getDocumentationUrl`**: Now uses shared constants from
`twenty-shared` for base URL, default path, and supported languages

## Key Changes

### Junction Toggle Fix
- Added `settings` field to the form schema in
`SettingsDataModelFieldRelationForm.tsx`
- Fixed the toggle to properly merge settings when updating
`junctionTargetFieldId`

### Type-Safe Documentation Paths
- New constants in `twenty-shared/constants`:
- `DOCUMENTATION_PATHS` - All 161 documentation paths as typed constants
  - `DOCUMENTATION_SUPPORTED_LANGUAGES` - 14 supported languages
  - `DOCUMENTATION_BASE_URL` / `DOCUMENTATION_DEFAULT_PATH`
- Generator script: `yarn docs:generate-paths`
- CI integration: Added to `docs-i18n-pull.yaml` workflow

### Documentation
- New article:
`/user-guide/data-model/how-tos/create-many-to-many-relations`
- Updated `/user-guide/data-model/capabilities/relation-fields.mdx` with
Lab warning and link

## Test plan
- [ ] Verify junction toggle saves correctly when enabled/disabled
- [ ] Verify documentation link opens correct localized page
- [ ] Verify `yarn docs:generate-paths` regenerates paths correctly
2026-01-25 13:29:20 +01:00
Paul RastoinandGitHub 3b512164d1 [Debug log level] Print validation build result failure (#17423)
# Introduction
As per title, in order to ease debug
Motivation
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3796168946
2026-01-25 11:10:37 +00:00
62845cac87 Fix the default value of search record if the filter is boolean type (#17297)
This fixes the #15896. For problem statement. Please refer to the shared
video mentioned in the issue.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Aligns filter defaults across UI by centralizing initial value
computation.
> 
> - Add boolean handling in `useGetInitialFilterValue` to return
`value/displayValue` of `'false'` when operand is `IS`
> - Update `WorkflowDropdownStepOutputItems` to use
`getInitialFilterValue` for initial `value` instead of an empty string,
based on field type and default operand
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3ce05fa31a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
2026-01-24 22:27:05 +00:00
fa0615d41e Migrate attachments to morph relations + fix morph join column filtering (#17381)
Closes [1744](https://github.com/twentyhq/core-team-issues/issues/1744).

This PR migrates attachments to morph relations behind a feature flag,
following the TimelineActivity pattern. it introduces the
`IS_ATTACHMENT_MIGRATED` flag, updates standard field metadata and
indexes to use morph relations, adds a workspace migration that renames
`attachment.*Id` columns to `target*Id` and converts the corresponding
field metadata to `MORPH_RELATION` with a shared `morphId`. On the
frontend, attachment read/write paths now switch to `target*Id` when the
flag is enabled.

It also fixes optimistic filtering for morph join columns. The metadata
API deduplicates morph fields, so attachments now expose a single target
field of type `MORPH_RELATION` plus a `morphRelations` array listing
each target object. Because only one `settings.joinColumnName` is
returned (e.g. `targetRocketId`), filters like `targetCompanyId` don’t
map to any field and the optimistic cache code throws.
`doesMorphRelationJoinColumnMatch` resolves this by computing all valid
join column names from `morphRelations` using
`computeMorphRelationFieldName` and comparing them to the filter key.
That makes filters like `targetCompanyId` resolvable even with a single
target field, so attachment uploads and list matching no longer crash.

<img width="477" height="474" alt="image"
src="https://github.com/user-attachments/assets/50e19418-3438-4d1e-9f1f-1bc1a03174a9"
/>

<br />
<br />

Today the metadata API returns one morph field called `target` and a
list of possible targets (`morphRelations`), but it does not tell us the
join column for each target. That’s why the Frontend had to compute join
column names.

If we want to fix this at the API level, there are two options:

- Add join column names to each target in `morphRelations` (e.g. company
→ `targetCompanyId`). This is additive and low‑risk.
- Return each target as its own field (`targetCompany`, `targetPerson`,
etc.) instead of a single target. This is a larger change because it
changes the shape of metadata and would require more UI updates.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces morph relations for attachments behind
`IS_ATTACHMENT_MIGRATED`, aligning server schema/metadata and frontend
behavior.
> 
> - Adds `IS_ATTACHMENT_MIGRATED` flag (frontend/server) and
seeds/defaults; updates generated GraphQL enums
> - New workspace upgrade `1.17` command migrates data: renames
`attachment.*Id` → `target*Id` and converts related fields to
`MORPH_RELATION` with shared `morphId`
> - Updates standard field metadata and indexes to `target*` (attachment
+ related objects), dev seeds, snapshots, and workspace entity types
> - Frontend: switches attachment read/write filters via
`getActivityTargetObjectFieldIdName` using the feature flag; updates
hooks/components (`useAttachments`, `useUploadAttachmentFile`, editors);
expands `Attachment` type
> - Fixes optimistic cache filtering to recognize morph join columns in
`isRecordMatchingFilter` by computing valid join-column keys from
`morphRelations`
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f208fa23b1. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <[email protected]>
2026-01-24 21:17:24 +00:00
21ce7ea420 docs: align type import guidelines with ESLint configuration (#17275)
### Summary
The style guide currently discourages type imports, which contradicts
the enforced ESLint rule
`@typescript-eslint/consistent-type-imports` and existing code usage
(e.g. Storybook stories).

This PR updates the documentation to recommend inline type imports,
aligning the style guide
with tooling and preventing contributor confusion.

### Why
- Aligns documentation with enforced ESLint configuration
- Prevents ESLint auto-fix conflicts
- Improves contributor experience

Fixes #<### Summary
The style guide currently discourages type imports, which contradicts
the enforced ESLint rule
`@typescript-eslint/consistent-type-imports` and existing code usage
(e.g. Storybook stories).

This PR updates the documentation to recommend inline type imports,
aligning the style guide
with tooling and preventing contributor confusion.

### Why
- Aligns documentation with enforced ESLint configuration
- Prevents ESLint auto-fix conflicts
- Improves contributor experience

Fixes #<#17222>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Updates documentation to match tooling and current usage.
> 
> - Replaces "Enforcing No-Type Imports" with **Type Imports**,
recommending inline type imports
> - Updates examples to prefer `import { type X } from ...` and avoid
importing types as runtime values
> - Clarifies ESLint `@typescript-eslint/consistent-type-imports`
configuration to prefer explicit type imports and enforce
`inline-type-imports` fix style
> - Changes confined to
`packages/twenty-docs/l/fr/developers/contribute/capabilities/frontend-development/style-guide.mdx`
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e2250e43ad. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Félix Malfait <[email protected]>
2026-01-24 20:50:33 +00:00
84d140025a fix: refresh virtualized table when field metadata is updated ( #16388 ) (#17214)
Fixes #16388 

Now we have used the metadata fetching from network using
resetVirtualizationBecauseDataChanged(), as we navigate back to the
table after updating the field content. As the virtualised table uses
the "cache-first" strategy, it fails to give fresh data from the data
base

Please let me know i we need to add the loading state (and it's UI
requirements) while it fetches the cell content, as currently the cell
is empty until it's populated by fresh data




https://github.com/user-attachments/assets/901085ba-4337-4a5d-86c8-15ac84250e8f

---------

Co-authored-by: Arun kumar <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
2026-01-24 18:56:00 +00:00
Félix MalfaitandGitHub 389b6ce7b2 fix(twenty-server): preserve input order in createMany response (#17412)
## Summary
- The `createMany` API was returning records in arbitrary order because
`fetchUpsertedRecords` used `WHERE id IN (...)` without `ORDER BY`
- This caused flaky tests that assumed input order was preserved (e.g.,
`people-merge-many.integration-spec.ts`)
- Fixed by reordering the fetched records to match the original input
order from `generatedMaps`

## Root Cause
```typescript
// Before: No ORDER BY, so SQL returns in arbitrary order
const upsertedRecords = await queryBuilder
  .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)) })
  .getMany();  // Returns in arbitrary order!
```

## Fix
```typescript
// After: Reorder results to match original input order
const orderedIds = objectRecords.generatedMaps.map((record) => record.id);
const upsertedRecords = await queryBuilder
  .where({ id: In(orderedIds) })
  .getMany();

// Preserve original input order
const recordsById = new Map(upsertedRecords.map((record) => [record.id, record]));
return orderedIds
  .map((id) => recordsById.get(id))
  .filter((record) => record !== undefined);
```

## Test plan
- [x] Run `people-merge-many.integration-spec.ts` multiple times -
passes consistently
- [x] Lint passes
2026-01-24 12:49:48 +00:00
Félix MalfaitandGitHub cd7c2864d2 fix(twenty-server): add SSRF protection to webhook requests (#17403)
## Summary

- Adds SSRF (Server-Side Request Forgery) protection to webhook requests
by using the same secure axios adapter already used by HTTP workflow
actions
- Prevents webhooks from making requests to private/internal IP
addresses (10.x, 192.168.x, 172.16-31.x, 169.254.x, localhost)
- Adds specific error logging when a webhook fails due to SSRF
protection

## Context

The HTTP workflow tool (`HTTP_REQUEST` action) already had SSRF
protection via `HTTP_TOOL_SAFE_MODE_ENABLED`, but webhooks were using
`HttpService` directly without this protection. This inconsistency meant
users could potentially configure webhooks to probe internal
infrastructure.

### What's protected now:

| Feature | Before | After |
|---------|--------|-------|
| HTTP Workflow Action | Protected (secure adapter) | Protected (secure
adapter) |
| Webhooks | **Unprotected** | Protected (secure adapter) |

### The secure adapter validates:
1. Protocol must be `http:` or `https:`
2. DNS resolution of hostname
3. Resolved IP must not be in private ranges

## Test plan

- [ ] Configure a webhook with an external URL (e.g.,
`https://webhook.site`) - should work
- [ ] Configure a webhook with `http://localhost:3000` - should fail
with SSRF error in audit log
- [ ] Configure a webhook with `http://10.0.0.1/test` - should fail with
SSRF error in audit log
- [ ] Configure a webhook with a domain that resolves to a private IP -
should fail
2026-01-24 10:46:46 +00:00
Lucas BordeauandGitHub a07590eba5 Change formatResult to return string instead of Date object for DATE_TIME (#17407)
This PR modifies our broadly used `formatResult` util to counter act
TypeORM transforming any date time to a `Date` object.

Instead we return the ISO string for any `DATE_TIME`, this way we're not
transporting Date object from one function to another in the backend.

We do this because there was problems working with events utils that
take string date time in parameters and received Date objects.

As this is a recurring problem and because it's an opinionated choice
from TypeORM, we chose to switch to string only in our codebase, from
TypeORM's output to frontend.
2026-01-23 18:22:01 +00:00
Lucas BordeauandGitHub 9ecab8fb82 Fix event logic for soft-delete and restore (#17393)
This PR changes the shape and logic of SSE events `DELETE` and
`RESTORE`, because they behave like `UPDATE` events in practice, they
should share the same logic.

Before this PR, it was impossible for the frontend to obtain the
`deletedAt` value, and the logic to handle soft-delete and restore would
have been flawed.

Because there is a typing confusion in the parameters of
`formatTwentyOrmEventToDatabaseBatchEvent`, due to TypeORM, we also
update this util to only accept an array of records, instead of `T |
T[]`. We should improve our TypeORM layer in the future.

Also the naming was not clear, so we clearly use `recordsAfter` and
`recordsBefore` as much as possible, because that is what we have at the
end in events.

Events are sent from their respective query builders, so these last ones
have been updated also.

Because TypeORM `soft-remove` operation only returns record ids, we add
`.getMany()` to fetch all fields for soft-removed records, so that our
event can have before and after.
2026-01-23 17:55:07 +00:00
Thomas TrompetteandGitHub 2346efd71f Add prometheus exporter (#17392)
Create a metric endpoint and expose prometheus gauge for event stream
count.
2026-01-23 15:38:15 +00:00
4782 changed files with 222711 additions and 137210 deletions
+204 -24
View File
@@ -22,24 +22,51 @@ Examples of existing syncable entities: `skill`, `agent`, `view`, `viewField`, `
## Table of Contents
1. [Overview](#overview)
2. [File Structure](#file-structure)
3. [Step-by-Step Implementation](#step-by-step-implementation)
- [Step 1: Add Metadata Name Constant](#step-1-add-metadata-name-constant-twenty-shared)
- [Step 2: Create TypeORM Entity](#step-2-create-typeorm-entity)
- [Step 3: Define Flat Entity Type](#step-3-define-flat-entity-type)
- [Step 4: Define Editable Properties](#step-4-define-editable-properties)
- [Step 5: Register in Central Constants](#step-5-register-in-central-constants)
- [Step 6: Create Cache Service](#step-6-create-cache-service)
- [Step 7: Create Flat Entity Module](#step-7-create-flat-entity-module)
- [Step 8: Define Action Types](#step-8-define-action-types)
- [Step 9: Create Validator Service](#step-9-create-validator-service)
- [Step 10: Create Builder Service](#step-10-create-builder-service)
- [Step 11: Create Action Handlers](#step-11-create-action-handlers-runner)
- [Step 12: Wire in Orchestrator Service](#step-12-wire-in-orchestrator-service-critical)
- [Step 13: Register in Modules](#step-13-register-in-modules)
4. [Using the Entity in Services](#using-the-entity-in-services)
5. [Integration Tests](#integration-tests)
- [Creating a New Syncable Entity](#creating-a-new-syncable-entity)
- [What is a Syncable Entity?](#what-is-a-syncable-entity)
- [Table of Contents](#table-of-contents)
- [Overview](#overview)
- [Key Design Principle: Separation of Concerns](#key-design-principle-separation-of-concerns)
- [File Structure](#file-structure)
- [Step-by-Step Implementation](#step-by-step-implementation)
- [Step 1: Add Metadata Name Constant (twenty-shared)](#step-1-add-metadata-name-constant-twenty-shared)
- [Step 2: Create TypeORM Entity](#step-2-create-typeorm-entity)
- [Step 2b: Using JsonbProperty and SerializedRelation Types](#step-2b-using-jsonbproperty-and-serializedrelation-types)
- [JsonbProperty Wrapper](#jsonbproperty-wrapper)
- [SerializedRelation Type](#serializedrelation-type)
- [Complete Example](#complete-example)
- [Step 3: Define Flat Entity Type](#step-3-define-flat-entity-type)
- [Step 4: Define Editable Properties](#step-4-define-editable-properties)
- [Step 5: Register in Central Constants](#step-5-register-in-central-constants)
- [5a. All Flat Entity Types Registry](#5a-all-flat-entity-types-registry)
- [5b. Properties to Compare and Stringify](#5b-properties-to-compare-and-stringify)
- [5c. Metadata Relations](#5c-metadata-relations)
- [5d. Required Metadata for Validation](#5d-required-metadata-for-validation)
- [Step 6: Create Cache Service](#step-6-create-cache-service)
- [Step 7: Create Flat Entity Module](#step-7-create-flat-entity-module)
- [Step 8: Define Action Types](#step-8-define-action-types)
- [Step 9: Create Validator Service](#step-9-create-validator-service)
- [Step 10: Create Builder Service](#step-10-create-builder-service)
- [Step 11: Create Action Handlers (Runner)](#step-11-create-action-handlers-runner)
- [Step 12: Wire in Orchestrator Service (CRITICAL)](#step-12-wire-in-orchestrator-service-critical)
- [Step 13: Register in Modules](#step-13-register-in-modules)
- [13a. Builder Module](#13a-builder-module)
- [13b. Validators Module](#13b-validators-module)
- [13c. Action Handlers Module](#13c-action-handlers-module)
- [Using the Entity in Services](#using-the-entity-in-services)
- [Integration Tests](#integration-tests)
- [Checklist](#checklist)
- [Syncable Entity Requirements](#syncable-entity-requirements)
- [JSONB Properties and Serialized Relations](#jsonb-properties-and-serialized-relations)
- [Registration (twenty-shared)](#registration-twenty-shared)
- [Flat Entity Definition](#flat-entity-definition)
- [Central Constants Registration](#central-constants-registration)
- [Cache Layer](#cache-layer)
- [Migration Builder](#migration-builder)
- [Migration Runner](#migration-runner)
- [Orchestrator Wiring (⚠️ COMMONLY FORGOTTEN)](#orchestrator-wiring--commonly-forgotten)
- [Module Registration](#module-registration)
- [Testing](#testing)
---
@@ -176,9 +203,6 @@ export class MyEntityEntity
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
standardId: string | null;
@Column({ nullable: false })
name: string;
@@ -237,6 +261,157 @@ export abstract class WorkspaceRelatedEntity {
---
### Step 2b: Using JsonbProperty and SerializedRelation Types
When your entity has JSONB columns or stores foreign key references inside JSONB structures, you must use the branded type wrappers to enable automatic universal identifier mapping.
#### JsonbProperty Wrapper
Wrap all JSONB column types with `JsonbProperty<T>` to mark them for the universal entity transformation system:
```typescript
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
@Entity('myEntity')
export class MyEntityEntity extends SyncableEntity {
// Simple JSONB column - wrap the type
@Column({ type: 'jsonb', nullable: true })
settings: JsonbProperty<MyEntitySettings> | null;
// JSONB column with complex type
@Column({ type: 'jsonb', nullable: false })
configuration: JsonbProperty<MyEntityConfiguration>;
// Array stored as JSONB
@Column({ type: 'jsonb', nullable: true })
tags: JsonbProperty<string[]> | null;
}
```
**When to use `JsonbProperty<T>`:**
- Any column with `type: 'jsonb'` that stores an object or array
- Configuration objects, settings, metadata blobs
- Any structured data stored as JSON in the database
**What it enables:**
- The type system can identify which properties are JSONB columns
- Automatic transformation of serialized relations within JSONB structures
- Type-safe universal entity mapping
#### SerializedRelation Type
Use `SerializedRelation` for properties **inside JSONB structures** that store foreign key references (entity IDs):
```typescript
import { SerializedRelation } from 'twenty-shared/types';
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
// Define the JSONB structure type
type MyEntityConfiguration = {
name: string;
// This stores a reference to another field's ID - use SerializedRelation
targetFieldMetadataId: SerializedRelation;
// This stores a reference to an object's ID
sourceObjectMetadataId: SerializedRelation;
// Regular string - NOT a foreign key reference
displayFormat: string;
};
@Entity('myEntity')
export class MyEntityEntity extends SyncableEntity {
@Column({ type: 'jsonb', nullable: false })
configuration: JsonbProperty<MyEntityConfiguration>;
}
```
**When to use `SerializedRelation`:**
- Properties inside JSONB that store UUIDs referencing other entities
- Foreign key relationships that can't use TypeORM relations (because they're in JSONB)
- Any `*Id` property inside a JSONB structure that references another metadata entity
**What it enables:**
- Automatic renaming from `*Id` to `*UniversalIdentifier` in universal entities
- Type-safe extraction of serialized relation properties
- Proper handling during workspace sync/migration
#### Complete Example
```typescript
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { SerializedRelation } from 'twenty-shared/types';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
// JSONB structure with serialized relations
type WidgetConfiguration = {
title: string;
// Foreign keys stored in JSONB - use SerializedRelation
fieldMetadataId: SerializedRelation;
objectMetadataId: SerializedRelation;
// Optional foreign key
viewId?: SerializedRelation;
// Regular properties (not foreign keys)
displayMode: 'compact' | 'expanded';
maxItems: number;
};
type GridPosition = {
row: number;
column: number;
width: number;
height: number;
};
@Entity('widget')
export class WidgetEntity extends SyncableEntity implements Required<WidgetEntity> {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false })
name: string;
// JSONB column with serialized relations - wrap with JsonbProperty
@Column({ type: 'jsonb', nullable: false })
configuration: JsonbProperty<WidgetConfiguration>;
// JSONB column without serialized relations - still wrap with JsonbProperty
@Column({ type: 'jsonb', nullable: false })
gridPosition: JsonbProperty<GridPosition>;
@Column({ default: false })
isCustom: boolean;
// ... other columns
}
```
**Result in Universal Entity:**
When transformed to a universal entity, the `configuration` property will have its `SerializedRelation` fields automatically renamed:
```typescript
// Original (in database/flat entity)
{
fieldMetadataId: "abc-123",
objectMetadataId: "def-456",
viewId: "ghi-789",
displayMode: "compact",
maxItems: 10,
}
// Transformed (in universal entity)
{
fieldMetadataUniversalIdentifier: "abc-123",
objectMetadataUniversalIdentifier: "def-456",
viewUniversalIdentifier: "ghi-789",
displayMode: "compact",
maxItems: 10,
}
```
---
### Step 3: Define Flat Entity Type
**File:** `src/engine/metadata-modules/flat-my-entity/types/flat-my-entity.type.ts`
@@ -517,7 +692,7 @@ import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { MyEntityExceptionCode } from 'src/engine/metadata-modules/my-entity/my-entity.exception';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
@@ -585,7 +760,7 @@ export class FlatMyEntityValidatorService {
}
// Prevent deletion of standard entities unless it's a system build
if (!buildOptions.isSystemBuild && isStandardMetadata(existingEntity)) {
if (!buildOptions.isSystemBuild && belongsToTwentyStandardApp(existingEntity)) {
validationResult.errors.push({
code: MyEntityExceptionCode.MY_ENTITY_IS_STANDARD,
message: t`Cannot delete standard entity`,
@@ -738,7 +913,7 @@ export class WorkspaceMigrationMyEntityActionsBuilderService extends WorkspaceEn
type: 'update',
metadataName: 'myEntity',
entityId: flatEntityId,
updates: flatEntityUpdates,
update: flatEntityUpdates,
};
return {
@@ -1143,6 +1318,11 @@ Before considering your syncable entity complete, verify:
- [ ] Entity has `isCustom` boolean column
- [ ] Entity-to-flat transform sets `universalIdentifier` correctly (`standardId || id`)
### JSONB Properties and Serialized Relations
- [ ] All JSONB columns are wrapped with `JsonbProperty<T>`
- [ ] Foreign key references inside JSONB structures use `SerializedRelation` type
- [ ] JSONB structure types are properly defined with `SerializedRelation` for `*Id` properties
### Registration (twenty-shared)
- [ ] Metadata name added to `ALL_METADATA_NAME`
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
+8 -6
View File
@@ -14,8 +14,8 @@ concurrency:
env:
# restore-cache action adds 'v4-' prefix and '-<branch>-<sha>' suffix to the key
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-depot-ubuntu-24.04-8-runner
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-depot-ubuntu-24.04-8-runner-${{ github.ref_name }}-${{ github.sha }}
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-ubuntu-latest-8-cores-runner
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-ubuntu-latest-8-cores-runner-${{ github.ref_name }}-${{ github.sha }}
jobs:
changed-files-check:
@@ -27,6 +27,7 @@ jobs:
packages/twenty-front/**
packages/twenty-ui/**
packages/twenty-shared/**
packages/twenty-sdk/**
changed-files-check-e2e:
uses: ./.github/workflows/changed-files.yaml
with:
@@ -38,7 +39,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
env:
REACT_APP_SERVER_BASE_URL: http://localhost:3000
steps:
@@ -64,7 +65,7 @@ jobs:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
front-sb-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
needs: front-sb-build
strategy:
fail-fast: false
@@ -85,6 +86,7 @@ jobs:
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-sdk
- name: Install Playwright
run: |
cd packages/twenty-front
@@ -138,7 +140,7 @@ jobs:
timeout-minutes: 30
if: false
needs: front-sb-build
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
env:
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
@@ -204,7 +206,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
env:
NODE_OPTIONS: "--max-old-space-size=10240"
steps:
+48 -50
View File
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test:unit, test:integration]
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
steps:
- name: Cancel Previous Runs
uses: styfle/[email protected]
@@ -39,64 +39,62 @@ jobs:
uses: ./.github/actions/yarn-install
- name: Build
run: npx nx build twenty-sdk
- name: Install Playwright
if: contains(matrix.task, 'storybook')
run: npx playwright install chromium
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
tag: scope:sdk
tasks: ${{ matrix.task }}
# TODO: Re-enable sdk-e2e-test once application sync is stable
# sdk-e2e-test:
# timeout-minutes: 30
# runs-on: depot-ubuntu-24.04-8
# needs: [changed-files-check, sdk-test]
# if: needs.changed-files-check.outputs.any_changed == 'true'
# services:
# postgres:
# image: twentycrm/twenty-postgres-spilo
# env:
# PGUSER_SUPERUSER: postgres
# PGPASSWORD_SUPERUSER: postgres
# ALLOW_NOSSL: 'true'
# SPILO_PROVIDER: 'local'
# ports:
# - 5432:5432
# options: >-
# --health-cmd pg_isready
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# redis:
# image: redis
# ports:
# - 6379:6379
# env:
# NODE_ENV: test
# steps:
# - name: Fetch custom Github Actions and base branch history
# uses: actions/checkout@v4
# with:
# fetch-depth: 0
# - name: Install dependencies
# uses: ./.github/actions/yarn-install
# - name: Server / Append billing config to .env.test
# working-directory: packages/twenty-server
# run: |
# echo "" >> .env.test
# echo "IS_BILLING_ENABLED=true" >> .env.test
# echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
# echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
# echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
# echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
# - name: Server / Create Test DB
# run: |
# PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
# - name: SDK / Run E2E Tests
# run: npx nx test:e2e twenty-sdk
sdk-e2e-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
postgres:
image: twentycrm/twenty-postgres-spilo
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
env:
NODE_ENV: test
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
run: npx nx build twenty-sdk
- name: Server / Create Test DB
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
- name: SDK / Run e2e Tests
uses: ./.github/actions/nx-affected
with:
tag: scope:sdk
tasks: test:e2e
ci-sdk-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, sdk-test]
needs: [changed-files-check, sdk-test, sdk-e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
+3 -3
View File
@@ -31,7 +31,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -143,7 +143,7 @@ jobs:
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
server-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
needs: server-setup
steps:
- name: Fetch custom Github Actions and base branch history
@@ -164,7 +164,7 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
runs-on: ubuntu-latest-8-cores
needs: server-setup
strategy:
fail-fast: false
+174
View File
@@ -0,0 +1,174 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted]
issues:
types: [opened, assigned]
repository_dispatch:
types: [claude-core-team-issues]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.client_payload.issue_number }}
cancel-in-progress: false
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot') ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run Claude Code
id: claude-code
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
additional_permissions: |
actions: read
claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"'
settings: |
{
"env": {
"PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default"
}
}
- name: Post Create-PR link if Claude ran out of turns
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "" ]; then
exit 0
fi
AHEAD=$(git rev-list --count main.."$BRANCH" 2>/dev/null || echo "0")
if [ "$AHEAD" = "0" ]; then
exit 0
fi
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
exit 0
fi
ISSUE_NUMBER="${{ github.event.issue.number || github.event.pull_request.number }}"
ENCODED_BRANCH=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$BRANCH', safe=''))")
PR_URL="https://github.com/${{ github.repository }}/compare/main...${ENCODED_BRANCH}?quick_pull=1"
BODY="⚠️ Claude ran out of turns before creating a PR. Work has been pushed to [\`$BRANCH\`](https://github.com/${{ github.repository }}/tree/$ENCODED_BRANCH).\n\n[**Create PR →**]($PR_URL)"
if [ -n "$ISSUE_NUMBER" ]; then
gh issue comment "$ISSUE_NUMBER" --body "$(echo -e "$BODY")"
fi
claude-cross-repo:
if: github.event_name == 'repository_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build prompt from dispatch payload
id: prompt
uses: actions/github-script@v7
with:
script: |
const p = context.payload.client_payload;
let prompt;
if (p.comment_body) {
prompt = `You are responding to a comment on issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository.\n\nThe comment by @${p.sender} says:\n\n${p.comment_body}\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
} else {
prompt = `You are responding to issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository, opened by @${p.sender}.\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
}
core.setOutput('prompt', prompt);
core.setOutput('repo', p.repo_full_name);
core.setOutput('issue_number', p.issue_number);
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: ${{ steps.prompt.outputs.prompt }}
additional_permissions: |
actions: read
claude_args: '--max-turns 200 --model opus --allowedTools "Edit,Write,WebFetch,Bash(bash packages/twenty-utils/setup-dev-env.sh),Bash(npx nx *),Bash(npx jest *),Bash(yarn *),Bash(git *),Bash(gh *),Bash(sed *),Bash(python3 *),Bash(rm *),Bash(find *),Bash(grep *),Bash(cat *),Bash(ls *),Bash(head *),Bash(tail *),Bash(wc *),Bash(sort *),Bash(uniq *),Bash(mkdir *),Bash(cp *),Bash(mv *),Bash(touch *),Bash(chmod *),Bash(echo *),Bash(curl *),Bash(cd *),Bash(pwd *),Bash(diff *),Bash(xargs *),Bash(awk *),Bash(cut *),Bash(tee *),Bash(tr *)"'
settings: |
{
"env": {
"PG_DATABASE_URL": "postgres://postgres:postgres@localhost:5432/default"
}
}
- name: Post response to source issue
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.TWENTY_DISPATCH_TOKEN }}
script: |
const [owner, repo] = '${{ steps.prompt.outputs.repo }}'.split('/');
const issueNumber = parseInt('${{ steps.prompt.outputs.issue_number }}', 10);
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: `Claude finished processing this request. [See workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
});
+22
View File
@@ -22,6 +22,28 @@
"close": false
},
"problemMatcher": []
},
{
"label": "twenty-server - run unit test file",
"type": "shell",
"command": "npx nx run twenty-server:jest -- --config ./jest.config.mjs ${relativeFile} --silent=false ${input:watchMode} ${input:updateSnapshot}",
"options": {
"cwd": "${workspaceFolder}/packages/twenty-server",
"env": {
"NODE_ENV": "test",
"NODE_OPTIONS": "--max-old-space-size=12288 --import tsx/esm"
},
"shell": {
"executable": "/bin/zsh",
"args": ["-l", "-c"]
}
},
"presentation": {
"reveal": "always",
"panel": "new",
"close": false
},
"problemMatcher": []
}
],
"inputs": [
+74 -30
View File
@@ -21,30 +21,31 @@ npx nx run twenty-server:worker # Start background worker
### Testing
```bash
# Run tests
# Preferred: run a single test file (fast)
npx jest path/to/test.test.ts --config=packages/PROJECT/jest.config.mjs
# Run all tests for a package
npx nx test twenty-front # Frontend unit tests
npx nx test twenty-server # Backend unit tests
npx nx run twenty-server:test:integration:with-db-reset # Integration tests with DB reset
# Storybook
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:test twenty-front # Run Storybook tests
npx nx storybook:build twenty-front
npx nx storybook:test twenty-front
When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
# When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
```
### Code Quality
```bash
# Linting (diff with main - fastest)
npx nx lint:diff-with-main twenty-front # Lint only files changed vs main
npx nx lint:diff-with-main twenty-server # Lint only files changed vs main
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix files changed vs main
# Linting (diff with main - fastest, always prefer this)
npx nx lint:diff-with-main twenty-front
npx nx lint:diff-with-main twenty-server
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix
# Linting (full project)
npx nx lint twenty-front # Lint all files in frontend
npx nx lint twenty-server # Lint all files in backend
npx nx lint twenty-front --fix # Auto-fix all linting issues
# Linting (full project - slower, use only when needed)
npx nx lint twenty-front
npx nx lint twenty-server
# Type checking
npx nx typecheck twenty-front
@@ -57,7 +58,8 @@ npx nx fmt twenty-server
### Build
```bash
# Build packages
# Build packages (twenty-shared must be built first)
npx nx build twenty-shared
npx nx build twenty-front
npx nx build twenty-server
```
@@ -69,7 +71,7 @@ npx nx database:reset twenty-server # Reset database
npx nx run twenty-server:database:init:prod # Initialize database
npx nx run twenty-server:database:migrate:prod # Run migrations
# Generate migration
# Generate migration (replace [name] with kebab-case descriptive name)
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/common/[name] -d src/database/typeorm/core/core.datasource.ts
# Sync metadata
@@ -78,8 +80,9 @@ npx nx run twenty-server:command workspace:sync-metadata
### GraphQL
```bash
# Generate GraphQL types
# Generate GraphQL types (run after schema changes)
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
```
## Architecture Overview
@@ -107,13 +110,36 @@ packages/
- **Named exports only** (no default exports)
- **Types over interfaces** (except when extending third-party interfaces)
- **String literals over enums** (except for GraphQL enums)
- **No 'any' type allowed**
- **No 'any' type allowed** — strict TypeScript enforced
- **Event handlers preferred over useEffect** for state updates
- **Props down, events up** — unidirectional data flow
- **Composition over inheritance**
- **No abbreviations** in variable names (`user` not `u`, `fieldMetadata` not `fm`)
### Naming Conventions
- **Variables/functions**: camelCase
- **Constants**: SCREAMING_SNAKE_CASE
- **Types/Classes**: PascalCase (suffix component props with `Props`, e.g. `ButtonProps`)
- **Files/directories**: kebab-case with descriptive suffixes (`.component.tsx`, `.service.ts`, `.entity.ts`, `.dto.ts`, `.module.ts`)
- **TypeScript generics**: descriptive names (`TData` not `T`)
### File Structure
- Components under 300 lines, services under 500 lines
- Components in their own directories with tests and stories
- Use `index.ts` barrel exports for clean imports
- Import order: external libraries first, then internal (`@/`), then relative
### Comments
- Use short-form comments (`//`), not JSDoc blocks
- Explain WHY (business logic), not WHAT
- Do not comment obvious code
- Multi-line comments use multiple `//` lines, not `/** */`
### State Management
- **Recoil** for global state management
- Component-specific state with React hooks
- **Recoil** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
- GraphQL cache managed by Apollo Client
- Use functional state updates: `setState(prev => prev + 1)`
### Backend Architecture
- **NestJS modules** for feature organization
@@ -122,36 +148,54 @@ packages/
- **Redis** for caching and session management
- **BullMQ** for background job processing
### Database
### Database & Migrations
- **PostgreSQL** as primary database
- **Redis** for caching and sessions
- **TypeORM migrations** for schema management
- **ClickHouse** for analytics (when enabled)
- Always generate migrations when changing entity files
- Migration names must be kebab-case (e.g. `add-agent-turn-evaluation`)
- Include both `up` and `down` logic in migrations
- Never delete or rewrite committed migrations
### Utility Helpers
Use existing helpers from `twenty-shared` instead of manual type guards:
- `isDefined()`, `isNonEmptyString()`, `isNonEmptyArray()`
## Development Workflow
IMPORTANT: Use Context7 for code generation, setup or configuration steps, or library/API documentation. Automatically use the Context7 MCP tools to resolve library IDs and get library docs without waiting for explicit requests.
### Before Making Changes
1. Always run linting and type checking after code changes
2. Test changes with relevant test suites
3. Ensure database migrations are properly structured
1. Always run linting (`lint:diff-with-main`) and type checking after code changes
2. Test changes with relevant test suites (prefer single-file test runs)
3. Ensure database migrations are generated for entity changes
4. Check that GraphQL schema changes are backward compatible
5. Run `graphql:generate` after any GraphQL schema changes
### Code Style Notes
- Use **Emotion** for styling with styled-components pattern
- Follow **Nx** workspace conventions for imports
- Use **Lingui** for internationalization
- Components should be in their own directories with tests and stories
- Apply security first, then formatting (sanitize before format)
### Testing Strategy
- **Unit tests** with Jest for both frontend and backend
- **Integration tests** for critical backend workflows
- **Storybook** for component development and testing
- **E2E tests** with Playwright for critical user flows
- **Test behavior, not implementation** — focus on user perspective
- **Test pyramid**: 70% unit, 20% integration, 10% E2E
- Query by user-visible elements (text, roles, labels) over test IDs
- Use `@testing-library/user-event` for realistic interactions
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## CI Environment (GitHub Actions)
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
- The script is idempotent and safe to run multiple times.
## Important Files
- `nx.json` - Nx workspace configuration with task definitions
- `tsconfig.base.json` - Base TypeScript configuration
- `package.json` - Root package with workspace definitions
- `.cursor/rules/` - Development guidelines and best practices
- `.cursor/rules/` - Detailed development guidelines and best practices
+8 -2
View File
@@ -11,7 +11,9 @@ import unicornPlugin from 'eslint-plugin-unicorn';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
import jsoncParser from 'jsonc-eslint-parser';
const twentyRules = await nxPlugin.loadWorkspaceRules('packages/twenty-eslint-rules');
const twentyRules = await nxPlugin.loadWorkspaceRules(
'packages/twenty-eslint-rules',
);
export default [
// Base JavaScript configuration
@@ -65,6 +67,10 @@ export default [
sourceTag: 'scope:sdk',
onlyDependOnLibsWithTags: ['scope:sdk', 'scope:shared'],
},
{
sourceTag: 'scope:create-app',
onlyDependOnLibsWithTags: ['scope:create-app', 'scope:shared'],
},
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
@@ -197,7 +203,7 @@ export default [
plugins: {
...mdxPlugin.flat.plugins,
'@nx': nxPlugin,
'twenty': { rules: twentyRules },
twenty: { rules: twentyRules },
},
},
mdxPlugin.flatCodeBlocks,
+18 -11
View File
@@ -15,9 +15,12 @@
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a readytorun project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zeroconfig project bootstrap
- Preconfigured scripts for auth, generate, dev sync, oneoff sync, uninstall
- Preconfigured scripts for auth, dev mode (watch & sync), generate, uninstall, and function management
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
@@ -32,7 +35,7 @@ cd my-twenty-app
corepack enable
yarn install
# Get Help
# Get help
yarn run help
# Authenticate using your API key (you'll be prompted)
@@ -44,29 +47,33 @@ yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn app:generate
# Start dev mode: automatically syncs local changes to your workspace
# Start dev mode: watches, builds, and syncs local changes to your workspace
yarn app:dev
# Or run a onetime sync
yarn app:sync
# Watch your application's functions logs
# Watch your application's function logs
yarn function:logs
# Execute a function with a JSON payload
yarn function:execute -n my-function -p '{"key": "value"}'
# Uninstall the application from the current workspace
yarn app:uninstall
```
## What gets scaffolded
- A minimal app structure ready for Twenty
- A minimal app structure ready for Twenty with example files:
- `application-config.ts` - Application metadata configuration
- `roles/default-role.ts` - Default role for logic functions
- `logic-functions/hello-world.ts` - Example logic function with HTTP trigger
- `front-components/hello-world.tsx` - Example front component
- TypeScript configuration
- Prewired scripts that wrap the `twenty` CLI from twenty-sdk
- Example placeholders to help you add entities, actions, and sync logic
## Next steps
- Explore the generated project and add your first entity with `yarn entity:add`.
- Use `yarn auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn entity:add` (logic functions, front components, objects, roles).
- Use `yarn app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Keep your types uptodate using `yarn app:generate`.
- Use `yarn app:dev` while you iterate to see changes instantly in your workspace.
## Publish your application
+8 -99
View File
@@ -1,111 +1,20 @@
import js from '@eslint/js';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import prettierPlugin from 'eslint-plugin-prettier';
import baseConfig from '../../eslint.config.mjs';
export default [
js.configs.recommended,
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
// Node.js globals
process: 'readonly',
console: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
global: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
// Browser globals that Node.js also has
URL: 'readonly',
URLSearchParams: 'readonly',
// Node.js types
NodeJS: 'readonly',
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
prettier: prettierPlugin,
},
ignores: ['**/dist/**'],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
...typescriptEslint.configs.recommended.rules,
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
'no-useless-escape': 'off',
},
},
{
files: ['**/*.js'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
process: 'readonly',
console: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
global: 'readonly',
},
},
},
{
files: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**/*.ts'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
// Node.js globals
process: 'readonly',
console: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
global: 'readonly',
// Jest globals
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
jest: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
prettier: prettierPlugin,
},
rules: {
...typescriptEslint.configs.recommended.rules,
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
'no-useless-escape': 'off',
'no-console': 'off',
},
},
{
ignores: ['dist/**', 'node_modules/**'],
ignores: ['src/**/*.ts', '!src/cli/**/*.ts'],
},
];
+9 -3
View File
@@ -1,14 +1,18 @@
{
"name": "create-twenty-app",
"version": "0.3.1",
"version": "0.5.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
"files": [
"dist/**/*"
"dist",
"README.md",
"package.json"
],
"scripts": {
"build": "npx rimraf dist && npx vite build"
"build": "npx rimraf dist && npx vite build",
"prepublishOnly": "tsx ../twenty-utils/pack-scripts/pre-publish-only.ts",
"postpublish": "tsx ../twenty-utils/pack-scripts/post-publish.ts"
},
"keywords": [
"twenty",
@@ -34,6 +38,7 @@
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
"twenty-shared": "workspace:*",
"uuid": "^13.0.0"
},
"devDependencies": {
@@ -43,6 +48,7 @@
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.startcase": "^4",
"@types/node": "^20.0.0",
"typescript": "^5.9.2",
"vite": "^7.0.0",
"vite-plugin-dts": "^4.5.4",
"vite-tsconfig-paths": "^4.2.1"
@@ -14,12 +14,6 @@ Then, start development mode to sync your app and watch for changes:
yarn app:dev
```
Or run a one-time sync:
```bash
yarn app:sync
```
Open your Twenty instance and go to `/settings/applications` section to see the result.
## Available Commands
@@ -29,11 +23,12 @@ Open your Twenty instance and go to `/settings/applications` section to see the
yarn auth:login # Authenticate with Twenty
yarn auth:logout # Remove credentials
yarn auth:status # Check auth status
yarn auth:switch # Switch default workspace
yarn auth:list # List all configured workspaces
# Application
yarn app:dev # Start dev mode (sync + watch)
yarn app:sync # One-time sync
yarn entity:add # Add a new entity (function, object, role)
yarn app:dev # Start dev mode (watch, build, and sync)
yarn entity:add # Add a new entity (function, front-component, object, role)
yarn app:generate # Generate typed Twenty client
yarn function:logs # Stream function logs
yarn function:execute # Execute a function with JSON payload
@@ -12,6 +12,9 @@ jest.mock('fs-extra', () => {
};
});
const APPLICATION_FILE_NAME = 'application-config.ts';
const DEFAULT_ROLE_FILE_NAME = 'default-role.ts';
describe('copyBaseApplicationProject', () => {
let testAppDirectory: string;
@@ -32,7 +35,7 @@ describe('copyBaseApplicationProject', () => {
}
});
it('should create the correct folder structure with src/app/', async () => {
it('should create the correct folder structure with src/', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
@@ -40,16 +43,16 @@ describe('copyBaseApplicationProject', () => {
appDirectory: testAppDirectory,
});
// Verify src/app/ folder exists
// Verify src/ folder exists
const srcAppPath = join(testAppDirectory, 'src');
expect(await fs.pathExists(srcAppPath)).toBe(true);
// Verify application.config.ts exists in src/app/
const appConfigPath = join(srcAppPath, 'application.config.ts');
// Verify application-config.ts exists in src/
const appConfigPath = join(srcAppPath, APPLICATION_FILE_NAME);
expect(await fs.pathExists(appConfigPath)).toBe(true);
// Verify default-function.role.ts exists in src/app/
const roleConfigPath = join(srcAppPath, 'default-function.role.ts');
// Verify default-role.ts exists in src/
const roleConfigPath = join(srcAppPath, 'roles', DEFAULT_ROLE_FILE_NAME);
expect(await fs.pathExists(roleConfigPath)).toBe(true);
});
@@ -67,8 +70,7 @@ describe('copyBaseApplicationProject', () => {
const packageJson = await fs.readJson(packageJsonPath);
expect(packageJson.name).toBe('my-test-app');
expect(packageJson.version).toBe('0.1.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.3.1');
expect(packageJson.scripts['app:sync']).toBe('twenty app:sync');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.5.0');
expect(packageJson.scripts['app:dev']).toBe('twenty app:dev');
});
@@ -103,7 +105,7 @@ describe('copyBaseApplicationProject', () => {
expect(yarnLockContent).toContain('yarn lockfile v1');
});
it('should create application.config.ts with defineApp and correct values', async () => {
it('should create application-config.ts with defineApplication and correct values', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
@@ -111,22 +113,18 @@ describe('copyBaseApplicationProject', () => {
appDirectory: testAppDirectory,
});
const appConfigPath = join(
testAppDirectory,
'src',
'application.config.ts',
);
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
const appConfigContent = await fs.readFile(appConfigPath, 'utf8');
// Verify it uses defineApp
// Verify it uses defineApplication
expect(appConfigContent).toContain(
"import { defineApp } from 'twenty-sdk'",
"import { defineApplication } from 'twenty-sdk'",
);
expect(appConfigContent).toContain('export default defineApp({');
expect(appConfigContent).toContain('export default defineApplication({');
// Verify it imports the role identifier
expect(appConfigContent).toContain(
"import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from 'src/default-function.role'",
"import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'",
);
// Verify display name and description
@@ -140,11 +138,11 @@ describe('copyBaseApplicationProject', () => {
// Verify it references the role
expect(appConfigContent).toContain(
'functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER',
'defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER',
);
});
it('should create default-function.role.ts with defineRole and correct values', async () => {
it('should create default-role.ts with defineRole and correct values', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
@@ -155,7 +153,8 @@ describe('copyBaseApplicationProject', () => {
const roleConfigPath = join(
testAppDirectory,
'src',
'default-function.role.ts',
'roles',
DEFAULT_ROLE_FILE_NAME,
);
const roleConfigContent = await fs.readFile(roleConfigPath, 'utf8');
@@ -167,7 +166,7 @@ describe('copyBaseApplicationProject', () => {
// Verify it exports the universal identifier constant
expect(roleConfigContent).toContain(
'export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER',
'export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER',
);
// Verify role label includes app name
@@ -183,7 +182,7 @@ describe('copyBaseApplicationProject', () => {
// Verify it has a universalIdentifier (UUID format)
expect(roleConfigContent).toMatch(
/universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER/,
/universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER/,
);
});
@@ -211,11 +210,7 @@ describe('copyBaseApplicationProject', () => {
appDirectory: testAppDirectory,
});
const appConfigPath = join(
testAppDirectory,
'src',
'application.config.ts',
);
const appConfigPath = join(testAppDirectory, 'src', APPLICATION_FILE_NAME);
const appConfigContent = await fs.readFile(appConfigPath, 'utf8');
expect(appConfigContent).toContain("description: ''");
@@ -244,11 +239,11 @@ describe('copyBaseApplicationProject', () => {
// Read both app configs
const firstAppConfig = await fs.readFile(
join(firstAppDir, 'src', 'application.config.ts'),
join(firstAppDir, 'src', APPLICATION_FILE_NAME),
'utf8',
);
const secondAppConfig = await fs.readFile(
join(secondAppDir, 'src', 'application.config.ts'),
join(secondAppDir, 'src', APPLICATION_FILE_NAME),
'utf8',
);
@@ -284,19 +279,19 @@ describe('copyBaseApplicationProject', () => {
appDirectory: secondAppDir,
});
// Read both role configs
const firstRoleConfig = await fs.readFile(
join(firstAppDir, 'src', 'default-function.role.ts'),
join(firstAppDir, 'src', 'roles', DEFAULT_ROLE_FILE_NAME),
'utf8',
);
const secondRoleConfig = await fs.readFile(
join(secondAppDir, 'src', 'default-function.role.ts'),
join(secondAppDir, 'src', 'roles', DEFAULT_ROLE_FILE_NAME),
'utf8',
);
// Extract UUIDs using regex
const uuidRegex =
/DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/;
/DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =\s*'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/;
const firstUuid = firstRoleConfig.match(uuidRegex)?.[1];
const secondUuid = secondRoleConfig.match(uuidRegex)?.[1];
@@ -1,8 +1,9 @@
import * as fs from 'fs-extra';
import { join } from 'path';
import { v4 } from 'uuid';
import { ASSETS_DIR } from 'twenty-shared/application';
const APP_FOLDER = 'src';
const SRC_FOLDER = 'src';
export const copyBaseApplicationProject = async ({
appName,
@@ -21,32 +22,45 @@ export const copyBaseApplicationProject = async ({
await createGitignore(appDirectory);
await createPublicAssetDirectory(appDirectory);
await createYarnLock(appDirectory);
const appFolderPath = join(appDirectory, APP_FOLDER);
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
await fs.ensureDir(appFolderPath);
await fs.ensureDir(sourceFolderPath);
await createDefaultServerlessFunctionRoleConfig({
await createDefaultRoleConfig({
displayName: appDisplayName,
appDirectory: appFolderPath,
appDirectory: sourceFolderPath,
fileFolder: 'roles',
fileName: 'default-role.ts',
});
await createDefaultFrontComponent({
appDirectory: appFolderPath,
appDirectory: sourceFolderPath,
fileFolder: 'front-components',
fileName: 'hello-world.tsx',
});
await createDefaultFunction({
appDirectory: appFolderPath,
appDirectory: sourceFolderPath,
fileFolder: 'logic-functions',
fileName: 'hello-world.ts',
});
await createApplicationConfig({
displayName: appDisplayName,
description: appDescription,
appDirectory: appFolderPath,
appDirectory: sourceFolderPath,
fileName: 'application-config.ts',
});
};
const createPublicAssetDirectory = async (appDirectory: string) => {
await fs.ensureDir(join(appDirectory, ASSETS_DIR));
};
const createYarnLock = async (appDirectory: string) => {
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
@@ -71,7 +85,9 @@ generated
# dev
/dist/
.twenty
.twenty/*
!.twenty/output/
# production
/build
@@ -96,22 +112,26 @@ yarn-error.log*
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
};
const createDefaultServerlessFunctionRoleConfig = async ({
const createDefaultRoleConfig = async ({
displayName,
appDirectory,
fileFolder,
fileName,
}: {
displayName: string;
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineRole } from 'twenty-sdk';
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'${universalIdentifier}';
export default defineRole({
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: '${displayName} default function role',
description: '${displayName} default function role',
canReadAllObjectRecords: true,
@@ -121,13 +141,18 @@ export default defineRole({
});
`;
await fs.writeFile(join(appDirectory, 'default-function.role.ts'), content);
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createDefaultFrontComponent = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
@@ -150,68 +175,72 @@ export default defineFrontComponent({
});
`;
await fs.writeFile(
join(appDirectory, 'hello-world.front-component.tsx'),
content,
);
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createDefaultFunction = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const triggerUniversalIdentifier = v4();
const content = `import { defineFunction } from 'twenty-sdk';
const content = `import { defineLogicFunction } from 'twenty-sdk';
const handler = async (): Promise<{ message: string }> => {
return { message: 'Hello, World!' };
};
export default defineFunction({
// Logic function handler - rename and implement your logic
export default defineLogicFunction({
universalIdentifier: '${universalIdentifier}',
name: 'hello-world-function',
description: 'A sample serverless function',
name: 'hello-world-logic-function',
description: 'A simple logic function',
timeoutSeconds: 5,
handler,
triggers: [
{
universalIdentifier: '${triggerUniversalIdentifier}',
type: 'route',
path: '/hello-world-function',
httpMethod: 'GET',
isAuthRequired: false,
},
],
httpRouteTriggerSettings: {
path: '/hello-world-logic-function',
httpMethod: 'GET',
isAuthRequired: false,
},
});
`;
await fs.writeFile(join(appDirectory, 'hello-world.function.ts'), content);
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createApplicationConfig = async ({
displayName,
description,
appDirectory,
fileFolder,
fileName,
}: {
displayName: string;
description?: string;
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const content = `import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from 'src/default-function.role';
const content = `import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApp({
export default defineApplication({
universalIdentifier: '${v4()}',
displayName: '${displayName}',
description: '${description ?? ''}',
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
`;
await fs.writeFile(join(appDirectory, 'application.config.ts'), content);
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createPackageJson = async ({
@@ -238,8 +267,6 @@ const createPackageJson = async ({
'auth:switch': 'twenty auth:switch',
'auth:list': 'twenty auth:list',
'app:dev': 'twenty app:dev',
'app:build': 'twenty app:build',
'app:sync': 'twenty app:sync',
'entity:add': 'twenty entity:add',
'app:generate': 'twenty app:generate',
'function:logs': 'twenty function:logs',
@@ -250,13 +277,13 @@ const createPackageJson = async ({
'lint:fix': 'eslint --fix',
},
dependencies: {
'twenty-sdk': '0.3.1',
'twenty-sdk': '0.5.0',
},
devDependencies: {
typescript: '^5.9.3',
'@types/node': '^24.7.2',
'@types/react': '^19.0.2',
react: '^19.0.2',
'@types/react': '^18.2.0',
react: '^18.2.0',
eslint: '^9.32.0',
'typescript-eslint': '^8.50.0',
},
+2 -1
View File
@@ -12,7 +12,8 @@
"types": ["jest", "node"],
"paths": {
"@/*": ["./src/*"]
}
},
"jsx": "react"
},
"include": [
"src/**/*.ts",
@@ -13,7 +13,7 @@ const config: ApplicationConfig = {
isSecret: false,
},
},
functionRoleUniversalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
defaultRoleUniversalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
};
export default config;
@@ -95,7 +95,7 @@ export class PostCard {
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note.universalIdentifier,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
@@ -14,7 +14,7 @@ export const functionRole: RoleConfig = {
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -23,8 +23,8 @@ export const functionRole: RoleConfig = {
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -8,8 +8,25 @@
"yarn": ">=4.0.2"
},
"packageManager": "[email protected]",
"scripts": {
"auth:login": "twenty auth login",
"auth:logout": "twenty auth logout",
"auth:status": "twenty auth status",
"auth:switch": "twenty auth switch",
"auth:list": "twenty auth list",
"app:dev": "twenty app dev",
"app:sync": "twenty app sync",
"entity:add": "twenty entity add",
"app:generate": "twenty app generate",
"function:logs": "twenty function logs",
"function:execute": "twenty function execute",
"app:uninstall": "twenty app uninstall",
"help": "twenty help",
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"twenty-sdk": "0.0.4"
"twenty-sdk": "0.3.1"
},
"devDependencies": {
"@types/node": "^24.7.2"
@@ -1,6 +1,6 @@
import { type ApplicationConfig } from 'twenty-sdk/application';
import { defineApp } from 'twenty-sdk';
const config: ApplicationConfig = {
export default defineApp({
universalIdentifier: '94f7db30-59e5-4b09-a5fe-64cd3d4a65b0',
displayName: 'Self Hosting',
description: 'Used to manage billing and telemetry of self-hosted instances',
@@ -16,6 +16,4 @@ const config: ApplicationConfig = {
isSecret: false,
},
},
};
export default config;
});
@@ -0,0 +1,18 @@
import { FieldType, defineObject } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
nameSingular: 'selfHostingUser',
namePlural: 'selfHostingUsers',
labelSingular: 'Self Hosting User',
labelPlural: 'Self Hosting Users',
fields: [
{
type: FieldType.EMAILS,
name: 'email',
label: 'Email',
description: 'The email of the self hosting user',
universalIdentifier: 'a4b7892c-431a-4d44-973e-a5481652704f',
},
],
});
@@ -1,5 +1,20 @@
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
import { createClient } from '../generated';
import { defineFunction } from 'twenty-sdk';
import { createClient } from '../../generated';
// TODO: import from twenty-sdk when 0.4.0 is deployed
type ServerlessFunctionEvent<TBody = object> = {
headers: Record<string, string | undefined>;
queryStringParameters: Record<string, string | undefined>;
pathParameters: Record<string, string | undefined>;
body: TBody | null;
isBase64Encoded: boolean;
requestContext: {
http: {
method: string;
path: string;
};
};
};
type TelemetryEventPayload = {
action: string;
@@ -22,10 +37,10 @@ type TelemetryEventPayload = {
};
export const main = async (
params: TelemetryEventPayload,
params: ServerlessFunctionEvent<TelemetryEventPayload>,
): Promise<{ success: boolean; message: string; error?: string }> => {
try {
const { action, payload } = params;
const { action, payload } = params.body || {};
if (action !== 'user_signup') {
return {
@@ -69,7 +84,10 @@ export const main = async (
createSelfHostingUser: {
__args: {
data: {
name: payload?.payload?.events?.[0]?.userFirstName + ' ' + payload?.payload?.events?.[0]?.userLastName,
name:
payload?.payload?.events?.[0]?.userFirstName +
' ' +
payload?.payload?.events?.[0]?.userLastName,
email: {
primaryEmail: userEmail,
additionalEmails: null,
@@ -97,10 +115,11 @@ export const main = async (
}
};
export const config: ServerlessFunctionConfig = {
export default defineFunction({
universalIdentifier: '10104201-622b-4a5e-9f27-8f2af19b2a3c',
name: 'telemetry-webhook',
timeoutSeconds: 5,
handler: main,
triggers: [
{
universalIdentifier: '7c8e3f5a-9b4c-4d1e-8f2a-1b3c4d5e6f7a',
@@ -110,5 +129,4 @@ export const config: ServerlessFunctionConfig = {
isAuthRequired: false,
},
],
};
});
@@ -1,18 +0,0 @@
import { FieldMetadata, FieldMetadataType, ObjectMetadata } from 'twenty-sdk/application';
@ObjectMetadata({
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
nameSingular: 'selfHostingUser',
namePlural: 'selfHostingUsers',
labelSingular: 'Self Hosting User',
labelPlural: 'Self Hosting Users',
})
export class SelfHostingUser {
@FieldMetadata({
type: FieldMetadataType.EMAILS,
label: 'Email',
description: 'The email of the self hosting user',
universalIdentifier: '06f3fb53-599e-4c6b-9df6-8f731973afd7',
})
email: object;
}
@@ -20,7 +20,11 @@
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"paths": {
"src/*": ["./src/*"],
"~/*": ["./*"]
}
},
"exclude": [
"node_modules",
File diff suppressed because it is too large Load Diff
@@ -42,25 +42,50 @@
{{- regexFind "\\|(.+)$" . | trimPrefix "|" -}}
{{- end -}}
{{/* Compose DB connection URL */}}
{{- define "twenty.dbUrl" -}}
{{- if .Values.server.env.PG_DATABASE_URL -}}
{{- .Values.server.env.PG_DATABASE_URL -}}
{{- else if .Values.db.enabled -}}
{{- $host := printf "%s-db" (include "twenty.fullname" .) -}}
{{- $user := .Values.db.internal.appUser | default "twenty_app_user" -}}
{{- $pass := .Values.db.internal.appPassword | default (randAlphaNum 32) -}}
{{- $db := .Values.db.internal.database | default "twenty" -}}
{{- printf "postgres://%s:%s@%s.%s.svc.cluster.local/%s" $user $pass $host (include "twenty.namespace" .) $db -}}
{{/* Check if using external secret for database password */}}
{{- define "twenty.db.useExternalSecret" -}}
{{- if and (not .Values.db.enabled) .Values.db.external.secretName .Values.db.external.passwordKey -}}
true
{{- else -}}
false
{{- end -}}
{{- end -}}
{{/* Database URL secret name */}}
{{- define "twenty.dbUrl.secretName" -}}
{{- printf "%s-db-url" (include "twenty.fullname" .) -}}
{{- end -}}
{{/* Database password secret name */}}
{{- define "twenty.dbPassword.secretName" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- .Values.db.external.secretName -}}
{{- else -}}
{{- include "twenty.dbUrl.secretName" . -}}
{{- end -}}
{{- end -}}
{{/* Database password secret key */}}
{{- define "twenty.dbPassword.secretKey" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- .Values.db.external.passwordKey -}}
{{- else if .Values.db.enabled -}}
appPassword
{{- else -}}
password
{{- end -}}
{{- end -}}
{{/* Database URL template for external secret (will be evaluated at runtime) */}}
{{- define "twenty.dbUrl.template" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- $scheme := "postgres" -}}
{{- $host := .Values.db.external.host -}}
{{- $port := .Values.db.external.port | default 5432 -}}
{{- $user := .Values.db.external.user | default "postgres" -}}
{{- $pass := .Values.db.external.password | default "postgres" -}}
{{- $db := .Values.db.external.database | default "twenty" -}}
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
{{- printf "%s://%s:%s@%s:%v/%s%s" $scheme $user $pass $host $port $db $qs -}}
{{- printf "%s://%s:$(DB_PASSWORD)@%s:%v/%s%s" $scheme $user $host $port $db $qs -}}
{{- end -}}
{{- end -}}
@@ -78,9 +103,11 @@
{{- end -}}
{{- end -}}
{{/* Compose Server URL from ingress, else service */}}
{{/* Compose Server URL from override, ingress, or service */}}
{{- define "twenty.serverUrl" -}}
{{- if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) -}}
{{- if .Values.server.env.SERVER_URL -}}
{{- .Values.server.env.SERVER_URL -}}
{{- else if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) -}}
{{- $host := (index .Values.server.ingress.hosts 0).host -}}
{{- $tls := gt (len .Values.server.ingress.tls) 0 -}}
{{- $scheme := ternary "https" "http" $tls -}}
@@ -116,11 +116,21 @@ spec:
- >-
npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource
env:
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbPassword.secretName" . }}
key: {{ include "twenty.dbPassword.secretKey" . }}
- name: PG_DATABASE_URL
value: {{ include "twenty.dbUrl.template" . | quote }}
{{- else }}
- name: PG_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-url
name: {{ include "twenty.dbUrl.secretName" . }}
key: url
{{- end }}
containers:
- name: server
{{- $img := include "twenty.server.image" . }}
@@ -129,11 +139,21 @@ spec:
env:
- name: SERVER_URL
value: {{ include "twenty.serverUrl" . | quote }}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbPassword.secretName" . }}
key: {{ include "twenty.dbPassword.secretKey" . }}
- name: PG_DATABASE_URL
value: {{ include "twenty.dbUrl.template" . | quote }}
{{- else }}
- name: PG_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-url
name: {{ include "twenty.dbUrl.secretName" . }}
key: url
{{- end }}
- name: REDIS_URL
value: {{ include "twenty.redisUrl" . | quote }}
- name: SIGN_IN_PREFILLED
@@ -52,11 +52,21 @@ spec:
env:
- name: SERVER_URL
value: {{ include "twenty.serverUrl" . | quote }}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbPassword.secretName" . }}
key: {{ include "twenty.dbPassword.secretKey" . }}
- name: PG_DATABASE_URL
value: {{ include "twenty.dbUrl.template" . | quote }}
{{- else }}
- name: PG_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-url
name: {{ include "twenty.dbUrl.secretName" . }}
key: url
{{- end }}
- name: REDIS_URL
value: {{ include "twenty.redisUrl" . | quote }}
- name: STORAGE_TYPE
@@ -1,4 +1,5 @@
{{- $secretName := printf "%s-db-url" (include "twenty.fullname" .) -}}
{{- if .Values.db.enabled -}}
{{- $existingSecret := lookup "v1" "Secret" (include "twenty.namespace" .) $secretName -}}
{{- $appPassword := "" -}}
{{- if $existingSecret -}}
@@ -20,3 +21,25 @@ type: Opaque
stringData:
url: {{ printf "postgres://%s:%s@%s-db.%s.svc.cluster.local/%s" (urlquery $appUser) (urlquery $appPassword) (include "twenty.fullname" .) (include "twenty.namespace" .) (.Values.db.internal.database | default "twenty") | quote }}
appPassword: {{ $appPassword | quote }}
{{- else if not .Values.db.external.secretName -}}
{{- $scheme := "postgres" -}}
{{- $host := .Values.db.external.host -}}
{{- $port := .Values.db.external.port | default 5432 -}}
{{- $user := .Values.db.external.user | default "postgres" -}}
{{- $pass := .Values.db.external.password | default "" -}}
{{- $db := .Values.db.external.database | default "twenty" -}}
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $secretName }}
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
type: Opaque
stringData:
url: {{ printf "%s://%s:%s@%s:%v/%s%s" $scheme (urlquery $user) (urlquery $pass) $host $port $db $qs | quote }}
password: {{ $pass | quote }}
{{- end -}}
@@ -45,6 +45,7 @@
"env": {
"type": "object",
"properties": {
"SERVER_URL": { "type": "string", "description": "Override for the server URL (e.g., https://crm.example.com). If not set, derived from ingress or service." },
"NODE_PORT": { "type": "integer", "minimum": 1 },
"PG_DATABASE_URL": { "type": "string" },
"REDIS_URL": { "type": "string" },
@@ -19,8 +19,13 @@ storage:
bucket: ""
region: ""
endpoint: ""
# Option A: direct values
accessKeyId: ""
secretAccessKey: ""
# Option B: reference a Secret
# secretName: my-s3-creds
# accessKeyIdKey: accessKeyId
# secretAccessKeyKey: secretAccessKey
# Auth tokens (random if not provided)
secrets:
@@ -137,6 +142,8 @@ db:
password: ""
database: twenty
ssl: false
secretName: ""
passwordKey: ""
# Redis
redisInternal:
+2
View File
@@ -14,6 +14,7 @@ COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
COPY ./packages/twenty-sdk/package.json /app/packages/twenty-sdk/
# Install all dependencies
RUN yarn && yarn cache clean && npx nx reset
@@ -39,6 +40,7 @@ ARG REACT_APP_SERVER_BASE_URL
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-ui /app/packages/twenty-ui
COPY ./packages/twenty-shared /app/packages/twenty-shared
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
RUN npx nx build twenty-front
@@ -17,7 +17,10 @@ setup_and_migrate_db() {
yarn database:migrate:prod
fi
yarn command:prod cache:flush
yarn command:prod upgrade
yarn command:prod cache:flush
echo "Successfully migrated DB!"
}
@@ -159,6 +159,12 @@ You should run all commands in the following steps from the root of the project.
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
This creates a superuser role named `postgres` with login access.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Option 2:** If you have docker installed:
```bash
@@ -9,16 +9,13 @@ Apps are currently in alpha testing. The feature is functional but still evolvin
## What Are Apps?
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and logic functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
**What you can do today:**
- Define custom objects and fields as code (managed data model)
- Build serverless functions with custom triggers
- Build logic functions with custom triggers
- Deploy the same app across multiple workspaces
**Coming soon:**
- Custom UI layouts and components
## Prerequisites
- Node.js 24+ and Yarn 4
@@ -48,15 +45,12 @@ From here you can:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn app:sync
# Watch your application's functions logs
# Watch your application's function logs
yarn function:logs
# Execute a function by name
@@ -66,7 +60,7 @@ yarn function:execute -n my-function -p '{"name": "test"}'
yarn app:uninstall
# Display commands' help
yarn app:help
yarn help
```
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
@@ -94,79 +88,60 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
src/
app/
application.config.ts # Required - main application configuration
default-function.role.ts # Default role for serverless functions
// your entities (*.object.ts, *.function.ts, *.role.ts)
utils/ # Optional - handler implementations & utilities
```
### Convention-over-configuration
Applications use a **convention-over-configuration** approach where entities are detected by their file suffix. This allows flexible organization within the `src/app/` folder:
| File suffix | Entity type |
|-------------|-------------|
| `*.object.ts` | Custom object definitions |
| `*.function.ts` | Serverless function definitions |
| `*.role.ts` | Role definitions |
### Supported folder organizations
You can organize your entities in any of these patterns:
**Traditional (by type):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**Feature-based:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**Flat:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
├── application-config.ts # Required - main application configuration
├── roles/
└── default-role.ts # Default role for logic functions
├── logic-functions/
│ └── hello-world.ts # Example logic function
└── front-components/
└── hello-world.tsx # Example front component
```
At a high level:
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall`, and authentication commands that delegate to the local `twenty` CLI.
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
- **.nvmrc**: Pins the Node.js version expected by the project.
- **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
- **README.md**: A short README in the app root with basic instructions.
- **src/app/**: The main place where you define your application-as-code:
- `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See "Application config" below.
- `*.role.ts`: Role definitions used by your serverless functions. See "Default function role" below.
- `*.object.ts`: Custom object definitions.
- `*.function.ts`: Serverless function definitions.
- **src/utils/**: Optional folder for handler implementations and utilities.
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- **src/**: The main place where you define your application-as-code
### Entity detection
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
| Helper function | Entity type |
|-----------------|-------------|
| `defineObject()` | Custom object definitions |
| `defineLogicFunction()` | Logic function definitions |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Role definitions |
| `defineField()` | Field extensions for existing objects |
<Note>
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
</Note>
Example of a detected entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Later commands will add more files and folders:
- `yarn app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
- `yarn app:create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
l
- `yarn entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
## Authentication
@@ -207,16 +182,18 @@ The twenty-sdk provides typed building blocks and helper functions you use insid
### Helper functions
The SDK provides four helper functions with built-in validation for defining your app entities:
The SDK provides helper functions for defining your app entities. As described in [Entity detection](#entity-detection), you must use `export default define<Entity>({...})` for your entities to be detected:
| Function | Purpose |
|----------|---------|
| `defineApp()` | Configure application metadata |
| `defineApplication()` | Configure application metadata (required, one per app) |
| `defineObject()` | Define custom objects with fields |
| `defineFunction()` | Define serverless functions with handlers |
| `defineLogicFunction()` | Define logic functions with handlers |
| `defineFrontComponent()` | Define front components for custom UI |
| `defineRole()` | Configure role permissions and object access |
| `defineField()` | Extend existing objects with additional fields |
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
These functions validate your configuration at build time and provide IDE autocompletion and type safety.
### Defining objects
@@ -297,80 +274,29 @@ Key points:
- The `universalIdentifier` must be unique and stable across deployments.
- Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`.
- The `fields` array is optional — you can define objects without custom fields.
- You can scaffold new objects using `yarn app:create-entity`, which guides you through naming, fields, and relationships.
- You can scaffold new objects using `yarn entity:add`, which guides you through naming, fields, and relationships.
<Note>
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
</Note>
<Accordion title="Alternative: Decorator-based syntax">
You can also define objects using TypeScript decorators. This approach uses class-based syntax with `@Object`, `@Field`, and `@Relation` decorators:
```typescript
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
import { type Note } from '../../generated';
### Application config (application-config.ts)
@Object({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
icon: 'IconMail',
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
}
```
Note: The decorator approach requires `experimentalDecorators` in your TypeScript config.
</Accordion>
### Application config (application.config.ts)
Every app has a single `application.config.ts` file that describes:
Every app has a single `application-config.ts` file that describes:
- **Who the app is**: identifiers, display name, and description.
- **How its functions run**: which role they use for permissions.
- **(Optional) variables**: keyvalue pairs exposed to your functions as environment variables.
Use `defineApp()` to define your application configuration:
Use `defineApplication()` to define your application configuration:
```typescript
// src/app/application.config.ts
import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApp({
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
@@ -383,18 +309,18 @@ export default defineApp({
isSecret: false,
},
},
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
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 (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `functionRoleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
- `defaultRoleUniversalIdentifier` must match the role file (see below).
#### Roles and permissions
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's serverless functions.
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions.
- The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
- The typed client will be restricted to the permissions granted to that role.
@@ -405,14 +331,14 @@ Applications can define roles that encapsulate permissions on your workspace's o
When you scaffold a new app, the CLI also creates a default role file. Use `defineRole()` to define roles with built-in validation:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
@@ -425,7 +351,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -434,8 +360,8 @@ export default defineRole({
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -444,10 +370,10 @@ export default defineRole({
});
```
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
The `universalIdentifier` of this role is then referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`. In other words:
- **\*.role.ts** defines what the default function role can do.
- **application.config.ts** points to that role so your functions inherit its permissions.
- **application-config.ts** points to that role so your functions inherit its permissions.
Notes:
- Start from the scaffolded role, then progressively restrict it following leastprivilege.
@@ -455,22 +381,17 @@ Notes:
- `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
- See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Serverless function config and entrypoint
### Logic function config and entrypoint
Each function file uses `defineFunction()` to export a configuration with a handler and optional triggers. Use the `*.function.ts` file suffix for automatic detection.
Each function file uses `defineLogicFunction()` to export a configuration with a handler and optional triggers.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
const handler = async (
params:
| RoutePayload
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload,
) => {
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
@@ -486,7 +407,7 @@ const handler = async (
return result;
};
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
@@ -501,18 +422,18 @@ export default defineFunction({
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
{
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
type: 'cron',
pattern: '0 0 1 1 *',
},
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
{
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
type: 'databaseEvent',
eventName: 'person.updated',
updatedFields: ['name'],
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
@@ -552,10 +473,10 @@ const handler = async (event: RoutePayload) => {
**To migrate existing functions:** Update your handler to destructure from `event.body`, `event.queryStringParameters`, or `event.pathParameters` instead of directly from the params object.
</Warning>
When a route trigger invokes your function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
@@ -582,10 +503,10 @@ The `RoutePayload` type has the following structure:
### Forwarding HTTP headers
By default, HTTP headers from incoming requests are **not** passed to your serverless function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
```typescript
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
@@ -620,23 +541,59 @@ const handler = async (event: RoutePayload) => {
You can create new functions in two ways:
- **Scaffolded**: Run `yarn app:create-entity` and choose the option to add a new function. This generates a starter file with a handler and config.
- **Manual**: Create a new `*.function.ts` file and use `defineFunction()`, following the same pattern.
- **Scaffolded**: Run `yarn entity:add` and choose the option to add a new logic function. This generates a starter file with a handler and config.
- **Manual**: Create a new `*.logic-function.ts` file and use `defineLogicFunction()`, following the same pattern.
### Front components
Front components let you build custom React components that render within Twenty's UI. Use `defineFrontComponent()` to define components with built-in validation:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>My Custom Widget</h1>
<p>This is a custom front component for Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-widget',
description: 'A custom widget component',
component: MyWidget,
});
```
Key points:
- Front components are React components that render in isolated contexts within Twenty.
- Use the `*.front-component.tsx` file suffix for automatic detection.
- The `component` field references your React component.
- Components are built and synced automatically during `yarn app:dev`.
You can create new front components in two ways:
- **Scaffolded**: Run `yarn entity:add` and choose the option to add a new front component.
- **Manual**: Create a new `*.front-component.tsx` file and use `defineFrontComponent()`.
### Generated typed client
Run yarn app:generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
```typescript
import Twenty from './generated';
import Twenty from '~/generated';
const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
The client is re-generated by `yarn app:generate`. Re-run after changing your objects and `yarn app:sync` or when onboarding to a new workspace.
The client is re-generated by `yarn app:generate`. Re-run after changing your objects or when onboarding to a new workspace.
#### Runtime credentials in serverless functions
#### Runtime credentials in logic functions
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
@@ -645,13 +602,13 @@ When your function runs on Twenty, the platform injects credentials as environme
Notes:
- You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
- The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
- Applications can define roles to follow leastprivilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role's universal identifier.
- The API key's permissions are determined by the role referenced in your `application-config.ts` via `defaultRoleUniversalIdentifier`. This is the default role used by logic functions of your application.
- Applications can define roles to follow leastprivilege. Grant only the permissions your functions need, then point `defaultRoleUniversalIdentifier` to that role's universal identifier.
### Hello World example
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
Explore a minimal, end-to-end example that demonstrates objects, logic functions, front components, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Manual setup (without the scaffolder)
@@ -666,25 +623,29 @@ Then add scripts like these:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"help": "twenty help"
}
}
```
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:sync`, etc.
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:generate`, etc.
## Troubleshooting
- Authentication errors: run `yarn auth:login` and ensure your API key has the required permissions.
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
- Types or client missing/outdated: run `yarn app:generate` and then `yarn app:dev`.
- Types or client missing/outdated: run `yarn app:generate`.
- Dev mode not syncing: ensure `yarn app:dev` is running and that changes are not ignored by your environment.
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -289,19 +289,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
</Warning>
## Serverless Functions
## Logic Functions
Twenty supports serverless functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
Twenty supports logic functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
<Warning>
**Security Notice:** The local serverless driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
**Security Notice:** The local driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
</Warning>
### Available Drivers
| Driver | Environment Variable | Use Case | Security Level |
|--------|---------------------|----------|----------------|
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable serverless functions entirely | N/A |
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable logic functions entirely | N/A |
| Local | `SERVERLESS_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |
@@ -321,11 +321,11 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**To disable serverless functions:**
**To disable logic functions:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a serverless function will return an error. This is useful if you want to run Twenty without serverless function capabilities.
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a logic function will return an error. This is useful if you want to run Twenty without logic function capabilities.
</Note>
File diff suppressed because it is too large Load Diff
@@ -170,6 +170,13 @@ cd twenty
يقوم هذا بإنشاء دور مشرف نظام باسم `postgres` مع إمكانية تسجيل الدخول.
```bash
اسم الدور | الخصائص | عضو في
-----------+-------------+-----------
postgres | مشرف نظام | {}
john | مشرف نظام | {}
```
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
@@ -9,18 +9,14 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
## ما هي التطبيقات؟
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف بلا خادم في الكود — مما يجعل الإنشاء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف المنطق في الكود — مما يجعل البناء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
**ما الذي يمكنك فعله اليوم:**
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
* أنشئ وظائف بلا خادم مع مشغلات مخصصة
* أنشئ وظائف منطقية مع مشغلات مخصصة
* انشر التطبيق نفسه عبر مساحات عمل متعددة
**قريبًا:**
* تخطيطات ومكونات واجهة مستخدم مخصصة
## المتطلبات الأساسية
* Node.js 24+ وYarn 4
@@ -50,15 +46,12 @@ yarn app:dev
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn app:sync
# Watch your application's functions logs
# Watch your application's function logs
yarn function:logs
# Execute a function by name
@@ -68,7 +61,7 @@ yarn function:execute -n my-function -p '{"name": "test"}'
yarn app:uninstall
# Display commands' help
yarn app:help
yarn help
```
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
@@ -96,82 +89,61 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # مجلد الأصول العامة (صور، خطوط، إلخ)
src/
app/
application.config.ts # مطلوب - التكوين الرئيسي للتطبيق
default-function.role.ts # الدور الافتراضي للوظائف بدون خادم
// الكيانات الخاصة بك (*.object.ts, *.function.ts, *.role.ts)
utils/ # اختياري - تنفيذات المُعالِجات والأدوات المساعدة
```
### الاتفاقية فوق التهيئة
تستخدم التطبيقات نهج **الاتفاقية فوق التهيئة** حيث تُكتشف الكيانات عبر لاحقة اسم الملف. يتيح ذلك تنظيمًا مرنًا داخل مجلد `src/app/`:
| لاحقة الملف | نوع الكيان |
| --------------- | ---------------------- |
| `*.object.ts` | تعريفات كائنات مخصصة |
| `*.function.ts` | تعريفات وظائف بلا خادم |
| `*.role.ts` | تعريفات الأدوار |
### طرق تنظيم المجلدات المدعومة
يمكنك تنظيم الكيانات بأي من الأنماط التالية:
**تقليدي (حسب النوع):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**حسب الميزة:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**مسطح:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
├── application-config.ts # مطلوب - التكوين الرئيسي للتطبيق
├── roles/
└── default-role.ts # الدور الافتراضي لوظائف المنطق
├── logic-functions/
└── hello-world.ts # مثال لوظيفة منطقية
└── front-components/
└── hello-world.tsx # مثال لمكوّن الواجهة الأمامية
```
بشكل عام:
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `dev` و`sync` و`generate` و`create-entity` و`logs` و`uninstall` و`auth` التي تفوِّض إلى `twenty` CLI المحلي.
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `app:dev` و`app:generate` و`entity:add` و`function:logs` و`function:execute` و`app:uninstall` وأوامر المصادقة التي تُفوِّض إلى `twenty` CLI المحلي.
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **eslint.config.mjs** و**tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **src/app/**: المكان الرئيسي حيث تعرّف تطبيقك ككود:
* `application.config.ts`: التكوين العام لتطبيقك (بيانات وصفية وربط وقت التشغيل). انظر "تكوين التطبيق" أدناه.
* `*.role.ts`: تعريفات الأدوار المستخدمة بواسطة وظائفك بلا خادم. انظر "الدور الافتراضي للوظيفة" أدناه.
* `*.object.ts`: تعريفات كائنات مخصصة.
* `*.function.ts`: تعريفات وظائف بلا خادم.
* **src/utils/**: مجلد اختياري لتنفيذات المعالجات والأدوات المساعدة.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
### اكتشاف الكيانات
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
| دالة مساعدة | نوع الكيان |
| ------------------------ | --------------------------------- |
| `defineObject()` | تعريفات كائنات مخصصة |
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | تعريفات الأدوار |
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
<Note>
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
</Note>
مثال على كيان تم اكتشافه:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn app:generate` سيُنشئ مجلدًا `generated/` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* `yarn app:create-entity` سيضيف ملفات تعريف الكيانات تحت `src/app/` لكائناتك المخصصة أو الوظائف أو الأدوار.
l
* `yarn entity:add` سيضيف ملفات تعريف الكيانات تحت `src/` لكائناتك المخصصة أو الوظائف أو المكونات الواجهية أو الأدوار.
## المصادقة
@@ -212,16 +184,18 @@ Once you've switched workspaces with `auth:switch`, all subsequent commands will
### دوال مساعدة
يوفّر SDK أربع دوال مساعدة مع تحقق مدمج لتعريف كيانات تطبيقك:
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
| دالة | الغرض |
| ------------------ | ---------------------------------------- |
| `defineApp()` | تهيئة بيانات التطبيق الوصفية |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineFunction()` | تعريف وظائف بلا خادم مع معالجات |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| دالة | الغرض |
| ------------------------ | ---------------------------------------------------- |
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
تتحقق هذه الدوال من تكوينك في وقت التشغيل وتوفر إكمالًا تلقائيًا أفضل في بيئة التطوير وأمان أنواع أعلى.
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
### تعريف الكائنات
@@ -302,79 +276,28 @@ export default defineObject({
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
* يمكنك إنشاء كائنات جديدة باستخدام `yarn app:create-entity`، والذي يرشدك خلال التسمية والحقول والعلاقات.
* يمكنك إنشاء كائنات جديدة باستخدام `yarn entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
<Note>
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية مثل `name` و`createdAt` و`updatedAt` و`createdBy` و`position` و`deletedAt`. لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
</Note>
<Accordion title="بديل: صياغة قائمة على المزيّنات">
يمكنك أيضًا تعريف كائنات باستخدام مزيّنات TypeScript. يستخدم هذا النهج صياغة معتمدة على الأصناف مع مزيّنات `@Object` و`@Field` و`@Relation`:
### تكوين التطبيق (application-config.ts)
```typescript
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
import { type Note } from '../../generated';
@Object({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
icon: 'IconMail',
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
}
```
ملاحظة: يتطلب نهج المزيّنات `experimentalDecorators` في تهيئة TypeScript لديك.
</Accordion>
### تكوين التطبيق (application.config.ts)
كل تطبيق لديه ملف واحد `application.config.ts` يصف:
كل تطبيق لديه ملف واحد `application-config.ts` يصف:
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
استخدم `defineApp()` لتعريف تهيئة تطبيقك:
Use `defineApplication()` to define your application configuration:
```typescript
// src/app/application.config.ts
import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApp({
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
@@ -387,7 +310,7 @@ export default defineApp({
isSecret: false,
},
},
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
@@ -395,11 +318,11 @@ export default defineApp({
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` يجب أن يطابق الدور الذي تعرّفه في ملف `*.role.ts` (انظر أدناه).
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
#### الأدوار والصلاحيات
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `functionRoleUniversalIdentifier` في `application.config.ts` الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك.
* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا.
* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور.
@@ -410,14 +333,14 @@ export default defineApp({
عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
@@ -430,7 +353,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -439,8 +362,8 @@ export default defineRole({
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -449,10 +372,10 @@ export default defineRole({
});
```
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application.config.ts` باسم `functionRoleUniversalIdentifier`. بعبارة أخرى:
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى:
* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة.
* **application.config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك صلاحياته.
* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته.
الملاحظات:
@@ -461,22 +384,17 @@ export default defineRole({
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه.
* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### تكوين وظيفة بلا خادم ونقطة الدخول
### تكوين الوظيفة المنطقية ونقطة الدخول
كل ملف وظيفة يستخدم `defineFunction()` لتصدير تكوين مع معالج ومشغلات اختيارية. استخدم لاحقة الملف `*.function.ts` للاكتشاف التلقائي.
كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
const handler = async (
params:
| RoutePayload
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload,
) => {
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
@@ -492,7 +410,7 @@ const handler = async (
return result;
};
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
@@ -507,18 +425,18 @@ export default defineFunction({
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
{
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
type: 'cron',
pattern: '0 0 1 1 *',
},
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
{
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
type: 'databaseEvent',
eventName: 'person.updated',
updatedFields: ['name'],
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
@@ -565,10 +483,10 @@ export default defineFunction({
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
</Warning>
عندما يستدعي مشغّل المسار دالتك، فإنه يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
@@ -595,10 +513,10 @@ const handler = async (event: RoutePayload) => {
### تمرير رؤوس HTTP
افتراضيًا، لا يتم تمرير رؤوس HTTP من الطلبات الواردة إلى دالتك بدون خادم لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى وظيفتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
```typescript
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
يمكنك إنشاء وظائف جديدة بطريقتين:
* **مُنشأ بالقالب**: شغّل `yarn app:create-entity` واختر خيار إضافة وظيفة جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
* **يدوي**: أنشئ ملفًا جديدًا `*.function.ts` واستخدم `defineFunction()` مع اتباع النمط نفسه.
* **مُنشأ بالقالب**: شغّل `yarn entity:add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه.
### المكوّنات الأمامية
تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>My Custom Widget</h1>
<p>This is a custom front component for Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-widget',
description: 'A custom widget component',
component: MyWidget,
});
```
النقاط الرئيسية:
* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty.
* استخدم لاحقة الملف `*.front-component.tsx` للاكتشاف التلقائي.
* يشير الحقل `component` إلى مكوّن React الخاص بك.
* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn app:dev`.
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
* **مُنشأ بالقالب**: شغّل `yarn entity:add` واختر خيار إضافة مكوّن أمامي جديد.
* **يدوي**: أنشئ ملفًا جديدًا `*.front-component.tsx` واستخدم `defineFrontComponent()`.
### عميل مُولَّد مضبوط الأنواع
شغّل yarn app:generate لإنشاء عميل محلي مضبوط الأنواع في generated/ استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
```typescript
import Twenty from './generated';
import Twenty from '~/generated';
const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
يُعاد توليد العميل بواسطة `yarn app:generate`. أعِد تشغيله بعد تغيير كائناتك وتشغيل `yarn app:sync` أو عند الانضمام إلى مساحة عمل جديدة.
يُعاد توليد العميل بواسطة `yarn app:generate`. أعِد التشغيل بعد تغيير كائناتك أو عند الانضمام إلى مساحة عمل جديدة.
#### بيانات الاعتماد في وقت التشغيل في الوظائف بلا خادم
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك:
@@ -659,12 +614,12 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
الملاحظات:
* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل.
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application.config.ts` عبر `functionRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `functionRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك.
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
### مثال Hello World
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف ومشغلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## إعداد يدوي (بدون المهيئ)
@@ -679,25 +634,29 @@ yarn add -D twenty-sdk
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"help": "twenty help"
}
}
```
يمكنك الآن تشغيل الأوامر نفسها عبر Yarn، مثل `yarn app:dev` و`yarn app:sync`، إلخ.
يمكنك الآن تشغيل الأوامر نفسها عبر Yarn، مثل `yarn app:dev` و`yarn app:generate`، إلخ.
## استكشاف الأخطاء وإصلاحها
* أخطاء المصادقة: شغّل `yarn auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
* الأنواع أو العميل مفقود/قديم: شغّل `yarn app:generate` ثم `yarn app:dev`.
* الأنواع أو العميل مفقود/قديم: شغّل `yarn app:generate`.
* وضع التطوير لا يزامن: تأكد من أن `yarn app:dev` قيد التشغيل وأن التغييرات ليست متجاهلة من بيئتك.
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
</Warning>
## وظائف بلا خادم
## الوظائف المنطقية
تدعم Twenty وظائف بلا خادم لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
تدعم Twenty الوظائف المنطقية لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
<Warning>
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي لوظائف بلا خادم (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
</Warning>
### برامج التشغيل المتاحة
| برنامج التشغيل | متغير البيئة | حالة الاستخدام | مستوى الأمان |
| -------------- | -------------------------- | ------------------------------- | ----------------------------- |
| معطل | `SERVERLESS_TYPE=DISABLED` | تعطيل وظائف بلا خادم بالكامل | غير متاح |
| معطل | `SERVERLESS_TYPE=DISABLED` | تعطيل الوظائف المنطقية بالكامل | غير متاح |
| محلي | `SERVERLESS_TYPE=LOCAL` | بيئات التطوير والبيئات الموثوقة | منخفض (من دون عزل) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | الإنتاج مع شيفرة غير موثوق بها | مرتفع (عزل على مستوى الأجهزة) |
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**لتعطيل وظائف بلا خادم:**
**لتعطيل الوظائف المنطقية:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة بلا خادم إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون قدرات وظائف بلا خادم.
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة منطقية إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون إمكانات الوظائف المنطقية.
</Note>
@@ -59,4 +59,4 @@ description: ميزات مدعومة بالذكاء الاصطناعي قادم
سنحدّث هذا القسم عندما تصبح ميزات الذكاء الاصطناعي متاحة. وفي هذه الأثناء:
* تابع [GitHub](https://github.com/twentyhq/twenty) للاطّلاع على تحديثات التطوير
* انضم إلى [Discord](https://discord.gg/twenty) لمشاركة الملاحظات وطلبات الميزات
* انضم إلى [Discord](https://discord.gg/UfGNZJfAG6) لمشاركة الملاحظات وطلبات الميزات
@@ -39,12 +39,16 @@ description: اربط السجلات عبر كائنات مختلفة باستخ
**مثال:** يمكن ربط العديد من الأشخاص بالعديد من المشاريع، والعكس صحيح.
<Warning>
**متعدد-إلى-متعدد غير مدعوم بعد.**
تستخدم العلاقات من نوع متعدد إلى متعدد نمط **كائن ربط**: كائن وسيط يربط بين الجانبين. باستخدام ميزة علاقة الربط، تعرض Twenty السجلات المرتبطة النهائية مباشرةً، مع إخفاء الكائن الوسيط من واجهة المستخدم.
هذا النوع من العلاقات مُخطّط للنصف الأول من عام 2026. كحل بديل، أنشئ كائنًا وسيطًا "junction" (مثال: "Project Assignments") لديه علاقات من نوع متعدد-إلى-واحد مع كلا الكائنين.
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
<Warning>
**ميزة المختبر**: يجب تمكين علاقات الربط في **الإعدادات → التحديثات → المختبر** قبل الاستخدام.
</Warning>
راجع [كيفية إنشاء علاقات متعدد-إلى-متعدد](/l/ar/user-guide/data-model/how-tos/create-many-to-many-relations) للحصول على دليل كامل خطوة بخطوة.
## إنشاء حقل علاقة
1. اذهب إلى **الإعدادات → نموذج البيانات**
@@ -0,0 +1,180 @@
---
title: إنشاء علاقات متعدد-إلى-متعدد
description: اربط السجلات حيث يمكن ربط عناصر كثيرة على كلا الجانبين معًا باستخدام كائنات الربط.
---
تتيح علاقات متعدد-إلى-متعدد ربط سجلات متعددة على كلا الجانبين. على سبيل المثال: يمكن للعديد من الأشخاص العمل على العديد من المشاريع، ويمكن لكل مشروع أن يضم العديد من الأشخاص.
<Warning>
**ميزة المختبر**: علاقات الربط متاحة حاليًا في المختبر. فعِّلها في **الإعدادات → التحديثات → المختبر** قبل اتباع هذا الدليل.
</Warning>
<Note>
تتطلب هذه الميزة أيضًا تفعيل **الوضع المتقدم** (زر التبديل في أسفل يمين صفحة الإعدادات).
</Note>
## متى نستخدم علاقات متعدد-إلى-متعدد
استخدم علاقات متعدد-إلى-متعدد عندما يمكن لكل جانب من العلاقة أن يحتوي على عدة ارتباطات:
| العلاقة | مثال |
| ------------------ | --------------------------------------------------------------------- |
| الأشخاص ↔ المشاريع | قد يعمل الشخص على عدة مشاريع؛ ويضم المشروع عدة أعضاء فريق |
| الشركات ↔ الوسوم | يمكن أن تحتوي الشركة على عدة وسوم؛ ويمكن أن ينطبق الوسم على عدة شركات |
| المنتجات ↔ الطلبات | يمكن أن يوجد المنتج في عدة طلبات؛ ويحتوي الطلب على عدة منتجات |
## كيف يعمل
تستخدم Twenty نمط **كائن الربط** لعلاقات متعدد-إلى-متعدد. يوضع كائن الربط بين كائنين ويحتفظ بالارتباطات:
```
People ←→ Project Assignments ←→ Projects
```
يحتوي كائن **تعيينات المشروع** (كائن ربط) على:
* علاقة مع الأشخاص (متعدد-إلى-واحد)
* علاقة مع المشاريع (متعدد-إلى-واحد)
عند تفعيل مفتاح علاقة الربط، تعرض Twenty السجلات المرتبطة مباشرةً بدلًا من إظهار سجلات كائن الربط الوسيطة.
## المتطلبات الأساسية
1. **تفعيل علاقات الربط في المختبر**: انتقل إلى **الإعدادات → التحديثات → المختبر** وفعِّل **علاقات الربط**
2. **فعِّل الوضع المتقدم**: شغِّل **الوضع المتقدم** من أسفل يمين الشريط الجانبي لصفحة الإعدادات
3. خطِّط نموذج البيانات الخاص بك:
* ما الكائنان اللذان ستربطهما؟
* ما الاسم الذي ينبغي أن يُطلق على كائن الربط؟
## الخطوة 1: إنشاء كائن الربط
أولًا، أنشئ الكائن الوسيط الذي سيحتفظ بالارتباطات.
1. اذهب إلى **الإعدادات → نموذج البيانات**
2. انقر **+ كائن جديد**
3. سمِّه تسمية وصفية (مثلًا: "تعيين مشروع"، "عضو فريق"، "طلب منتج")
4. انقر على **حفظ**
<Tip>
**اتفاقية التسمية**: استخدم اسمًا يصف العلاقة، مثل "تعيين مشروع" أو "عضوية الفريق". هذا يجعل نموذج البيانات أسهل في الفهم.
</Tip>
## الخطوة 2: إنشاء علاقات من كائن الربط
أضِف حقول علاقة من كائن الربط إلى كلا الكائنين اللذين تريد ربطهما.
### العلاقة الأولى (كائن الربط → الكائن A)
1. حدِّد كائن الربط في **الإعدادات → نموذج البيانات**
2. انقر **+ إضافة حقل**
3. اختر **العلاقة** كنوع الحقل
4. اختر الكائن الأول (مثلًا، "الأشخاص")
5. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد** (يمكن لعديد من التعيينات الارتباط بشخص واحد)
6. قم بتسمية الحقول:
* الحقل على كائن الربط: مثلًا، "شخص"
* الحقل على الأشخاص: مثلًا، "تعيينات المشروع"
7. انقر على **حفظ**
### العلاقة الثانية (كائن الربط → الكائن B)
1. وأنت ما زلت في كائن الربط، انقر **+ إضافة حقل**
2. اختر **العلاقة** كنوع الحقل
3. اختر الكائن الثاني (مثلًا، "المشاريع")
4. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد**
5. قم بتسمية الحقول:
* الحقل على كائن الربط: مثلًا، "مشروع"
* الحقل على المشاريع: مثلًا، "أعضاء الفريق"
6. انقر على **حفظ**
## الخطوة 3: ضبط عرض علاقة الربط
قم الآن بضبط كائنات المصدر لعرض السجلات المرتبطة مباشرةً، مع تجاوز كائن الربط الوسيط.
1. اذهب إلى **الإعدادات → نموذج البيانات**
2. اختر الكائن الأول (مثلًا، "الأشخاص")
3. اعثر على حقل العلاقة الذي يشير إلى كائن الربط (مثلًا، "تعيينات المشروع")
4. انقر لتحرير الحقل
5. فعّل **"هذه علاقة بكائن ربط"**
6. حدِّد **العلاقة الهدف** (مثلًا، "مشروع" — الحقل على كائن الربط الذي يشير إلى الجانب الآخر)
7. انقر على **حفظ**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
كرِّر على الكائن الآخر:
1. اختر "المشاريع" في نموذج البيانات
2. حرِّر حقل العلاقة "أعضاء الفريق"
3. فعّل مفتاح الربط
4. حدِّد "شخص" كالعلاقة الهدف
5. حفظ
## النتيجة
بعد التكوين:
* في سجل **شخص**، يعرض حقل "تعيينات المشروع" **المشاريع** مباشرةً (وليس سجلات التعيين)
* في سجل **مشروع**، يعرض حقل "أعضاء الفريق" **الأشخاص** مباشرةً
لا يزال كائن الربط موجودًا ويخزّن الارتباطات، لكن واجهة المستخدم تقدّم عرضًا أوضح لعلاقات متعدد-إلى-متعدد.
## مثال: الأشخاص ↔ المشاريع
إليك شرحًا كاملًا خطوة بخطوة:
### إنشاء كائن الربط
* الاسم: **تعيين مشروع**
* الوصف: "يربط الأشخاص بالمشاريع التي يعملون عليها"
### إضافة علاقات
1. **تعيين مشروع → الأشخاص**
* النوع: متعدد-إلى-واحد
* الحقل على التعيين: "شخص"
* الحقل على الأشخاص: "تعيينات المشروع"
2. **تعيين مشروع → المشاريع**
* النوع: متعدد-إلى-واحد
* الحقل على التعيين: "مشروع"
* الحقل على المشاريع: "أعضاء الفريق"
### ضبط عرض علاقة الربط
1. على كائن **الأشخاص**:
* حرِّر حقل "تعيينات المشروع"
* فعّل مفتاح الربط
* الهدف: "مشروع"
2. على كائن **المشاريع**:
* حرِّر حقل "أعضاء الفريق"
* فعّل مفتاح الربط
* الهدف: "شخص"
### استخدمه
* افتح سجل شخص → سترى مشاريعه مباشرةً
* افتح سجل مشروع → سترى أعضاء الفريق مباشرةً
* أنشئ ارتباطات جديدة من أي جانب
## إضافة بيانات إضافية إلى الارتباطات
نظرًا لأن كائن الربط كائن حقيقي، يمكنك إضافة حقول مخصصة لتخزين معلومات حول العلاقة:
* **الدور**: "مطوّر"، "مصمّم"، "مدير"
* **تاريخ البدء**: متى انضمّوا إلى المشروع
* **الساعات المخصّصة**: عدد الساعات الأسبوعية على هذا المشروع
للوصول إلى هذه البيانات، انتقل إلى كائن الربط مباشرةً أو استعلم عنها عبر واجهة API.
## القيود
* **استيراد/تصدير CSV**: لا يُدعم استيراد علاقات متعدد-إلى-متعدد مباشرةً. بدلًا من ذلك، استورد السجلات إلى كائن الربط.
* **عوامل التصفية**: قد تكون خيارات التصفية حسب علاقات متعدد-إلى-متعدد محدودة.
## ذات صلة
* [حقول العلاقات](/l/ar/user-guide/data-model/capabilities/relation-fields) — شرح أنواع العلاقات
* [إنشاء كائنات مخصصة](/l/ar/user-guide/data-model/how-tos/create-custom-objects) — كيفية إنشاء الكائنات
* [إنشاء حقول العلاقات](/l/ar/user-guide/data-model/how-tos/create-relation-fields) — إعداد العلاقات الأساسي
@@ -9,7 +9,7 @@ description: تعرّف على المصطلحات الأساسية المستخ
## التطبيقات
التطبيقات هي امتدادات مخصّصة مُنشأة على شكل شيفرة يمكنها تعريف نماذج البيانات ووظائف بدون خوادم. تمكّن المطورين من إنشاء تخصيصات قابلة لإعادة الاستخدام يمكن نشرها عبر مساحات عمل متعددة.
التطبيقات هي امتدادات مخصّصة مُنشأة على شكل شيفرة يمكنها تعريف نماذج البيانات والوظائف المنطقية. تمكّن المطورين من إنشاء تخصيصات قابلة لإعادة الاستخدام يمكن نشرها عبر مساحات عمل متعددة.
## إجراءات الكود
@@ -136,7 +136,7 @@ image: /images/user-guide/workflows/workflow.png
| **تأكيدات الفعاليات** | تفاصيل الفعالية أو جدول الأعمال |
<Note>
المرفقات ثابتة—يتم إرسال الملف نفسه إلى جميع المستلمين. بالنسبة للمستندات الديناميكية (مثل عروض الأسعار المخصصة)، أنشئ الملفات وأرفقها باستخدام [وظيفة بدون خادم](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
المرفقات ثابتة—يتم إرسال الملف نفسه إلى جميع المستلمين. بالنسبة للمستندات الديناميكية (مثل عروض الأسعار المخصصة)، أنشئ الملفات وأرفقها باستخدام [وظيفة منطقية](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
</Note>
## أفضل الممارسات
@@ -256,7 +256,7 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
* اختبر الكود مباشرة في الخطوة
<Note>
إذا كنت بحاجة إلى استخدام مفاتيح API خارجية في كودك، فيجب إدخالها مباشرةً في جسم الدالة. لا يمكنك تكوين مفاتيح API في مكان آخر والإشارة إليها في الدالة عديمة الخادم.
إذا كنت بحاجة إلى استخدام مفاتيح API خارجية في كودك، فيجب إدخالها مباشرةً في جسم الدالة. لا يمكنك تكوين مفاتيح API في مكان آخر والإشارة إليها في الدالة المنطقية.
</Note>
<Tip>
@@ -7,7 +7,7 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
## نظرة عامة
يستخدم سير العمل هذا **المحفز اليدوي** بحيث يتمكن المستخدمون من إنشاء ملف PDF عند الطلب لأي سجل محدد. **وظيفة بلا خادم** تتولى ما يلي:
يستخدم سير العمل هذا **المحفز اليدوي** بحيث يتمكن المستخدمون من إنشاء ملف PDF عند الطلب لأي سجل محدد. تتولى **الوظيفة المنطقية** ما يلي:
1. تنزيل ملف PDF من عنوان URL (من خدمة إنشاء PDF)
2. رفع الملف إلى Twenty
@@ -17,7 +17,7 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
قبل إعداد سير العمل:
1. **أنشئ مفتاح API**: انتقل إلى **الإعدادات → واجهات برمجة التطبيقات** ثم أنشئ مفتاح API جديدًا. ستحتاج إلى هذا الرمز المميز للوظيفة بلا خادم.
1. **أنشئ مفتاح API**: انتقل إلى **الإعدادات → واجهات برمجة التطبيقات** ثم أنشئ مفتاح API جديدًا. ستحتاج إلى هذا الرمز المميز للوظيفة المنطقية.
2. **قم بإعداد خدمة إنشاء PDF** (اختياري): إذا كنت تريد إنشاء ملفات PDF ديناميكيًا (مثل عروض الأسعار)، فاستخدم خدمة مثل Carbone أو PDFMonkey أو DocuSeal لإنشاء ملف PDF والحصول على رابط تنزيل.
## إعداد خطوة بخطوة
@@ -32,9 +32,9 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
باستخدام المحفز اليدوي، يمكن للمستخدمين تشغيل سير العمل هذا عبر زر يظهر في أعلى اليمين عند تحديد سجل، وذلك لإنشاء ملف PDF وإرفاقه.
</Tip>
### الخطوة 2: إضافة وظيفة بلا خادم
### الخطوة 2: إضافة وظيفة منطقية
1. أضف إجراء **وظيفة بلا خادم**
1. أضف إجراء **Code** (وظيفة منطقية)
2. أنشئ وظيفة جديدة باستخدام الكود أدناه
3. قم بتهيئة معلمات الإدخال
@@ -45,10 +45,10 @@ description: أنشئ سير عمل لإنشاء ملف PDF (مثل عرض سع
| `companyId` | `{{trigger.object.id}}` |
<Note>
إذا كنت تُرفق إلى كائن مختلف (شخص، فرصة، إلخ)، فأعد تسمية المعلمة وفقًا لذلك (مثلًا، `personId`، `opportunityId`) وحدث الوظيفة بلا خادم.
إذا كنت تُرفِقه بكائن مختلف (شخص، فرصة، إلخ)، فأعد تسمية المعلمة وفقًا لذلك (مثلًا، `personId`، `opportunityId`) وحدّث الوظيفة المنطقية.
</Note>
#### كود الوظيفة بلا خادم
#### كود الوظيفة المنطقية
```typescript
export const main = async (
@@ -172,7 +172,7 @@ export const main = async (
إذا كنت تستخدم خدمة إنشاء PDF، يمكنك:
1. أولًا، أنشئ إجراء طلب HTTP لإنشاء ملف PDF
2. مرّر رابط ملف PDF المُعاد إلى الوظيفة بلا خادم كمعلمة
2. مرّر رابط ملف PDF المُعاد إلى الوظيفة المنطقية كمعلمة
```typescript
export const main = async (
@@ -211,7 +211,7 @@ export const main = async (
* **DocuSeal** - منصة أتمتة المستندات
* **Documint** - إنشاء مستندات يعتمد على واجهة برمجة التطبيقات أولًا
توفر كل خدمة واجهة برمجة تطبيقات تُرجع رابط ملف PDF، ويمكنك بعدها تمريره إلى الوظيفة بلا خادم.
توفر كل خدمة واجهة برمجة تطبيقات تُرجع رابط ملف PDF، ويمكنك بعدها تمريره إلى الوظيفة المنطقية.
## استكشاف الأخطاء وإصلاحها
@@ -224,5 +224,5 @@ export const main = async (
## ذات صلة
* [مشغلات سير العمل](/l/ar/user-guide/workflows/capabilities/workflow-triggers)
* [وظائف بلا خادم](/l/ar/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [الوظائف المنطقية](/l/ar/user-guide/workflows/capabilities/workflow-actions#code)
* [إنشاء عرض سعر أو فاتورة من Twenty](/l/ar/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -131,7 +131,7 @@ description: الأسئلة الشائعة حول سير العمل في Twenty.
</Accordion>
<Accordion title="ما أقصى وقت تنفيذ لإجراءات Code؟">
إجراءات Code (دوال بلا خادم) لديها **مهلة افتراضية قدرها 5 دقائق** (300 ثانية).
إجراءات Code (دوال منطقية) لديها **مهلة افتراضية قدرها 5 دقائق** (300 ثانية).
أقصى مهلة يمكن ضبطها هي **15 دقيقة** (900 ثانية).
@@ -169,6 +169,13 @@ Všechny příkazy v následujících krocích byste měli provádět z kořene
Tím vytvoříte superuživatelskou roli pojmenovanou `postgres` s přístupovými právy.
```bash
Jméno role | Vlastnosti | Členem
-----------+-------------+-----------
postgres | Superuživatel | {}
john | Superuživatel | {}
```
**Možnost 2:** Pokud máte nainstalován docker:
```bash
@@ -9,18 +9,14 @@ description: Vytvářejte a spravujte přizpůsobení Twenty jako kód.
## Co jsou aplikace?
Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako kód**. Místo konfigurace všeho přes uživatelské rozhraní definujete v kódu svůj datový model a serverless funkce — což zrychluje vývoj, údržbu i nasazování do více pracovních prostorů.
Aplikace vám umožňují vytvářet a spravovat přizpůsobení Twenty **jako kód**. Místo konfigurace všeho přes uživatelské rozhraní definujete v kódu svůj datový model a logické funkce — což zrychluje vývoj, údržbu i nasazování do více pracovních prostorů.
**Co můžete dělat už dnes:**
* Definujte vlastní objekty a pole jako kód (spravovaný datový model)
* Vytvářejte serverless funkce s vlastními spouštěči
* Vytvářejte logické funkce s vlastními spouštěči
* Nasazujte stejnou aplikaci do více pracovních prostorů
**Již brzy:**
* Vlastní rozvržení a komponenty uživatelského rozhraní
## Předpoklady
* Node.js 24+ a Yarn 4
@@ -49,26 +45,23 @@ yarn app:dev
Odtud můžete:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
# Přidejte do vaší aplikace novou entitu (s průvodcem)
yarn entity:add
# Generate a typed Twenty client and workspace entity types
# Vygenerujte typovaného klienta Twenty a typy entit pracovního prostoru
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn app:sync
# Watch your application's functions logs
# Sledujte logy funkcí vaší aplikace
yarn function:logs
# Execute a function by name
# Spusťte funkci podle názvu
yarn function:execute -n my-function -p '{"name": "test"}'
# Uninstall the application from the current workspace
# Odinstalujte aplikaci z aktuálního pracovního prostoru
yarn app:uninstall
# Display commands' help
yarn app:help
# Zobrazte nápovědu k příkazům
yarn help
```
Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
@@ -96,82 +89,61 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Složka s veřejnými prostředky (obrázky, písma apod.)
src/
app/
application.config.ts # Povinné - hlavní konfigurace aplikace
default-function.role.ts # Výchozí role pro serverless funkce
// vaše entity (*.object.ts, *.function.ts, *.role.ts)
utils/ # Volitelné - implementace handlerů a nástroje
```
### Konvence před konfigurací
Aplikace používají přístup **konvence před konfigurací**, kde jsou entity detekovány podle přípony souboru. To umožňuje flexibilní organizaci ve složce `src/app/`:
| Přípona souboru | Typ entity |
| --------------- | -------------------------- |
| `*.object.ts` | Definice vlastních objektů |
| `*.function.ts` | Definice serverless funkcí |
| `*.role.ts` | Definice rolí |
### Podporované uspořádání složek
Entity můžete uspořádat podle některého z těchto vzorů:
**Tradiční (podle typu):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**Podle funkcí:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**Plochá:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
├── application-config.ts # Povinné hlavní konfigurace aplikace
├── roles/
└── default-role.ts # Výchozí role pro logické funkce
├── logic-functions/
└── hello-world.ts # Ukázková logická funkce
└── front-components/
└── hello-world.tsx # Ukázková front-endová komponenta
```
V kostce:
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skripty jako `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` a `auth`, které delegují na lokální `twenty` CLI.
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skripty jako `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` a autentizační příkazy, které delegují na lokální `twenty` CLI.
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
* **eslint.config.mjs** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
* **src/app/**: Hlavní místo, kde definujete svou aplikaci jako kód:
* `application.config.ts`: Globální konfigurace vaší aplikace (metadata a napojení za běhu). Viz „Konfigurace aplikace“ níže.
* `*.role.ts`: Definice rolí používané vašimi serverless funkcemi. Viz „Výchozí role funkce“ níže.
* `*.object.ts`: Definice vlastních objektů.
* `*.function.ts`: Definice serverless funkcí.
* **src/utils/**: Volitelná složka pro implementace obslužných funkcí a pomocné nástroje.
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
### Detekce entit
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
| Pomocná funkce | Typ entity |
| ------------------------ | ------------------------------------- |
| `defineObject()` | Definice vlastních objektů |
| `defineLogicFunction()` | Definice logických funkcí |
| `defineFrontComponent()` | Definice frontendových komponent |
| `defineRole()` | Definice rolí |
| `defineField()` | Rozšíření polí u existujících objektů |
<Note>
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
</Note>
Příklad detekované entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Pozdější příkazy přidají další soubory a složky:
* `yarn app:generate` vytvoří složku `generated/` (typovaný klient Twenty + typy pracovního prostoru).
* `yarn app:create-entity` přidá soubory s definicemi entit do `src/app/` pro vaše vlastní objekty, funkce nebo role.
l
* `yarn entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty nebo role.
## Ověření
@@ -212,16 +184,18 @@ twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které použí
### Pomocné funkce
SDK poskytuje čtyři pomocné funkce s vestavěnou validací pro definování entit vaší aplikace:
SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](#entity-detection), musíte použít `export default define<Entity>({...})`, aby byly vaše entity detekovány:
| Funkce | Účel |
| ------------------ | ------------------------------------------------ |
| `defineApp()` | Konfigurace metadat aplikace |
| `defineObject()` | Definice vlastních objektů s poli |
| `defineFunction()` | Definice serverless funkcí s obslužnými funkcemi |
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
| Funkce | Účel |
| ------------------------ | ----------------------------------------------------------------- |
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
| `defineObject()` | Definice vlastních objektů s poli |
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
| `defineField()` | Rozšiřte existující objekty o další pole |
Tyto funkce validují vaši konfiguraci za běhu a poskytují lepší automatické doplňování v IDE a lepší typovou bezpečnost.
Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
### Definování objektů
@@ -302,79 +276,28 @@ Hlavní body:
* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními.
* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`.
* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí.
* Nové objekty můžete vygenerovat pomocí `yarn app:create-entity`, který vás provede pojmenováním, poli a vztahy.
* Nové objekty můžete vygenerovat pomocí `yarn entity:add`, který vás provede pojmenováním, poli a vztahy.
<Note>
**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole jako `name`, `createdAt`, `updatedAt`, `createdBy`, `position` a `deletedAt`. Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole.
</Note>
<Accordion title="Alternativa: Syntaxe založená na dekorátorech">
Objekty můžete definovat také pomocí dekorátorů TypeScriptu. Tento přístup používá třídovou syntaxi s dekorátory `@Object`, `@Field` a `@Relation`:
### Konfigurace aplikace (application-config.ts)
```typescript
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
import { type Note } from '../../generated';
@Object({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
icon: 'IconMail',
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
}
```
Poznámka: Přístup s dekorátory vyžaduje `experimentalDecorators` v konfiguraci TypeScriptu.
</Accordion>
### Konfigurace aplikace (application.config.ts)
Každá aplikace má jeden soubor `application.config.ts`, který popisuje:
Každá aplikace má jeden soubor `application-config.ts`, který popisuje:
* **Identitu aplikace**: identifikátory, zobrazovaný název a popis.
* **Jak běží její funkce**: kterou roli používají pro oprávnění.
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
K definování konfigurace aplikace použijte `defineApp()`:
Use `defineApplication()` to define your application configuration:
```typescript
// src/app/application.config.ts
import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApp({
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
@@ -387,7 +310,7 @@ export default defineApp({
isSecret: false,
},
},
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
@@ -395,11 +318,11 @@ Poznámky:
* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi.
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` se musí shodovat s rolí, kterou definujete ve svém souboru `*.role.ts` (viz níže).
* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže).
#### Role a oprávnění
Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `functionRoleUniversalIdentifier` v `application.config.ts` určuje výchozí roli používanou serverless funkcemi vaší aplikace.
Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi vaší aplikace.
* Běhový klíč API vložený jako `TWENTY_API_KEY` je odvozen z této výchozí role funkcí.
* Typovaný klient bude omezen oprávněními udělenými této roli.
@@ -410,14 +333,14 @@ Aplikace mohou definovat role, které zapouzdřují oprávnění k objektům a a
Když vygenerujete novou aplikaci, CLI také vytvoří výchozí soubor role. K definování rolí s vestavěnou validací použijte `defineRole()`:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
@@ -430,7 +353,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -439,8 +362,8 @@ export default defineRole({
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -449,10 +372,10 @@ export default defineRole({
});
```
Na `universalIdentifier` této role se poté odkazuje v `application.config.ts` jako na `functionRoleUniversalIdentifier`. Jinými slovy:
Na `universalIdentifier` této role se poté odkazuje v `application-config.ts` jako na `defaultRoleUniversalIdentifier`. Jinými slovy:
* **\*.role.ts** definuje, co může výchozí role funkce dělat.
* **application.config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
Poznámky:
@@ -461,22 +384,17 @@ Poznámky:
* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Držte je na minimu; přidávejte pouze to, co potřebujete.
* Podívejte se na funkční příklad v aplikaci Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Konfigurace serverless funkcí a vstupní bod
### Konfigurace logických funkcí a vstupní bod
Každý soubor funkce používá `defineFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. Pro automatickou detekci použijte příponu souboru `*.function.ts`.
Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
const handler = async (
params:
| RoutePayload
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload,
) => {
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
@@ -492,7 +410,7 @@ const handler = async (
return result;
};
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
@@ -507,18 +425,18 @@ export default defineFunction({
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
{
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
type: 'cron',
pattern: '0 0 1 1 *',
},
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
{
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
type: 'databaseEvent',
eventName: 'person.updated',
updatedFields: ['name'],
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
@@ -565,10 +483,10 @@ Poznámky:
**Jak migrovat existující funkce:** Aktualizujte svůj handler tak, aby destrukturoval z `event.body`, `event.queryStringParameters` nebo `event.pathParameters` místo přímo z objektu params.
</Warning>
Když spouštěč trasy vyvolá vaši funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`:
Když spouštěč trasy vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá formátu AWS HTTP API v2. Importujte typ z `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
@@ -595,10 +513,10 @@ Typ `RoutePayload` má následující strukturu:
### Přeposílání záhlaví HTTP
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší serverless funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
```typescript
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
Nové funkce můžete vytvářet dvěma způsoby:
* **Vygenerované**: Spusťte `yarn app:create-entity` a zvolte možnost přidat novou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací.
* **Ruční**: Vytvořte nový soubor `*.function.ts` a použijte `defineFunction()` podle stejného vzoru.
* **Vygenerované**: Spusťte `yarn entity:add` a zvolte možnost přidat novou logickou funkci. Tím se vygeneruje startovací soubor s obslužnou funkcí a konfigurací.
* **Ruční**: Vytvořte nový soubor `*.logic-function.ts` a použijte `defineLogicFunction()` podle stejného vzoru.
### Frontendové komponenty
Frontendové komponenty vám umožňují vytvářet vlastní React komponenty, které se vykreslují v rozhraní Twenty. K definování komponent s vestavěnou validací použijte `defineFrontComponent()`:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>My Custom Widget</h1>
<p>This is a custom front component for Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-widget',
description: 'A custom widget component',
component: MyWidget,
});
```
Hlavní body:
* Frontendové komponenty jsou React komponenty, které se vykreslují v izolovaných kontextech v rámci Twenty.
* Pro automatickou detekci použijte příponu souboru `*.front-component.tsx`.
* Pole `component` odkazuje na vaši React komponentu.
* Komponenty se během `yarn app:dev` automaticky sestaví a synchronizují.
Nové frontendové komponenty můžete vytvořit dvěma způsoby:
* **Vygenerované**: Spusťte `yarn entity:add` a zvolte možnost přidat novou frontendovou komponentu.
* **Ruční**: Vytvořte nový soubor `*.front-component.tsx` a použijte `defineFrontComponent()`.
### Generovaný typovaný klient
Spusťte yarn app:generate a vytvořte lokálního typovaného klienta v generated/ na základě schématu vašeho pracovního prostoru. Použijte jej ve svých funkcích:
```typescript
import Twenty from './generated';
import Twenty from '~/generated';
const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Klient je znovu generován příkazem `yarn app:generate`. Spusťte jej znovu po změně objektů a po `yarn app:sync`, případně při připojení k novému pracovnímu prostoru.
Klient je znovu generován příkazem `yarn app:generate`. Spusťte znovu po změně vašich objektů nebo při připojování k novému pracovnímu prostoru.
#### Běhové přihlašovací údaje v serverless funkcích
#### Běhové přihlašovací údaje v logických funkcích
Když vaše funkce běží na Twenty, platforma před spuštěním kódu vloží přihlašovací údaje jako proměnné prostředí:
@@ -659,12 +614,12 @@ Když vaše funkce běží na Twenty, platforma před spuštěním kódu vloží
Poznámky:
* Není nutné předávat URL ani klíč API vygenerovanému klientovi. Za běhu čte `TWENTY_API_URL` a `TWENTY_API_KEY` z process.env.
* Oprávnění API klíče jsou určena rolí odkazovanou v `application.config.ts` prostřednictvím `functionRoleUniversalIdentifier`. Toto je výchozí role používaná serverless funkcemi vaší aplikace.
* Aplikace mohou definovat role podle principu nejmenších oprávnění. Udělte pouze oprávnění, která vaše funkce potřebují, a poté nastavte `functionRoleUniversalIdentifier` na univerzální identifikátor této role.
* Oprávnění klíče API jsou určena rolí odkazovanou ve vašem `application-config.ts` prostřednictvím `defaultRoleUniversalIdentifier`. Toto je výchozí role používaná logickými funkcemi vaší aplikace.
* Aplikace mohou definovat role podle principu nejmenších oprávnění. Udělte pouze oprávnění, která vaše funkce potřebují, a poté nastavte `defaultRoleUniversalIdentifier` na univerzální identifikátor této role.
### Příklad Hello World
Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, funkce a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
Prozkoumejte minimalistický end-to-end příklad, který demonstruje objekty, logické funkce, frontendové komponenty a více spouštěčů [zde](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Ruční nastavení (bez scaffolderu)
@@ -679,25 +634,29 @@ Poté přidejte skripty jako tyto:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"help": "twenty help"
}
}
```
Nyní můžete spouštět stejné příkazy přes Yarn, např. `yarn app:dev`, `yarn app:sync` atd.
Nyní můžete spouštět stejné příkazy přes Yarn, např. `yarn app:dev`, `yarn app:generate` atd.
## Řešení potíží
* Chyby ověření: spusťte `yarn auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
* Typy nebo klient chybí/jsou zastaralé: spusťte `yarn app:generate` a poté `yarn app:dev`.
* Typy nebo klient chybí nebo jsou zastaralé: spusťte `yarn app:generate`.
* Režim vývoje nesynchronizuje: ujistěte se, že běží `yarn app:dev` a že vaše prostředí změny neignoruje.
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Režim pouze s prostředím:** Pokud nastavíte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, přidejte tyto proměnné do souboru `.env`
</Warning>
## Serverless funkce
## Logické funkce
Twenty podporuje serverless funkce pro pracovní postupy a vlastní logiku. Běhové prostředí se konfiguruje pomocí proměnné prostředí `SERVERLESS_TYPE`.
Twenty podporuje logické funkce pro pracovní postupy a vlastní logiku. Běhové prostředí se konfiguruje pomocí proměnné prostředí `SERVERLESS_TYPE`.
<Warning>
**Upozornění na zabezpečení:** Místní serverless ovladač (`SERVERLESS_TYPE=LOCAL`) spouští kód přímo na hostiteli v procesu Node.js bez sandboxu. Měl by být používán pouze pro důvěryhodný kód při vývoji. Pro produkční nasazení, která pracují s nedůvěryhodným kódem, důrazně doporučujeme použít `SERVERLESS_TYPE=LAMBDA` nebo `SERVERLESS_TYPE=DISABLED`.
**Upozornění na zabezpečení:** Místní ovladač (`SERVERLESS_TYPE=LOCAL`) spouští kód přímo na hostiteli v procesu Node.js bez sandboxu. Měl by být používán pouze pro důvěryhodný kód při vývoji. Pro produkční nasazení, která pracují s nedůvěryhodným kódem, důrazně doporučujeme použít `SERVERLESS_TYPE=LAMBDA` nebo `SERVERLESS_TYPE=DISABLED`.
</Warning>
### Dostupné ovladače
| Ovladač | Proměnná prostředí | Případ použití | Úroveň zabezpečení |
| --------- | -------------------------- | ------------------------------------------ | ----------------------------------- |
| Neaktivní | `SERVERLESS_TYPE=DISABLED` | Úplně zakázat serverless funkce | N/A |
| Neaktivní | `SERVERLESS_TYPE=DISABLED` | Úplně zakázat logické funkce | N/A |
| Lokální | `SERVERLESS_TYPE=LOCAL` | Vývojová a důvěryhodná prostředí | Nízká (bez sandboxu) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produkční prostředí s nedůvěryhodným kódem | Vysoká (izolace na úrovni hardwaru) |
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**Pro zakázání serverless funkcí:**
**Pro zakázání logických funkcí:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
Při použití `SERVERLESS_TYPE=DISABLED` skončí každý pokus o spuštění serverless funkce chybou. To je užitečné, pokud chcete provozovat Twenty bez podpory serverless funkcí.
Při použití `SERVERLESS_TYPE=DISABLED` skončí každý pokus o spuštění logické funkce chybou. To je užitečné, pokud chcete provozovat Twenty bez podpory logických funkcí.
</Note>
@@ -59,4 +59,4 @@ To zajistí, že AI agenti budou respektovat vaše zásady správy dat a budou p
Tuto sekci budeme aktualizovat, jakmile budou funkce AI k dispozici. Mezitím:
* Sledujte náš [GitHub](https://github.com/twentyhq/twenty) pro novinky o vývoji
* Přidejte se na náš [Discord](https://discord.gg/twenty) a podělte se o zpětnou vazbu a návrhy na nové funkce
* Přidejte se na náš [Discord](https://discord.gg/UfGNZJfAG6) a podělte se o zpětnou vazbu a návrhy na nové funkce
@@ -39,12 +39,16 @@ Mnoho záznamů v Objektu A může být propojeno s mnoha záznamy v Objektu B.
**Příklad:** Mnoho lidí může být propojeno s mnoha projekty a naopak.
<Warning>
**Mnoho k mnoha zatím není podporováno.**
Vztahy typu mnoho-na-mnoho používají vzor zvaný **spojovací objekt**: zprostředkující objekt, který propojuje obě strany. S funkcí spojovacího vztahu zobrazuje Twenty přímo výsledné propojené záznamy a zprostředkující objekt v UI skrývá.
Tento typ relace je plánován na 1. pololetí 2026. Jako dočasné řešení vytvořte prostřední "spojovací" objekt (např. "Project Assignments"), který má relace typu mnoho k jednomu k oběma objektům.
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
<Warning>
**Experimentální funkce**: Vztahy přes spojovací objekt je před použitím nutné povolit v **Settings → Updates → Lab**.
</Warning>
Podívejte se na [Jak vytvořit vztahy typu mnoho k mnoha](/l/cs/user-guide/data-model/how-tos/create-many-to-many-relations) pro úplný návod krok za krokem.
## Vytvoření relačního pole
1. Přejděte do **Nastavení → Datový model**
@@ -0,0 +1,180 @@
---
title: Vytváření vztahů mnoho-na-mnoho
description: Propojte záznamy, kde lze mnoho položek na obou stranách vzájemně propojit pomocí spojovacích objektů.
---
Vztahy mnoho-na-mnoho vám umožňují propojit více záznamů na obou stranách. Například: mnoho lidí může pracovat na mnoha projektech a každý projekt může mít mnoho lidí.
<Warning>
**Lab Feature**: Vztahy přes spojovací objekt jsou momentálně v Lab. Povolte je v **Settings → Updates → Lab** předtím, než budete pokračovat podle tohoto návodu.
</Warning>
<Note>
Tato funkce také vyžaduje, aby byl povolen **Advanced mode** (přepínač je vpravo dole v Settings).
</Note>
## Kdy použít mnoho-na-mnoho
Použijte mnoho-na-mnoho, když obě strany vztahu mohou mít více propojení:
| Vztah | Příklad |
| --------------------- | --------------------------------------------------------------------------- |
| Lidé ↔ Projekty | Osoba pracuje na více projektech; projekt má více členů týmu |
| Společnosti ↔ Štítky | Společnost může mít více štítků; štítek může platit pro více společností |
| Produkty ↔ Objednávky | Produkt může být v několika objednávkách; objednávka obsahuje více produktů |
## Jak to funguje
Twenty používá u vztahů mnoho-na-mnoho vzor **spojovacího objektu**. Spojovací objekt leží mezi dvěma objekty a uchovává propojení:
```
People ←→ Project Assignments ←→ Projects
```
Objekt **Project Assignments** (spojovací) má:
* Vztah na People (mnoho k jednomu)
* Vztah na Projects (mnoho k jednomu)
Když povolíte přepínač pro vztah přes spojovací objekt, Twenty zobrazí propojené záznamy přímo místo zobrazení prostředních záznamů spojovacího objektu.
## Předpoklady
1. **Povolte Junction Relations v Lab**: Přejděte do **Settings → Updates → Lab** a povolte **Junction Relations**
2. **Povolte Advanced mode**: Zapněte **Advanced mode** vpravo dole na postranním panelu Settings
3. Naplánujte svůj datový model:
* Které dva objekty propojujete?
* Jak by se měl jmenovat spojovací objekt?
## Krok 1: Vytvořte spojovací objekt
Nejprve vytvořte mezilehlý objekt, který bude uchovávat propojení.
1. Přejděte do **Nastavení → Datový model**
2. Klikněte na **+ New object**
3. Pojmenujte jej výstižně (např. "Project Assignment", "Team Member", "Product Order")
4. Klikněte na **Uložit**
<Tip>
**Konvence pojmenování**: Použijte název, který popisuje vztah, například "Project Assignment" nebo "Team Membership". Tím bude datový model srozumitelnější.
</Tip>
## Krok 2: Vytvořte vztahy ze spojovacího objektu
Přidejte relační pole ze spojovacího objektu do obou objektů, které chcete propojit.
### První vztah (Junction → Objekt A)
1. Vyberte svůj spojovací objekt v **Settings → Data Model**
2. Klikněte na **+ Add Field**
3. Zvolte **Relation** jako typ pole
4. Vyberte první objekt (např. "People")
5. Nastavte typ vztahu na **Many-to-One** (mnoho přiřazení může odkazovat na jednu osobu)
6. Pojmenujte pole:
* Pole na spojovacím objektu: např. "Person"
* Pole na People: např. "Project Assignments"
7. Klikněte na **Uložit**
### Druhý vztah (Junction → Objekt B)
1. Stále na spojovacím objektu klikněte na **+ Add Field**
2. Zvolte **Relation** jako typ pole
3. Vyberte druhý objekt (např. "Projects")
4. Nastavte typ vztahu na **Many-to-One**
5. Pojmenujte pole:
* Pole na spojovacím objektu: např. "Project"
* Pole na Projects: např. "Team Members"
6. Klikněte na **Uložit**
## Krok 3: Nakonfigurujte zobrazení vztahu přes spojovací objekt
Nyní nakonfigurujte zdrojové objekty tak, aby zobrazovaly propojené záznamy přímo a přeskočily mezilehlý spojovací objekt.
1. Přejděte do **Nastavení → Datový model**
2. Vyberte první objekt (např. "People")
3. Najděte relační pole směřující na spojovací objekt (např. "Project Assignments")
4. Klikněte pro úpravu pole
5. Povolte **"This is a relation to a Junction Object"**
6. Vyberte **Target relation** (např. "Project" — pole na spojovacím objektu, které ukazuje na druhou stranu)
7. Klikněte na **Uložit**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Opakujte pro druhý objekt:
1. Vyberte "Projects" v Data Model
2. Upravte relační pole "Team Members"
3. Povolte přepínač pro vztah přes spojovací objekt
4. Vyberte "Person" jako cílový vztah
5. Uložit
## Výsledek
Po konfiguraci:
* Na záznamu **Person** pole "Project Assignments" zobrazuje přímo **Projects** (nikoli záznamy přiřazení)
* Na záznamu **Project** pole "Team Members" zobrazuje přímo **People**
Spojovací objekt stále existuje a ukládá propojení, ale rozhraní zobrazuje čistší pohled mnoho-na-mnoho.
## Příklad: Lidé ↔ Projekty
Zde je kompletní postup:
### Vytvořte spojovací objekt
* Název: **Project Assignment**
* Popis: "Propojuje lidi s projekty, na kterých pracují"
### Přidejte vztahy
1. **Project Assignment → People**
* Typ: Many-to-One
* Pole na Assignment: "Person"
* Pole na People: "Project Assignments"
2. **Project Assignment → Projects**
* Typ: Many-to-One
* Pole na Assignment: "Project"
* Pole na Projects: "Team Members"
### Nakonfigurujte zobrazení spojovacího objektu
1. Na objektu **People**:
* Upravte pole "Project Assignments"
* Povolte přepínač pro vztah přes spojovací objekt
* Cíl: "Project"
2. Na objektu **Projects**:
* Upravte pole "Team Members"
* Povolte přepínač pro vztah přes spojovací objekt
* Cíl: "Person"
### Použití
* Otevřete záznam osoby → Uvidíte její projekty přímo
* Otevřete záznam projektu → Uvidíte členy týmu přímo
* Vytvářejte nová propojení z obou stran
## Přidávání doplňkových dat do propojení
Protože spojovací objekt je skutečný objekt, můžete přidat vlastní pole k ukládání informací o vztahu:
* **Role**: "Developer", "Designer", "Manager"
* **Start Date**: Kdy se k projektu připojili
* **Hours Allocated**: Týdenní počet hodin na tomto projektu
Pro přístup k těmto datům přejděte přímo na spojovací objekt nebo jej dotazujte přes API.
## Omezení
* **CSV Import/Export**: Přímý import vztahů mnoho-na-mnoho není podporován. Místo toho importujte záznamy do spojovacího objektu.
* **Filters**: Filtrování podle vztahů mnoho-na-mnoho může mít omezené možnosti.
## Související
* [Relační pole](/l/cs/user-guide/data-model/capabilities/relation-fields) — vysvětlení typů vztahů
* [Vytváření vlastních objektů](/l/cs/user-guide/data-model/how-tos/create-custom-objects) — jak vytvářet objekty
* [Vytváření relačních polí](/l/cs/user-guide/data-model/how-tos/create-relation-fields) — základní nastavení vztahů
@@ -9,7 +9,7 @@ API (rozhraní pro programování aplikací) umožňuje propojení Twenty s dal
## Aplikace
Aplikace jsou vlastní rozšíření vytvořená jako kód, která mohou definovat datové modely a bezserverové funkce. Umožňují vývojářům vytvářet znovupoužitelné přizpůsobení, které lze nasadit do více pracovních prostorů.
Aplikace jsou vlastní rozšíření vytvořená jako kód, která mohou definovat datové modely a logické funkce. Umožňují vývojářům vytvářet znovupoužitelné přizpůsobení, které lze nasadit do více pracovních prostorů.
## Akce kódu
@@ -136,7 +136,7 @@ K e-mailům odesílaným z pracovních postupů můžete přikládat soubory. P
| **Potvrzení událostí** | Podrobnosti o události nebo program |
<Note>
Přílohy jsou statické—všem příjemcům se posílá stejný soubor. U dynamických dokumentů (například personalizovaných nabídek) soubory vygenerujte a přiložte pomocí [Serverless funkce](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
Přílohy jsou statické—všem příjemcům se posílá stejný soubor. U dynamických dokumentů (například personalizovaných nabídek) soubory vygenerujte a přiložte pomocí [Logic funkce](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
</Note>
## Osvědčené postupy
@@ -256,7 +256,7 @@ Spouští vlastní JavaScript ve vašem pracovním postupu.
* Testovat kód přímo ve kroku
<Note>
Pokud potřebujete ve svém kódu použít externí klíče API, musíte je zadat přímo do těla funkce. Klíče API nelze nakonfigurovat jinde a odkazovat na ně v bezserverové funkci.
Pokud potřebujete ve svém kódu použít externí klíče API, musíte je zadat přímo do těla funkce. You cannot configure API keys elsewhere and reference them in the logic function.
</Note>
<Tip>
@@ -7,7 +7,7 @@ Automaticky vygenerujte nebo získejte PDF a připojte ho k záznamu v Twenty. T
## Přehled
Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mohou na požádání vygenerovat PDF pro libovolný vybraný záznam. O zpracování se stará **Serverless funkce**:
Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mohou na požádání vygenerovat PDF pro libovolný vybraný záznam. O zpracování se stará **logická funkce**:
1. Stažení PDF z adresy URL (ze služby pro generování PDF)
2. Nahrání souboru do Twenty
@@ -17,7 +17,7 @@ Tento pracovní postup používá **Ruční spouštěč**, takže uživatelé mo
Než nastavíte pracovní postup:
1. **Vytvořte klíč API**: Přejděte do **Nastavení → API** a vytvořte nový klíč API. Tento token budete potřebovat pro serverless funkci.
1. **Vytvořte klíč API**: Přejděte do **Nastavení → API** a vytvořte nový klíč API. Tento token budete potřebovat pro logickou funkci.
2. **Nastavte službu pro generování PDF** (volitelné): Pokud chcete dynamicky generovat PDF (např. nabídky), použijte službu jako Carbone, PDFMonkey nebo DocuSeal k vytvoření PDF a získání adresy URL pro stažení.
## Nastavení krok za krokem
@@ -32,9 +32,9 @@ Než nastavíte pracovní postup:
S **Ručním spouštěčem** mohou uživatelé spustit tento pracovní postup pomocí tlačítka, které se zobrazí vpravo nahoře po výběru záznamu, aby vygenerovali a připojili PDF.
</Tip>
### Krok 2: Přidejte serverless funkci
### Krok 2: Přidejte logickou funkci
1. Přidejte akci **Serverless funkce**
1. Přidejte akci **Code** (logická funkce)
2. Vytvořte novou funkci pomocí kódu níže
3. Nakonfigurujte vstupní parametry
@@ -45,10 +45,10 @@ Než nastavíte pracovní postup:
| `companyId` | `{{trigger.object.id}}` |
<Note>
Pokud připojujete k jinému objektu (Osoba, Příležitost apod.), přejmenujte parametr podle toho (např. `personId`, `opportunityId`) a aktualizujte serverless funkci.
Pokud připojujete k jinému objektu (Osoba, Příležitost apod.), přejmenujte parametr podle toho (např. `personId`, `opportunityId`) a aktualizujte logickou funkci.
</Note>
#### Kód serverless funkce
#### Kód logické funkce
```typescript
export const main = async (
@@ -172,7 +172,7 @@ Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy
Pokud používáte službu pro generování PDF, můžete:
1. Nejprve proveďte akci HTTP Request pro vygenerování PDF
2. Předejte vrácenou adresu URL PDF serverless funkci jako parametr
2. Předejte vrácenou adresu URL PDF logické funkci jako parametr
```typescript
export const main = async (
@@ -211,7 +211,7 @@ Pro vytváření dynamických nabídek nebo faktur:
* **DocuSeal** - Platforma pro automatizaci dokumentů
* **Documint** - Generování dokumentů primárně přes API
Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete předat serverless funkci.
Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete předat logické funkci.
## Řešení potíží
@@ -224,5 +224,5 @@ Každá služba poskytuje API, které vrací adresu URL PDF, kterou pak můžete
## Související
* [Spouštěče pracovních postupů](/l/cs/user-guide/workflows/capabilities/workflow-triggers)
* [Serverless funkce](/l/cs/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Logické funkce](/l/cs/user-guide/workflows/capabilities/workflow-actions#code)
* [Vygenerujte nabídku nebo fakturu z Twenty](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -131,7 +131,7 @@ description: Často kladené otázky k pracovním postupům v Twenty.
</Accordion>
<Accordion title="Jaký je maximální čas spuštění pro akce Code?">
Akce Code (serverless funkce) mají **výchozí časový limit 5 minut** (300 sekund).
Akce Code (logické funkce) mají **výchozí časový limit 5 minut** (300 sekund).
Maximální nastavitelný časový limit je **15 minut** (900 sekund).
@@ -170,6 +170,13 @@ Alle folgenden Befehle innerhalb des Projekts sind vom Stammverzeichnis aus ausz
Dadurch wird eine Superuser-Rolle namens `postgres` mit Anmeldezugriff erstellt.
```bash
Rollenname | Attribute | Mitglied von
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Option 2:** Wenn Sie Docker installiert haben:
```bash
@@ -9,18 +9,14 @@ description: Twenty-Anpassungen als Code erstellen und verwalten.
## Was sind Apps?
Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und serverlose Funktionen im Code — das beschleunigt Entwicklung, Wartung und Rollout auf mehrere Workspaces.
Mit Apps können Sie Twenty-Anpassungen **als Code** erstellen und verwalten. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und Logikfunktionen im Code — das beschleunigt Entwicklung, Wartung und den Rollout auf mehrere Workspaces.
**Was Sie heute tun können:**
* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell)
* Serverlose Funktionen mit benutzerdefinierten Triggern erstellen
* Logikfunktionen mit benutzerdefinierten Triggern erstellen
* Dieselbe App in mehreren Workspaces bereitstellen
**Bald verfügbar:**
* Benutzerdefinierte UI-Layouts und Komponenten
## Voraussetzungen
* Node.js 24+ und Yarn 4
@@ -49,26 +45,23 @@ yarn app:dev
Von hier aus können Sie:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
# Eine neue Entität zu deiner Anwendung hinzufügen (geführt)
yarn entity:add
# Generate a typed Twenty client and workspace entity types
# Einen typisierten Twenty-Client und Entitätstypen für den Arbeitsbereich generieren
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn app:sync
# Watch your application's functions logs
# Die Funktionsprotokolle deiner Anwendung überwachen
yarn function:logs
# Execute a function by name
# Eine Funktion anhand ihres Namens ausführen
yarn function:execute -n my-function -p '{"name": "test"}'
# Uninstall the application from the current workspace
# Die Anwendung aus dem aktuellen Arbeitsbereich deinstallieren
yarn app:uninstall
# Display commands' help
yarn app:help
# Hilfe zu Befehlen anzeigen
yarn help
```
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
@@ -96,82 +89,61 @@ my-twenty-app/
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
src/
app/
application.config.ts # Required - main application configuration
default-function.role.ts # Default role for serverless functions
// your entities (*.object.ts, *.function.ts, *.role.ts)
utils/ # Optional - handler implementations & utilities
```
### Konvention vor Konfiguration
Anwendungen verwenden einen Ansatz **Konvention vor Konfiguration**, bei dem Entitäten anhand ihrer Dateiendung erkannt werden. Dies ermöglicht eine flexible Organisation im Ordner `src/app/`:
| Dateiendung | Entitätstyp |
| --------------- | ------------------------------------- |
| `*.object.ts` | Benutzerdefinierte Objektdefinitionen |
| `*.function.ts` | Definitionen serverloser Funktionen |
| `*.role.ts` | Rollendefinitionen |
### Unterstützte Ordnerorganisationen
Sie können Ihre Entitäten nach einem der folgenden Muster organisieren:
**Traditionell (nach Typ):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**Feature-basiert:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**Flach:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
├── application-config.ts # Required - main application configuration
├── roles/
└── default-role.ts # Default role for logic functions
├── logic-functions/
│ └── hello-world.ts # Example logic function
└── front-components/
└── hello-world.tsx # Example front component
```
Auf hoher Ebene:
* **package.json**: Deklariert App-Name, Version, Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie Skripte wie `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall` und `auth` hinzu, die an die lokale `twenty`-CLI delegieren.
* **package.json**: Deklariert den App-Namen, die Version, Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie Skripte wie `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` sowie Authentifizierungsbefehle hinzu, die an die lokale `twenty`-CLI delegieren.
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
* **eslint.config.mjs** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
* **src/app/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren:
* `application.config.ts`: Globale Konfiguration für Ihre App (Metadaten und Laufzeit-Anbindung). Siehe unten „Anwendungskonfiguration“.
* `*.role.ts`: Rollendefinitionen, die von Ihren serverlosen Funktionen verwendet werden. Siehe unten „Standard-Funktionsrolle“.
* `*.object.ts`: Benutzerdefinierte Objektdefinitionen.
* `*.function.ts`: Definitionen serverloser Funktionen.
* **src/utils/**: Optionaler Ordner für Handler-Implementierungen und Utilities.
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
### Entitätserkennung
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
| Hilfsfunktion | Entitätstyp |
| ------------------------ | ---------------------------------------- |
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
| `defineRole()` | Rollendefinitionen |
| `defineField()` | Felderweiterungen für bestehende Objekte |
<Note>
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
</Note>
Beispiel für eine erkannte Entität:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
* `yarn app:generate` erstellt einen `generated/`-Ordner (typisierter Twenty-Client + Workspace-Typen).
* `yarn app:create-entity` fügt unter `src/app/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen oder Rollen hinzu.
l
* `yarn entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Front-Komponenten oder Rollen hinzu.
## Authentifizierung
@@ -212,16 +184,18 @@ Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie i
### Hilfsfunktionen
Das SDK stellt vier Hilfsfunktionen mit eingebauter Validierung bereit, um Ihre App-Entitäten zu definieren:
Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](#entity-detection) beschrieben, müssen Sie `export default define<Entity>({...})` verwenden, damit Ihre Entitäten erkannt werden:
| Funktion | Zweck |
| ------------------ | ---------------------------------------------------- |
| `defineApp()` | Anwendungsmetadaten konfigurieren |
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
| `defineFunction()` | Serverlose Funktionen mit Handlern definieren |
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
| Funktion | Zweck |
| ------------------------ | -------------------------------------------------------------- |
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
Diese Funktionen validieren Ihre Konfiguration zur Laufzeit und bieten bessere IDE-Autovervollständigung sowie Typsicherheit.
Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit.
### Objekte definieren
@@ -302,79 +276,28 @@ Hauptpunkte:
* Der `universalIdentifier` muss eindeutig und über Deployments hinweg stabil sein.
* Jedes Feld benötigt `name`, `type`, `label` und einen eigenen stabilen `universalIdentifier`.
* Das Array `fields` ist optional — Sie können Objekte ohne benutzerdefinierte Felder definieren.
* Sie können mit `yarn app:create-entity` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen.
* Sie können mit `yarn entity:add` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen.
<Note>
**Basisfelder werden automatisch erstellt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, fügt Twenty automatisch Standardfelder wie `name`, `createdAt`, `updatedAt`, `createdBy`, `position` und `deletedAt` hinzu. Sie müssen diese nicht in Ihrem `fields`-Array definieren — fügen Sie nur Ihre benutzerdefinierten Felder hinzu.
</Note>
<Accordion title="Alternative: Dekoratorbasierte Syntax">
Sie können Objekte auch mit TypeScript-Dekoratoren definieren. Dieser Ansatz verwendet klassenbasierte Syntax mit den Dekoratoren `@Object`, `@Field` und `@Relation`:
### Anwendungskonfiguration (application-config.ts)
```typescript
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
import { type Note } from '../../generated';
@Object({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: 'A post card object',
icon: 'IconMail',
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
}
```
Hinweis: Der Dekorator-Ansatz erfordert `experimentalDecorators` in Ihrer TypeScript-Konfiguration.
</Accordion>
### Anwendungskonfiguration (application.config.ts)
Jede App hat eine einzelne Datei `application.config.ts`, die Folgendes beschreibt:
Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschreibt:
* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung.
* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden.
* **(Optional) Variablen**: SchlüsselWert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden.
Verwenden Sie `defineApp()`, um Ihre Anwendungskonfiguration zu definieren:
Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren:
```typescript
// src/app/application.config.ts
import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
export default defineApp({
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
@@ -387,7 +310,7 @@ export default defineApp({
isSecret: false,
},
},
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
@@ -395,11 +318,11 @@ Notizen:
* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
* `functionRoleUniversalIdentifier` muss mit der Rolle übereinstimmen, die Sie in Ihrer `*.role.ts`-Datei definieren (siehe unten).
* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten).
#### Rollen und Berechtigungen
Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `functionRoleUniversalIdentifier` in `application.config.ts` legt die Standardrolle fest, die von den serverlosen Funktionen Ihrer App verwendet wird.
Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und Aktionen Ihres Workspaces kapseln. Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen Ihrer App verwendet wird.
* Der zur Laufzeit als `TWENTY_API_KEY` injizierte API-Schlüssel wird von dieser Standard-Funktionsrolle abgeleitet.
* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt.
@@ -410,14 +333,14 @@ Anwendungen können Rollen definieren, die Berechtigungen für die Objekte und A
Wenn Sie eine neue App erzeugen, erstellt die CLI auch eine Standard-Rolldatei. Verwenden Sie `defineRole()`, um Rollen mit eingebauter Validierung zu definieren:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER =
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineRole({
universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
@@ -430,7 +353,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -439,8 +362,8 @@ export default defineRole({
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
@@ -449,10 +372,10 @@ export default defineRole({
});
```
Der `universalIdentifier` dieser Rolle wird anschließend in `application.config.ts` als `functionRoleUniversalIdentifier` referenziert. Anders ausgedrückt:
Der `universalIdentifier` dieser Rolle wird anschließend in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert. Anders ausgedrückt:
* **\*.role.ts** definiert, was die Standard-Funktionsrolle darf.
* **application.config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben.
* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben.
Notizen:
@@ -461,22 +384,17 @@ Notizen:
* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal; fügen Sie nur hinzu, was Sie benötigen.
* Ein funktionierendes Beispiel finden Sie in der Hello-World-App: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Konfiguration serverloser Funktionen und Einstiegspunkt
### Konfiguration von Logikfunktionen und Einstiegspunkt
Jede Funktionsdatei verwendet `defineFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. Verwenden Sie die Dateiendung `*.function.ts` für die automatische Erkennung.
Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
const handler = async (
params:
| RoutePayload
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload,
) => {
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
@@ -492,7 +410,7 @@ const handler = async (
return result;
};
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
@@ -507,18 +425,18 @@ export default defineFunction({
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
{
universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
type: 'cron',
pattern: '0 0 1 1 *',
},
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
{
universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
type: 'databaseEvent',
eventName: 'person.updated',
updatedFields: ['name'],
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
@@ -565,10 +483,10 @@ Notizen:
**So migrieren Sie bestehende Funktionen:** Aktualisieren Sie Ihren Handler, sodass er nicht mehr direkt aus dem params-Objekt destrukturiert, sondern aus `event.body`, `event.queryStringParameters` oder `event.pathParameters`.
</Warning>
Wenn ein Routen-Trigger Ihre Funktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS-HTTP-API-v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`:
Wenn ein Routen-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem AWS HTTP API v2-Format entspricht. Importieren Sie den Typ aus `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
@@ -595,10 +513,10 @@ Der Typ `RoutePayload` hat die folgende Struktur:
### Weiterleiten von HTTP-Headern
Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre serverlose Funktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf:
Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. Um auf bestimmte Header zuzugreifen, listen Sie diese explizit im Array `forwardedRequestHeaders` auf:
```typescript
export default defineFunction({
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
@@ -633,23 +551,60 @@ const handler = async (event: RoutePayload) => {
Sie können neue Funktionen auf zwei Arten erstellen:
* **Generiert**: Führen Sie `yarn app:create-entity` aus und wählen Sie die Option zum Hinzufügen einer neuen Funktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt.
* **Manuell**: Erstellen Sie eine neue `*.function.ts`-Datei und verwenden Sie `defineFunction()` nach demselben Muster.
* **Generiert**: Führen Sie `yarn entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Logikfunktion. Dadurch wird eine Starterdatei mit Handler und Konfiguration erzeugt.
* **Manuell**: Erstellen Sie eine neue `*.logic-function.ts`-Datei und verwenden Sie `defineLogicFunction()` nach demselben Muster.
### Frontend-Komponenten
Frontend-Komponenten ermöglichen es Ihnen, benutzerdefinierte React-Komponenten zu erstellen, die innerhalb der Twenty-UI gerendert werden. Verwenden Sie `defineFrontComponent()`, um Komponenten mit eingebauter Validierung zu definieren:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>My Custom Widget</h1>
<p>This is a custom front component for Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'my-widget',
description: 'A custom widget component',
component: MyWidget,
});
```
Hauptpunkte:
* Frontend-Komponenten sind React-Komponenten, die in isolierten Kontexten innerhalb von Twenty gerendert werden.
* Verwenden Sie die Dateiendung `*.front-component.tsx` für die automatische Erkennung.
* Das Feld `component` verweist auf Ihre React-Komponente.
* Komponenten werden während `yarn app:dev` automatisch gebaut und synchronisiert.
Sie können neue Frontend-Komponenten auf zwei Arten erstellen:
* **Generiert**: Führen Sie `yarn entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente.
* **Manuell**: Erstellen Sie eine neue `*.front-component.tsx`-Datei und verwenden Sie `defineFrontComponent()`.
### Generierter typisierter Client
Führen Sie yarn app:generate aus, um einen lokalen typisierten Client in generated/ basierend auf Ihrem Workspace-Schema zu erstellen. Verwenden Sie ihn in Ihren Funktionen:
```typescript
import Twenty from './generated';
import Twenty from '~/generated';
const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
Der Client wird durch `yarn app:generate` erneut generiert. Führen Sie ihn nach Änderungen an Ihren Objekten und nach `yarn app:sync` bzw. beim Onboarding in einen neuen Workspace erneut aus.
Der Client wird durch `yarn app:generate` erneut generiert. Führen Sie ihn nach Änderungen an Ihren Objekten oder beim Onboarding in einen neuen Workspace erneut aus.
#### Laufzeit-Anmeldedaten in serverlosen Funktionen
#### Laufzeit-Anmeldedaten in Logikfunktionen
Wenn Ihre Funktion auf Twenty läuft, injiziert die Plattform vor der Ausführung Ihres Codes Anmeldedaten als Umgebungsvariablen:
@@ -659,12 +614,12 @@ Wenn Ihre Funktion auf Twenty läuft, injiziert die Plattform vor der Ausführun
Notizen:
* Sie müssen dem generierten Client weder URL noch API-Schlüssel übergeben. Er liest `TWENTY_API_URL` und `TWENTY_API_KEY` zur Laufzeit aus process.env.
* Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in Ihrer `application.config.ts` über `functionRoleUniversalIdentifier` verwiesen wird. Dies ist die Standardrolle, die von den serverlosen Funktionen Ihrer Anwendung verwendet wird.
* Anwendungen können Rollen definieren, um das Least-Privilege-Prinzip einzuhalten. Gewähren Sie nur die Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann mit `functionRoleUniversalIdentifier` auf den universellen Bezeichner dieser Rolle.
* Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in Ihrer `application-config.ts` über `defaultRoleUniversalIdentifier` verwiesen wird. Dies ist die Standardrolle, die von den Logikfunktionen Ihrer Anwendung verwendet wird.
* Anwendungen können Rollen definieren, um das Least-Privilege-Prinzip einzuhalten. Gewähren Sie nur die Berechtigungen, die Ihre Funktionen benötigen, und verweisen Sie dann mit `defaultRoleUniversalIdentifier` auf den universellen Bezeichner dieser Rolle.
### Hello-World-Beispiel
Ein minimales End-to-End-Beispiel, das Objekte, Funktionen und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
Ein minimales End-to-End-Beispiel, das Objekte, Logikfunktionen, Frontend-Komponenten und mehrere Trigger demonstriert, finden Sie [hier](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Manuelle Einrichtung (ohne Scaffolder)
@@ -679,25 +634,29 @@ Fügen Sie dann Skripte wie diese hinzu:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"help": "twenty help"
}
}
```
Jetzt können Sie dieselben Befehle über Yarn ausführen, z. B. `yarn app:dev`, `yarn app:sync` usw.
Jetzt können Sie dieselben Befehle über Yarn ausführen, z. B. `yarn app:dev`, `yarn app:generate` usw.
## Fehlerbehebung
* Authentifizierungsfehler: Führen Sie `yarn auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
* Typen oder Client fehlen/veraltet: Führen Sie `yarn app:generate` und anschließend `yarn app:dev` aus.
* Typen oder Client fehlen/veraltet: Führen Sie `yarn app:generate` aus.
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn app:dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -292,19 +292,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Nur-Umgebungsmodus:** Wenn Sie `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` setzen, fügen Sie diese Variablen stattdessen Ihrer `.env`-Datei hinzu.
</Warning>
## Serverlose Funktionen
## Logikfunktionen
Twenty unterstützt serverlose Funktionen für Workflows und benutzerdefinierte Logik. Die Ausführungsumgebung wird über die Umgebungsvariable `SERVERLESS_TYPE` konfiguriert.
Twenty unterstützt Logikfunktionen für Workflows und benutzerdefinierte Logik. Die Ausführungsumgebung wird über die Umgebungsvariable `SERVERLESS_TYPE` konfiguriert.
<Warning>
**Sicherheitshinweis:** Der lokale serverlose Treiber (`SERVERLESS_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktivbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, empfehlen wir nachdrücklich, `SERVERLESS_TYPE=LAMBDA` oder `SERVERLESS_TYPE=DISABLED` zu verwenden.
**Sicherheitshinweis:** Der lokale Treiber (`SERVERLESS_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktivbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, empfehlen wir nachdrücklich, `SERVERLESS_TYPE=LAMBDA` oder `SERVERLESS_TYPE=DISABLED` zu verwenden.
</Warning>
### Verfügbare Treiber
| Treiber | Umgebungsvariable | Anwendungsfall | Sicherheitsstufe |
| ----------- | -------------------------- | -------------------------------------------------- | ---------------------------------- |
| Deaktiviert | `SERVERLESS_TYPE=DISABLED` | Serverlose Funktionen vollständig deaktivieren | N/A |
| Deaktiviert | `SERVERLESS_TYPE=DISABLED` | Logikfunktionen vollständig deaktivieren | N/A |
| Lokal | `SERVERLESS_TYPE=LOCAL` | Entwicklung und vertrauenswürdige Umgebungen | Niedrig (keine Sandbox) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produktivbetrieb mit nicht vertrauenswürdigem Code | Hoch (Isolation auf Hardwareebene) |
@@ -326,12 +326,12 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**Zum Deaktivieren serverloser Funktionen:**
**Zum Deaktivieren von Logikfunktionen:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
Bei Verwendung von `SERVERLESS_TYPE=DISABLED` führt jeder Versuch, eine serverlose Funktion auszuführen, zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne Unterstützung für serverlose Funktionen betreiben möchten.
Bei Verwendung von `SERVERLESS_TYPE=DISABLED` führt jeder Versuch, eine Logikfunktion auszuführen, zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne Unterstützung für Logikfunktionen betreiben möchten.
</Note>
@@ -59,4 +59,4 @@ So wird sichergestellt, dass KI-Agenten Ihre Richtlinien zur Daten-Governance re
Wir werden diesen Abschnitt aktualisieren, sobald KI-Funktionen verfügbar werden. In der Zwischenzeit:
* Folgen Sie unserem [GitHub](https://github.com/twentyhq/twenty) für Entwicklungsupdates
* Treten Sie unserem [Discord](https://discord.gg/twenty) bei, um Feedback und Funktionswünsche zu teilen
* Treten Sie unserem [Discord](https://discord.gg/UfGNZJfAG6) bei, um Feedback und Funktionswünsche zu teilen
@@ -39,12 +39,16 @@ Viele Datensätze in Objekt A können mit vielen Datensätzen in Objekt B verkn
**Beispiel:** Viele Personen können mit vielen Projekten verknüpft werden, und umgekehrt.
<Warning>
**Viele-zu-Viele-Beziehungen werden noch nicht unterstützt.**
Viele-zu-viele-Beziehungen verwenden ein **Verknüpfungsobjekt**-Muster: ein Zwischenobjekt, das beide Seiten verbindet. Mit der Funktion für Verknüpfungsbeziehungen zeigt Twenty die endgültig verknüpften Datensätze direkt an und blendet das Zwischenobjekt in der Benutzeroberfläche aus.
Dieser Beziehungstyp ist für H1 2026 geplant. Als Workaround erstellen Sie ein zwischengeschaltetes "Junction"-Objekt (z. B. "Projektzuweisungen"), das Viele-zu-Eins-Beziehungen zu beiden Objekten hat.
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
<Warning>
**Lab-Funktion**: Verknüpfungsbeziehungen müssen vor der Verwendung unter **Einstellungen → Updates → Lab** aktiviert werden.
</Warning>
Siehe [Viele-zu-Viele-Beziehungen erstellen](/l/de/user-guide/data-model/how-tos/create-many-to-many-relations) für eine vollständige Schritt-für-Schritt-Anleitung.
## Beziehungsfeld erstellen
1. Gehen Sie zu **Einstellungen → Datenmodell**
@@ -0,0 +1,180 @@
---
title: Viele-zu-Viele-Beziehungen erstellen
description: Verbinden Sie Datensätze, bei denen auf beiden Seiten viele Elemente mithilfe von Verknüpfungsobjekten miteinander verknüpft werden können.
---
Viele-zu-Viele-Beziehungen ermöglichen es Ihnen, auf beiden Seiten mehrere Datensätze zu verknüpfen. Beispiel: Viele Personen können an vielen Projekten arbeiten, und jedes Projekt kann viele Personen haben.
<Warning>
**Lab-Funktion**: Verknüpfungsbeziehungen befinden sich derzeit im Lab. Aktivieren Sie sie unter **Einstellungen → Updates → Lab**, bevor Sie dieser Anleitung folgen.
</Warning>
<Note>
Für diese Funktion muss außerdem der **Erweiterte Modus** aktiviert sein (Schalter unten rechts in den Einstellungen).
</Note>
## Wann Viele-zu-Viele-Beziehungen verwenden
Verwenden Sie Viele-zu-Viele-Beziehungen, wenn beide Seiten einer Beziehung mehrere Verknüpfungen haben können:
| Beziehung | Beispiel |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| Personen ↔ Projekte | Eine Person arbeitet an mehreren Projekten; ein Projekt hat mehrere Teammitglieder |
| Unternehmen ↔ Tags | Ein Unternehmen kann mehrere Tags haben; ein Tag kann für mehrere Unternehmen gelten |
| Produkte ↔ Bestellungen | Ein Produkt kann in mehreren Bestellungen enthalten sein; eine Bestellung enthält mehrere Produkte |
## Wie es funktioniert
Twenty verwendet für Viele-zu-Viele-Beziehungen ein Muster mit **Verknüpfungsobjekt**. Ein Verknüpfungsobjekt sitzt zwischen zwei Objekten und speichert die Verknüpfungen:
```
People ←→ Project Assignments ←→ Projects
```
Das Objekt **Projektzuweisungen** (Verknüpfung) hat:
* Eine Beziehung zu Personen (Viele-zu-Eins)
* Eine Beziehung zu Projekten (Viele-zu-Eins)
Wenn Sie den Schalter für die Verknüpfungsbeziehung aktivieren, zeigt Twenty verknüpfte Datensätze direkt an, anstatt die zwischengeschalteten Verknüpfungsdatensätze anzuzeigen.
## Voraussetzungen
1. **Verknüpfungsbeziehungen im Lab aktivieren**: Gehen Sie zu **Einstellungen → Updates → Lab** und aktivieren Sie **Verknüpfungsbeziehungen**
2. **Erweiterten Modus aktivieren**: Aktivieren Sie den **Erweiterten Modus** unten rechts in der Seitenleiste der Einstellungen
3. Planen Sie Ihr Datenmodell:
* Welche zwei Objekte verbinden Sie?
* Wie soll das Verknüpfungsobjekt heißen?
## Schritt 1: Verknüpfungsobjekt erstellen
Erstellen Sie zunächst das Zwischenobjekt, das die Verknüpfungen speichert.
1. Gehen Sie zu **Einstellungen → Datenmodell**
2. Klicken Sie auf **+ Neues Objekt**
3. Benennen Sie es aussagekräftig (z. B. "Projektzuweisung", "Teammitglied", "Produktbestellung")
4. Klicken Sie auf **Speichern**
<Tip>
**Namenskonvention**: Verwenden Sie einen Namen, der die Beziehung beschreibt, z. B. "Projektzuweisung" oder "Teammitgliedschaft". Das macht das Datenmodell leichter verständlich.
</Tip>
## Schritt 2: Beziehungen vom Verknüpfungsobjekt erstellen
Fügen Sie vom Verknüpfungsobjekt aus Beziehungsfelder zu beiden Objekten hinzu, die Sie verbinden möchten.
### Erste Beziehung (Verknüpfung → Objekt A)
1. Wählen Sie Ihr Verknüpfungsobjekt unter **Einstellungen → Datenmodell** aus
2. Klicken Sie auf **+ Feld hinzufügen**
3. Wählen Sie **Relation** als Feldtyp
4. Wählen Sie das erste Objekt aus (z. B. "Personen")
5. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest (viele Zuweisungen können mit einer Person verknüpft werden)
6. Benennen Sie die Felder:
* Feld auf der Verknüpfung: z. B. "Person"
* Feld bei Personen: z. B. "Projektzuweisungen"
7. Klicken Sie auf **Speichern**
### Zweite Beziehung (Verknüpfung → Objekt B)
1. Bleiben Sie im Verknüpfungsobjekt und klicken Sie auf **+ Feld hinzufügen**
2. Wählen Sie **Relation** als Feldtyp
3. Wählen Sie das zweite Objekt aus (z. B. "Projekte")
4. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest
5. Benennen Sie die Felder:
* Feld auf der Verknüpfung: z. B. "Projekt"
* Feld bei Projekten: z. B. "Teammitglieder"
6. Klicken Sie auf **Speichern**
## Schritt 3: Anzeige der Verknüpfungsbeziehung konfigurieren
Konfigurieren Sie nun die Quellobjekte so, dass verknüpfte Datensätze direkt angezeigt werden, wobei das zwischengeschaltete Verknüpfungsobjekt übersprungen wird.
1. Gehen Sie zu **Einstellungen → Datenmodell**
2. Wählen Sie das erste Objekt aus (z. B. "Personen")
3. Suchen Sie das Beziehungsfeld, das auf das Verknüpfungsobjekt zeigt (z. B. "Projektzuweisungen")
4. Klicken Sie, um das Feld zu bearbeiten
5. Aktivieren Sie **"Dies ist eine Beziehung zu einem Verknüpfungsobjekt"**
6. Wählen Sie die **Zielbeziehung** aus (z. B. "Projekt" — das Feld an der Verknüpfung, das auf die andere Seite zeigt)
7. Klicken Sie auf **Speichern**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Wiederholen Sie dies für das andere Objekt:
1. Wählen Sie "Projekte" im Datenmodell aus
2. Bearbeiten Sie das Beziehungsfeld "Teammitglieder"
3. Aktivieren Sie den Verknüpfungsschalter
4. Wählen Sie "Person" als Zielbeziehung aus
5. Speichern
## Ergebnis
Nach der Konfiguration:
* In einem **Person**-Datensatz zeigt das Feld "Projektzuweisungen" **Projekte** direkt an (keine Zuweisungsdatensätze)
* In einem **Projekt**-Datensatz zeigt das Feld "Teammitglieder" **Personen** direkt an
Das Verknüpfungsobjekt existiert weiterhin und speichert die Verknüpfungen, aber die UI präsentiert eine übersichtlichere Viele-zu-Viele-Ansicht.
## Beispiel: Personen ↔ Projekte
Hier ist eine vollständige Schritt-für-Schritt-Anleitung:
### Verknüpfungsobjekt erstellen
* Name: **Projektzuweisung**
* Beschreibung: "Verknüpft Personen mit den Projekten, an denen sie arbeiten"
### Beziehungen hinzufügen
1. **Projektzuweisung → Personen**
* Typ: Viele-zu-Eins
* Feld bei Zuweisung: "Person"
* Feld bei Personen: "Projektzuweisungen"
2. **Projektzuweisung → Projekte**
* Typ: Viele-zu-Eins
* Feld bei Zuweisung: "Projekt"
* Feld bei Projekten: "Teammitglieder"
### Verknüpfungsanzeige konfigurieren
1. Am Objekt **Personen**:
* Feld "Projektzuweisungen" bearbeiten
* Verknüpfungsschalter aktivieren
* Ziel: "Projekt"
2. Am Objekt **Projekte**:
* Feld "Teammitglieder" bearbeiten
* Verknüpfungsschalter aktivieren
* Ziel: "Person"
### Verwendung
* Öffnen Sie einen Person-Datensatz → Sehen Sie deren Projekte direkt
* Öffnen Sie einen Projekt-Datensatz → Sehen Sie Teammitglieder direkt
* Erstellen Sie neue Verknüpfungen von beiden Seiten
## Zusätzliche Daten zu Verknüpfungen hinzufügen
Da das Verknüpfungsobjekt ein echtes Objekt ist, können Sie benutzerdefinierte Felder hinzufügen, um Informationen über die Beziehung zu speichern:
* **Rolle**: "Entwickler", "Designer", "Manager"
* **Startdatum**: Wann sie dem Projekt beigetreten sind
* **Zugeordnete Stunden**: Wöchentliche Stunden für dieses Projekt
Um auf diese Daten zuzugreifen, navigieren Sie direkt zum Verknüpfungsobjekt oder greifen Sie per API-Abfrage darauf zu.
## Einschränkungen
* **CSV-Import/-Export**: Das direkte Importieren von Viele-zu-Viele-Beziehungen wird nicht unterstützt. Importieren Sie stattdessen Datensätze in das Verknüpfungsobjekt.
* **Filter**: Das Filtern nach Viele-zu-Viele-Beziehungen bietet möglicherweise nur begrenzte Optionen.
## Verwandt
* [Beziehungsfelder](/l/de/user-guide/data-model/capabilities/relation-fields) — Beziehungstypen erklärt
* [Benutzerdefinierte Objekte erstellen](/l/de/user-guide/data-model/how-tos/create-custom-objects) — so erstellen Sie Objekte
* [Beziehungsfelder erstellen](/l/de/user-guide/data-model/how-tos/create-relation-fields) — grundlegende Einrichtung von Beziehungen
@@ -9,7 +9,7 @@ API (Application Programming Interface) ermöglicht es Ihnen, Twenty mit anderen
## Apps
Apps sind benutzerdefinierte Erweiterungen, die als Code erstellt werden und Datenmodelle sowie serverlose Funktionen definieren können. Damit können Entwickler wiederverwendbare Anpassungen erstellen, die in mehreren Workspaces bereitgestellt werden können.
Apps sind benutzerdefinierte Erweiterungen, die als Code erstellt werden und Datenmodelle sowie Logikfunktionen definieren können. Damit können Entwickler wiederverwendbare Anpassungen erstellen, die in mehreren Workspaces bereitgestellt werden können.
## Code-Aktionen
@@ -136,7 +136,7 @@ Sie können Dateien an E-Mails anhängen, die von Workflows gesendet werden. Der
| **Veranstaltungsbestätigungen** | Veranstaltungsdetails oder Agenda |
<Note>
Anhänge sind statisch—dieselbe Datei wird an alle Empfänger gesendet. Für dynamische Dokumente (z. B. personalisierte Angebote) erstellen und hängen Sie Dateien mithilfe einer [Serverless-Funktion](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) an.
Anhänge sind statisch—dieselbe Datei wird an alle Empfänger gesendet. For dynamic documents (like personalized quotes), generate and attach files using a [Logic Function](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
</Note>
## Beste Praktiken
@@ -256,7 +256,7 @@ Führt benutzerdefiniertes JavaScript in Ihrem Workflow aus.
* Code direkt im Schritt testen
<Note>
If you need to use external API keys in your code, you must input them directly in the function body. You cannot configure API keys elsewhere and reference them in the serverless function.
If you need to use external API keys in your code, you must input them directly in the function body. Sie können API-Schlüssel nicht an anderer Stelle konfigurieren und sie dann in der Logikfunktion referenzieren.
</Note>
<Tip>
@@ -7,7 +7,7 @@ Automatisch ein PDF generieren oder abrufen und an einen Datensatz in Twenty anh
## Übersicht
Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Bedarf für jeden ausgewählten Datensatz ein PDF generieren können. Eine **Serverlose Funktion** übernimmt:
Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Bedarf für jeden ausgewählten Datensatz ein PDF generieren können. Eine **Logikfunktion** übernimmt:
1. Das Herunterladen des PDFs von einer URL (von einem PDF-Generierungsdienst)
2. Das Hochladen der Datei in Twenty
@@ -17,7 +17,7 @@ Dieser Workflow verwendet einen **Manuellen Auslöser**, damit Benutzer bei Beda
Bevor Sie den Workflow einrichten:
1. **API-Schlüssel erstellen**: Gehen Sie zu **Einstellungen → APIs** und erstellen Sie einen neuen API-Schlüssel. Sie benötigen dieses Token für die serverlose Funktion.
1. **API-Schlüssel erstellen**: Gehen Sie zu **Einstellungen → APIs** und erstellen Sie einen neuen API-Schlüssel. Sie benötigen dieses Token für die Logikfunktion.
2. **Richten Sie einen PDF-Generierungsdienst ein** (optional): Wenn Sie PDFs dynamisch generieren möchten (z. B. Angebote), verwenden Sie einen Dienst wie Carbone, PDFMonkey oder DocuSeal, um das PDF zu erstellen und eine Download-URL zu erhalten.
## Schritt-für-Schritt-Einrichtung
@@ -32,9 +32,9 @@ Bevor Sie den Workflow einrichten:
Mit einem manuellen Auslöser können Benutzer diesen Workflow über eine Schaltfläche ausführen, die oben rechts erscheint, sobald ein Datensatz ausgewählt ist, um ein PDF zu generieren und anzuhängen.
</Tip>
### Schritt 2: Serverlose Funktion hinzufügen
### Schritt 2: Logikfunktion hinzufügen
1. Fügen Sie eine **Serverlose Funktion**-Aktion hinzu
1. Fügen Sie eine **Code**-Aktion (Logikfunktion) hinzu
2. Erstellen Sie eine neue Funktion mit dem folgenden Code
3. Konfigurieren Sie die Eingabeparameter
@@ -45,10 +45,10 @@ Bevor Sie den Workflow einrichten:
| `companyId` | `{{trigger.object.id}}` |
<Note>
Wenn Sie an ein anderes Objekt anhängen (Person, Opportunity usw.), benennen Sie den Parameter entsprechend um (z. B. `personId`, `opportunityId`) und aktualisieren Sie die serverlose Funktion.
Wenn Sie an ein anderes Objekt anhängen (Person, Opportunity usw.), benennen Sie den Parameter entsprechend um (z. B. `personId`, `opportunityId`) und aktualisieren Sie die Logikfunktion.
</Note>
#### Code der serverlosen Funktion
#### Code der Logikfunktion
```typescript
export const main = async (
@@ -172,7 +172,7 @@ Aktualisieren Sie sowohl den Funktionsparameter als auch das Objekt `variables.d
Wenn Sie einen PDF-Generierungsdienst verwenden, können Sie:
1. Führen Sie zuerst eine HTTP-Request-Aktion aus, um das PDF zu generieren
2. Übergeben Sie die zurückgegebene PDF-URL als Parameter an die serverlose Funktion
2. Übergeben Sie die zurückgegebene PDF-URL als Parameter an die Logikfunktion
```typescript
export const main = async (
@@ -211,7 +211,7 @@ Zum Erstellen dynamischer Angebote oder Rechnungen:
* **DocuSeal** Plattform für Dokumentenautomatisierung
* **Documint** API-first-Dokumentenerstellung
Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie anschließend an die serverlose Funktion übergeben können.
Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie anschließend an die Logikfunktion übergeben können.
## Fehlerbehebung
@@ -224,5 +224,5 @@ Jeder Dienst stellt eine API bereit, die eine PDF-URL zurückgibt, die Sie ansch
## Verwandt
* [Workflow-Trigger](/l/de/user-guide/workflows/capabilities/workflow-triggers)
* [Serverlose Funktionen](/l/de/user-guide/workflows/capabilities/workflow-actions#serverless-function)
* [Logikfunktionen](/l/de/user-guide/workflows/capabilities/workflow-actions#code)
* [Ein Angebot oder eine Rechnung aus Twenty generieren](/l/de/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -131,7 +131,7 @@ description: Häufig gestellte Fragen zu Workflows in Twenty.
</Accordion>
<Accordion title="Wie lang ist die maximale Ausführungszeit für Code-Aktionen?">
Code-Aktionen (serverlose Funktionen) haben ein **Standard-Timeout von 5 Minuten** (300 Sekunden).
Code-Aktionen (Logikfunktionen) haben ein **Standard-Timeout von 5 Minuten** (300 Sekunden).
Das maximal konfigurierbare Timeout beträgt **15 Minuten** (900 Sekunden).
@@ -1,22 +1,22 @@
---
title: Best Practices
title: Mejores prácticas
---
This document outlines the best practices you should follow when working on the backend.
Este documento describe las mejores prácticas que debe seguir al trabajar en el backend.
## Follow a modular approach
## Siga un enfoque modular
The backend follows a modular approach, which is a fundamental principle when working with NestJS. Make sure you break down your code into reusable modules to maintain a clean and organized codebase.
Each module should encapsulate a particular feature or functionality and have a well-defined scope. This modular approach enables clear separation of concerns and removes unnecessary complexities.
El backend sigue un enfoque modular, que es un principio fundamental al trabajar con NestJS. Asegúrese de descomponer su código en módulos reutilizables para mantener una base de código limpia y organizada.
Cada módulo debe encapsular una característica o funcionalidad particular y tener un alcance bien definido. Este enfoque modular permite una clara separación de responsabilidades y elimina complejidades innecesarias.
## Expose services to use in modules
## Exponer servicios para usar en módulos
Always create services that have a clear and single responsibility, which enhances code readability and maintainability. Name the services descriptively and consistently.
Siempre cree servicios que tengan una responsabilidad clara y única, lo que mejora la legibilidad y mantenibilidad del código. Nombre los servicios de manera descriptiva y consistente.
You should also expose services that you want to use in other modules. Exposing services to other modules is possible through NestJS's powerful dependency injection system, and promotes loose coupling between components.
También debe exponer servicios que desee usar en otros módulos. Exponer servicios a otros módulos es posible a través del poderoso sistema de inyección de dependencias de NestJS, y promueve un acoplamiento débil entre los componentes.
## Avoid using `any` type
## Evitar usar el tipo `any`
When you declare a variable as `any`, TypeScript's type checker doesn't perform any type checking, making it possible to assign any type of values to the variable. TypeScript uses type inference to determine the type of variable based on the value. By declaring it as `any`, TypeScript can no longer infer the type. This makes it hard to catch type-related errors during development, leading to runtime errors and makes the code less maintainable, less reliable, and harder to understand for others.
Cuando declara una variable como `any`, el verificador de tipos de TypeScript no realiza ninguna comprobación de tipos, lo que hace posible asignar cualquier tipo de valores a la variable. TypeScript utiliza la inferencia de tipos para determinar el tipo de la variable a partir del valor. Al declararlo como `any`, TypeScript ya no puede inferir el tipo. Esto dificulta la captura de errores relacionados con el tipo durante el desarrollo, lo que lleva a errores en tiempo de ejecución y hace que el código sea menos mantenible, menos fiable y más difícil de entender para otros.
This is why everything should have a type. So if you create a new object with a first name and last name, you should create an interface or type that contains a first name and last name that defines the shape of the object you are manipulating.
Por eso todo debe tener un tipo. Entonces, si crea un nuevo objeto con un nombre y apellido, debe crear una interfaz o tipo que contenga un nombre y apellido y defina la forma del objeto que está manipulando.
@@ -1,39 +1,39 @@
---
title: Custom Objects
title: Objetos personalizados
---
Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects.
Los objetos son estructuras que te permiten almacenar datos (registros, atributos y valores) específicos de una organización. Twenty proporciona tanto objetos estándar como personalizados.
Standard objects are in-built objects with a set of attributes available for all users. Examples of standard objects in Twenty include Company and Person. Standard objects have standard fields that are also available for all Twenty users, like Company.displayName.
Los objetos estándar son objetos incorporados con un conjunto de atributos disponibles para todos los usuarios. Ejemplos de objetos estándar en Twenty incluyen Empresa y Persona. Los objetos estándar tienen campos estándar que también están disponibles para todos los usuarios de Twenty, como Company.displayName.
Custom objects are objects that you can create to store information that is unique to your organization. They are not built-in; members of your workspace can create and customize custom objects to hold information that standard objects aren't suitable for.
Los objetos personalizados son objetos que puedes crear para almacenar información que es única para tu organización. No están incorporados; los miembros de tu espacio de trabajo pueden crear y personalizar objetos personalizados para albergar información para la cual los objetos estándar no son aptos.
## High-level schema
## Esquema de alto nivel
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="High level schema" />
<img src="/images/docs/server/custom-object-schema.png" alt="Esquema de alto nivel" />
</div>
<br />
## How it works
## Cómo funciona
Custom objects come from metadata tables that determine the shape, name, and type of the objects. All this information is present in the metadata schema database, consisting of tables:
Los objetos personalizados provienen de tablas de metadatos que determinan la forma, el nombre y el tipo de los objetos. Toda esta información está presente en la base de datos del esquema de metadatos, que consta de tablas:
* **DataSource**: Details where the data is present.
* **Object**: Describes the object and links to a DataSource.
* **Field**: Outlines an Object's fields and connects to the Object.
* **DataSource**: Detalles de dónde se encuentra la información.
* **Object**: Describe el objeto y lo vincula a un DataSource.
* **Field**: Describe los campos de un objeto y lo conecta al objeto.
To add a custom object, the workspaceMember will query the /metadata API. This updates the metadata accordingly and computes a GraphQL schema based on the metadata, storing it in a GQL cache for later use.
Para añadir un objeto personalizado, el workspaceMember consultará la API de /metadata. Esto actualiza los metadatos de acuerdo y calcula un esquema GraphQL basado en los metadatos, almacenándolo en un caché de GQL para su uso posterior.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Query the /metadata API to add custom objects" />
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Consultando la API de /metadata para añadir objetos personalizados" />
</div>
<br />
To fetch data, the process involves making queries through the /graphql endpoint and passing them through the Query Resolver.
Para obtener datos, el proceso implica hacer consultas a través del endpoint /graphql y pasarlos a través del Query Resolver.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Query the /graphql endpoint to fetch data" />
<img src="/images/docs/server/custom-object-schema.png" alt="Consulta el endpoint /graphql para obtener datos" />
</div>
@@ -1,12 +1,12 @@
---
title: Feature Flags
title: Indicadores de característica
---
Feature flags are used to hide experimental features. For Twenty, they are set on workspace level and not on a user level.
Los indicadores de característica se usan para ocultar características experimentales. Para Twenty, se configuran a nivel de espacio de trabajo y no a nivel de usuario.
## Adding a new feature flag
## Agregar un nuevo indicador de característica
In `FeatureFlagKey.ts` add the feature flag:
En `FeatureFlagKey.ts` agrega el indicador de característica:
```ts
type FeatureFlagKey =
@@ -14,7 +14,7 @@ type FeatureFlagKey =
| ...;
```
Also add it to the enum in `feature-flag.entity.ts`:
También agrégalo al enum en `feature-flag.entity.ts`:
```ts
enum FeatureFlagKeys {
@@ -23,7 +23,7 @@ enum FeatureFlagKeys {
}
```
To apply a feature flag on a **backend** feature use:
Para aplicar un indicador de característica en una característica de **backend** usa:
```ts
@Gate({
@@ -31,16 +31,16 @@ To apply a feature flag on a **backend** feature use:
})
```
To apply a feature flag on a **frontend** feature use:
Para aplicar un indicador de característica en una característica de **frontend** usa:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Configure feature flags for the deployment
## Configurar indicadores de característica para el despliegue
Change the corresponding record in the Table `core.featureFlag`:
Cambie el registro correspondiente en la Tabla `core.featureFlag`:
| id | key | workspaceId | value |
| ------ | ------------------------ | ----------- | ------ |
| Random | `IS_FEATURENAME_ENABLED` | WorkspaceID | `true` |
| iD | clave | workspaceId | valor |
| --------- | ------------------------ | ------------------------- | ----------- |
| Aleatorio | `IS_FEATURENAME_ENABLED` | ID del espacio de trabajo | `verdadero` |
@@ -1,12 +1,12 @@
---
title: Folder Architecture
info: A detailed look into our server folder architecture
title: Arquitectura de Carpetas
info: Una mirada detallada a la arquitectura de carpetas de nuestro servidor
---
The backend directory structure is as follows:
La estructura del directorio backend es la siguiente:
```
server
servidor
└───ability
└───constants
└───core
@@ -21,105 +21,105 @@ server
└───utils
```
## Ability
## Habilidad
Defines permissions and includes handlers for each entity.
Define permisos e incluye gestores para cada entidad.
## Decorators
## Decoradores
Defines custom decorators in NestJS for added functionality.
Define decoradores personalizados en NestJS para funcionalidad adicional.
See [custom decorators](https://docs.nestjs.com/custom-decorators) for more details.
Ver [decoradores personalizados](https://docs.nestjs.com/custom-decorators) para más detalles.
## Filters
## Filtros
Includes exception filters to handle exceptions that might occur in GraphQL endpoints.
Incluye filtros de excepciones para manejar excepciones que puedan ocurrir en endpoints de GraphQL.
## Guards
## Guardias
See [guards](https://docs.nestjs.com/guards) for more details.
Ver [guardias](https://docs.nestjs.com/guards) para más detalles.
## Health
## Salud
Includes a publicly available REST API (healthz) that returns a JSON to confirm whether the database is working as expected.
Incluye una API REST públicamente disponible (healthz) que devuelve un JSON para confirmar si la base de datos está funcionando como se esperaba.
## Metadata
## Metadatos
Defines custom objects and makes available a GraphQL API (graphql/metadata).
Define objetos personalizados y hace disponible una API de GraphQL (graphql/metadata).
## Workspace
## Espacio de trabajo
Generates and serves custom GraphQL schema based on the metadata.
Genera y sirve un esquema GraphQL personalizado basado en los metadatos.
### Workspace Directory Structure
### Estructura del Directorio de Espacio de Trabajo
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───construcción-esquema-espacio-de-trabajo
└───fábricas
└───tipos-graphql
└───bases-datos
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───definiciones-objetos
└───servicios
└───almacenamiento
└───utilidades
└───constructor-resolver-espacio-de-trabajo
└───fábricas
└───interfaces
└───workspace-query-builder
└───factories
└───constructor-consultas-espacio-de-trabajo
└───fábricas
└───interfaces
└───workspace-query-runner
└───ejecutor-consultas-espacio-de-trabajo
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
└───utilidades
└───fuente-datos-espacio-de-trabajo
└───gestor-espacio-de-trabajo
└───ejecutor-migraciones-espacio-de-trabajo
└───utilidades
└───espacio.trabajo.module.ts
└───espacio.trabajo.factory.spec.ts
└───espacio.trabajo.factory.ts
```
The root of the workspace directory includes the `workspace.factory.ts`, a file containing the `createGraphQLSchema` function. This function generates workspace-specific schema by using the metadata to tailor a schema for individual workspaces. By separating the schema and resolver construction, we use the `makeExecutableSchema` function, which combines these discrete elements.
La raíz del directorio de espacio de trabajo incluye el `espacio.trabajo.factory.ts`, un archivo que contiene la función `createGraphQLSchema`. Esta funcn genera un esquema específico para el espacio de trabajo utilizando los metadatos para adaptar un esquema para espacios de trabajo individuales. Al separar la construcción del esquema y del resolver, usamos la función `makeExecutableSchema`, que combina estos elementos discretos.
This strategy is not just about organization, but also helps with optimization, such as caching generated type definitions to enhance performance and scalability.
Esta estrategia no solo se trata de organización, sino que también ayuda con la optimización, como el almacenamiento en caché de definiciones de tipos generados para mejorar el rendimiento y la escalabilidad.
### Workspace Schema builder
### Constructor de Esquema de Espacio de Trabajo
Generates the GraphQL schema, and includes:
Genera el esquema GraphQL, e incluye:
#### Factories:
#### Fábricas:
Specialised constructors to generate GraphQL-related constructs.
Constructores especializados para generar constructos relacionados con GraphQL.
* The type.factory translates field metadata into GraphQL types using `TypeMapperService`.
* The type-definition.factory creates GraphQL input or output objects derived from `objectMetadata`.
* La fábrica de tipos traduce los metadatos de los campos en tipos GraphQL utilizando `TypeMapperService`.
* La fábrica de definiciones de tipos crea objetos de entrada o salida de GraphQL derivados de `objectMetadata`.
#### GraphQL Types
#### Tipos GraphQL
Includes enumerations, inputs, objects, and scalars, and serves as the building blocks for the schema construction.
Incluye enumeraciones, entradas, objetos y escalares, y sirve como bloques de construcción para la construcción del esquema.
#### Interfaces and Object Definitions
#### Interfaces y Definiciones de Objetos
Contains the blueprints for GraphQL entities, and includes both predefined and custom types like `MONEY` or `URL`.
Contiene los planos para entidades GraphQL, e incluye tanto tipos predefinidos como personalizados como `MONEY` o `URL`.
#### Services
#### Servicios
Contains the service responsible for associating FieldMetadataType with its appropriate GraphQL scalar or query modifiers.
Contiene el servicio responsable de asociar FieldMetadataType con su escalar de GraphQL apropiado o modificadores de consulta.
#### Storage
#### Almacenamiento
Includes the `TypeDefinitionsStorage` class that contains reusable type definitions, preventing duplication of GraphQL types.
Incluye la clase `TypeDefinitionsStorage` que contiene definiciones de tipos reutilizables, previniendo duplicación de tipos GraphQL.
### Workspace Resolver Builder
### Constructor de Resolver de Espacio de Trabajo
Creates resolver functions for querying and mutating the GraphQL schema.
Crea funciones de resolutor para consultar y modificar el esquema GraphQL.
Each factory in this directory is responsible for producing a distinct resolver type, such as the `FindManyResolverFactory`, designed for adaptable application across various tables.
Cada fábrica en este directorio es responsable de producir un tipo de resolutor distinto, como el `FindManyResolverFactory`, diseñado para aplicación adaptable a través de varias tablas.
### Workspace Query Runner
### Ejecutor de Consultas de Espacio de Trabajo
Runs the generated queries on the database and parses the result.
Ejecuta las consultas generadas en la base de datos y analiza el resultado.
@@ -1,20 +1,20 @@
---
title: Message Queue
title: Cola de Mensajes
---
Queues facilitate async operations to be performed. They can be used for performing background tasks such as sending a welcome email on register.
Each use case will have its own queue class extended from `MessageQueueServiceBase`.
Las colas facilitan la realización de operaciones asíncronas. Se pueden utilizar para realizar tareas en segundo plano, como enviar un correo de bienvenida al registrarse.
Cada caso de uso tendrá su propia clase de cola extendida de `MessageQueueServiceBase`.
Currently, we only support `bull-mq`[bull-mq](https://bullmq.io/) as the queue driver.
Actualmente, solo soportamos `bull-mq`[bull-mq](https://bullmq.io/) como el controlador de cola.
## Steps to create and use a new queue
## Pasos para crear y usar una nueva cola
1. Add a queue name for your new queue under enum `MESSAGE_QUEUES`.
2. Provide the factory implementation of the queue with the queue name as the dependency token.
3. Inject the queue that you created in the required module/service with the queue name as the dependency token.
4. Add worker class with token based injection just like producer.
1. Agregue un nombre de cola para su nueva cola en la enumeración `MESSAGE_QUEUES`.
2. Proporcione la implementación de fábrica de la cola con el nombre de la cola como el token de dependencia.
3. Inyecte la cola que creó en el módulo/servicio requerido con el nombre de la cola como el token de dependencia.
4. Agregue una clase de trabajador con inyección basada en token, al igual que el productor.
### Example usage
### Ejemplo de uso
```ts
class Resolver {
@@ -1,19 +1,19 @@
---
title: Backend Commands
title: Comandos de Backend
---
## Useful commands
## Comandos útiles
These commands should be executed from packages/twenty-server folder.
From any other folder you can run `npx nx {command} twenty-server` (or `npx nx run twenty-server:{command}`).
Estos comandos deben ejecutarse desde la carpeta packages/twenty-server.
Desde cualquier otra carpeta, puedes ejecutar `npx nx {command} twenty-server` (o `npx nx run twenty-server:{command}`).
### First time setup
### Configuración inicial
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Starting the server
### Iniciando el servidor
```
npx nx run twenty-server:start
@@ -25,53 +25,52 @@ npx nx run twenty-server:start
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
### Prueba
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Note: you can run `npx nx run twenty-server:test:integration:with-db-reset` in case you need to reset the database before running the integration tests.
Nota: puedes ejecutar `npx nx run twenty-server:test:integration:with-db-reset` en caso de que necesites restablecer la base de datos antes de ejecutar las pruebas de integración.
### Resetting the database
### Restablecer la base de datos
If you want to reset and seed the database, you can run the following command:
Si deseas restablecer y sembrar la base de datos, puedes ejecutar el siguiente comando:
```bash
npx nx run twenty-server:database:reset
```
### Migrations
### Migraciones
#### For objects in Core/Metadata schemas (TypeORM)
#### Para objetos en esquemas Core/Metadata (TypeORM)
```bash
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/nameOfYourMigration -d src/database/typeorm/core/core.datasource.ts
```
#### For Workspace objects
#### Para objetos de Workspace
There are no migrations files, migration are generated automatically for each workspace,
stored in the database, and applied with this command
No hay archivos de migraciones, las migraciones se generan automáticamente para cada espacio de trabajo, se almacenan en la base de datos y se aplican con este comando
```bash
npx nx run twenty-server:command workspace:sync-metadata -f
```
<Warning>
This will drop the database and re-run the migrations and seed.
Esto eliminará la base de datos y volverá a ejecutar las migraciones y semillas.
Make sure to back up any data you want to keep before running this command.
Asegúrate de respaldar cualquier dato que desees conservar antes de ejecutar este comando.
</Warning>
## Tech Stack
## Stack Tecnológico
Twenty primarily uses NestJS for the backend.
Twenty utiliza principalmente NestJS para el backend.
Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM.
Prisma fue el primer ORM que usamos. Pero para permitir a los usuarios crear campos y objetos personalizados, un nivel más bajo tenía más sentido ya que necesitamos tener un control detallado. El proyecto ahora usa TypeORM.
Here's what the tech stack now looks like.
Así es como se ve la pila tecnológica ahora.
**Core**
@@ -79,23 +78,23 @@ Here's what the tech stack now looks like.
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**Database**
**Base de datos**
* [Postgres](https://www.postgresql.org/)
**Third-party integrations**
**Integraciones de terceros**
* [Sentry](https://sentry.io/welcome/) for tracking bugs
* [Sentry](https://sentry.io/welcome/) para rastrear errores
**Testing**
**Pruebas**
* [Jest](https://jestjs.io/)
**Tooling**
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [ESLint](https://eslint.org/)
**Development**
**Desarrollo**
* [AWS EKS](https://aws.amazon.com/eks/)
@@ -1,18 +1,18 @@
---
title: Zapier App
title: Aplicación Zapier
---
Effortlessly sync Twenty with 3000+ apps using [Zapier](https://zapier.com/). Automate tasks, boost productivity, and supercharge your customer relationships!
Sincroniza sin esfuerzo Twenty con más de 3000 aplicaciones usando [Zapier](https://zapier.com/). ¡Automatiza tareas, mejora la productividad y potencia tus relaciones con los clientes!
## About Zapier
## Acerca de Zapier
Zapier is a tool that allows you to automate workflows by connecting the apps that your team uses every day. The fundamental concept of Zapier is automation workflows, called Zaps, and include triggers and actions.
Zapier es una herramienta que permite automatizar flujos de trabajo al conectar las aplicaciones que tu equipo utiliza a diario. El concepto fundamental de Zapier son los flujos de trabajo automatizados, llamados Zaps, que incluyen desencadenantes y acciones.
You can learn more about how Zapier works [here](https://zapier.com/how-it-works).
Puedes aprender más sobre cómo funciona Zapier [aquí](https://zapier.com/how-it-works).
## Setup
## Configuración
### Step 1: Install Zapier packages
### Paso 1: Instalar paquetes de Zapier
```bash
cd packages/twenty-zapier
@@ -20,33 +20,33 @@ cd packages/twenty-zapier
yarn
```
### Step 2: Login with the CLI
### Paso 2: Iniciar sesión con la CLI
Use your Zapier credentials to log in using the CLI:
Utiliza tus credenciales de Zapier para iniciar sesión usando la CLI:
```bash
zapier login
```
### Step 3: Set environment variables
### Paso 3: Configurar variables de entorno
From the `packages/twenty-zapier` folder, run:
Desde la carpeta `packages/twenty-zapier`, ejecuta:
```bash
cp .env.example .env
```
Run the application locally, go to [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), and generate an API key.
Ejecuta la aplicación localmente, ve a [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks) y genera una clave API.
Replace the **YOUR_API_KEY** value in the `.env` file with the API key you just generated.
Reemplaza el valor de **YOUR_API_KEY** en el archivo `.env` con la clave API que acabas de generar.
## Development
## Desarrollo
<Warning>
Make sure to run `yarn build` before any `zapier` command.
Asegúrate de ejecutar `yarn build` antes de cualquier comando `zapier`.
</Warning>
### Test
### Prueba
```bash
yarn test
@@ -58,25 +58,25 @@ yarn test
yarn format
```
### Watch and compile as you edit code
### Observa y compila mientras editas el código
```bash
yarn watch
```
### Validate your Zapier app
### Valida tu aplicación Zapier
```bash
yarn validate
```
### Deploy your Zapier app
### Despliega tu aplicación Zapier
```bash
yarn deploy
```
### List all Zapier CLI commands
### Lista todos los comandos de Zapier CLI
```bash
zapier
@@ -1,78 +1,78 @@
---
title: Bugs, Requests & Pull Requests
info: Report issues, request features, and contribute code
title: Errores, solicitudes y pull requests
info: Informa de problemas, solicita funcionalidades y contribuye con código
---
## Reporting Bugs
## Reportar errores
To report a bug, please [create an issue on GitHub](https://github.com/twentyhq/twenty/issues/new).
Para reportar un error, por favor [crea un problema en GitHub](https://github.com/twentyhq/twenty/issues/new).
You can also ask for help on [Discord](https://discord.gg/cx5n4Jzs57).
También puedes pedir ayuda en [Discord](https://discord.gg/cx5n4Jzs57).
## Feature Requests
## Solicitudes de funciones
If you're not sure if it's a bug, and you feel it's closer to a feature request, then you should probably [open a discussion instead](https://github.com/twentyhq/twenty/discussions/new).
Si no estás seguro de si es un error y sientes que está más cerca de una solicitud de función, entonces probablemente deberías [abrir una discusión en su lugar](https://github.com/twentyhq/twenty/discussions/new).
## Submit a Pull Request
## Envía un pull request
Contributing code to Twenty starts with a pull request (PR).
Contribuir código a Twenty comienza con un pull request (PR).
### Before You Start
### Antes de empezar
1. Check [existing issues](https://github.com/twentyhq/twenty/issues) for related work
2. For new features, open an issue first to discuss
3. Review our [Code of Conduct](https://github.com/twentyhq/twenty/blob/main/CODE_OF_CONDUCT.md)
1. Consulta [los issues existentes](https://github.com/twentyhq/twenty/issues) para ver trabajos relacionados
2. Para nuevas funcionalidades, abre primero un issue para discutir
3. Revisa nuestro [Código de conducta](https://github.com/twentyhq/twenty/blob/main/CODE_OF_CONDUCT.md)
### Fork and Clone
### Haz un fork y clona
1. Fork the repository on GitHub
2. Clone your fork:
1. Haz un fork del repositorio en GitHub
2. Clona tu fork:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. Add upstream remote:
3. Añade el remoto upstream:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### Create a Branch
### Crea una rama
```bash
git checkout -b feature/your-feature-name
```
Use descriptive branch names:
Usa nombres de rama descriptivos:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### Make Your Changes
### Realiza tus cambios
1. Write clean, well-documented code
2. Follow existing code style
3. Add tests for new functionality
4. Update documentation if needed
1. Escribe código limpio y bien documentado
2. Sigue el estilo de código existente
3. Añade pruebas para la nueva funcionalidad
4. Actualiza la documentación si es necesario
### Submit Your PR
### Envía tu PR
1. Push your branch:
1. Haz push de tu rama:
```bash
git push origin feature/your-feature-name
```
2. Open a PR on GitHub
3. Fill in the PR template
4. Link related issues
2. Abre un PR en GitHub
3. Rellena la plantilla del PR
4. Vincula los issues relacionados
### PR Checklist
### Lista de verificación de PR
* [ ] Code follows project style guidelines
* [ ] Tests pass locally
* [ ] Documentation is updated
* [ ] PR description explains the changes
* [ ] El código sigue las pautas de estilo del proyecto
* [ ] Las pruebas pasan localmente
* [ ] La documentación está actualizada
* [ ] La descripción del PR explica los cambios
@@ -1,19 +1,19 @@
---
title: Best Practices
title: Mejores prácticas
---
This document outlines the best practices you should follow when working on the frontend.
Este documento describe las mejores prácticas que debes seguir al trabajar en el frontend.
## State management
## Gestión de estado
React and Recoil handle state management in the codebase.
React y Recoil manejan la gestión de estado en la base de código.
### Use `useRecoilState` to store state
### Usa `useRecoilState` para almacenar el estado
It's good practice to create as many atoms as you need to store your state.
Es buena práctica crear tantos átomos como necesites para almacenar tu estado.
<Warning>
It's better to use extra atoms than trying to be too concise with props drilling.
Es mejor usar átomos adicionales que intentar ser demasiado concisos con la perforación de props.
</Warning>
```tsx
@@ -36,29 +36,29 @@ export const MyComponent = () => {
}
```
### Do not use `useRef` to store state
### No uses `useRef` para almacenar el estado
Avoid using `useRef` to store state.
Evita usar `useRef` para almacenar el estado.
If you want to store state, you should use `useState` or `useRecoilState`.
See [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
Consulta [cómo gestionar las re-renderizaciones](#managing-re-renders) si sientes que necesitas `useRef` para evitar algunas re-renderizaciones.
## Managing re-renders
## Gestión de las re-renderizaciones
Re-renders can be hard to manage in React.
Las re-renderizaciones pueden ser difíciles de gestionar en React.
Here are some rules to follow to avoid unnecessary re-renders.
Aquí hay algunas reglas a seguir para evitar re-renderizaciones innecesarias.
Keep in mind that you can **always** avoid re-renders by understanding their cause.
Ten en cuenta que siempre puedes evitar re-renderizaciones comprendiendo su causa.
### Work at the root level
### Trabaja a nivel de raíz
Avoiding re-renders in new features is now made easy by eliminating them at the root level.
Ahora es fácil evitar re-renderizaciones en nuevas funciones eliminándolas a nivel de raíz.
The `PageChangeEffect` sidecar component contains just one `useEffect` that holds all the logic to execute on a page change.
That way you know that there's just one place that can trigger a re-render.
De esa manera, sabes que solo hay un lugar que puede desencadenar una re-renderización.
### Always think twice before adding `useEffect` in your codebase
@@ -68,17 +68,17 @@ You should think whether you need `useEffect`, or if you can move the logic in a
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
You can also find them in libraries like Apollo: `onCompleted`, `onError`, etc.
También puedes encontrarlas en bibliotecas como Apollo: `onCompleted`, `onError`, etc.
### Use a sibling component to extract `useEffect` or data fetching logic
If you feel like you need to add a `useEffect` in your root component, you should consider extracting it in a sidecar component.
You can apply the same for data fetching logic, with Apollo hooks.
Puedes aplicar lo mismo para la lógica de obtención de datos, con hooks de Apollo.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
// ❌ Malo, provocará re-renderizados incluso si los datos no cambian,
// porque useEffect necesita volver a evaluarse
export const PageComponent = () => {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
@@ -100,8 +100,8 @@ export const App = () => (
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
// ✅ Bueno, no provocará re-renderizados si los datos no cambian,
// porque useEffect se vuelve a evaluar en otro componente hermano
export const PageComponent = () => {
const [data, setData] = useRecoilState(dataState);
@@ -129,84 +129,84 @@ export const App = () => (
);
```
### Use recoil family states and recoil family selectors
### Usa estados de familia de recoil y selectores de familia de recoil
Recoil family states and selectors are a great way to avoid re-renders.
Los estados y selectores de familia de recoil son una gran manera de evitar re-renderizaciones.
They are useful when you need to store a list of items.
Son útiles cuando necesitas almacenar una lista de elementos.
### You shouldn't use `React.memo(MyComponent)`
### No deberías usar `React.memo(MyComponent)`
Avoid using `React.memo()` because it does not solve the cause of the re-render, but instead breaks the re-render chain, which can lead to unexpected behavior and make the code very hard to refactor.
Evita usar `React.memo()` porque no resuelve la causa de la re-renderización, sino que rompe la cadena de re-renderización, lo que puede llevar a un comportamiento inesperado y hacer que el código sea muy difícil de refactorizar.
### Limit `useCallback` or `useMemo` usage
### Limita el uso de `useCallback` o `useMemo`
They are often not necessary and will make the code harder to read and maintain for a gain of performance that is unnoticeable.
A menudo no son necesarios y harán que el código sea más difícil de leer y mantener para una ganancia de rendimiento que es imperceptible.
## Console.logs
`console.log` statements are valuable during development, offering real-time insights into variable values and code flow. But, leaving them in production code can lead to several issues:
Las declaraciones `console.log` son valiosas durante el desarrollo, ofreciendo información en tiempo real sobre los valores de las variables y el flujo de código. Pero, dejarlas en el código de producción puede llevar a varios problemas:
1. **Performance**: Excessive logging can affect the runtime performance, especially on client-side applications.
1. **Rendimiento**: El registro excesivo puede afectar el rendimiento en tiempo de ejecución, especialmente en aplicaciones del lado del cliente.
2. **Security**: Logging sensitive data can expose critical information to anyone who inspects the browser's console.
2. **Seguridad**: Registrar datos sensibles puede exponer información crítica a cualquier persona que inspeccione la consola del navegador.
3. **Cleanliness**: Filling up the console with logs can obscure important warnings or errors that developers or tools need to see.
3. **Limpieza**: Llenar la consola con registros puede oscurecer advertencias o errores importantes que los desarrolladores o herramientas necesitan ver.
4. **Professionalism**: End users or clients checking the console and seeing a myriad of log statements might question the code's quality and polish.
4. **Profesionalismo**: Los usuarios finales o clientes que revisen la consola y vean una miríada de declaraciones de registros podrían cuestionar la calidad y el acabado del código.
Make sure you remove all `console.logs` before pushing the code to production.
Asegúrate de eliminar todos los `console.logs` antes de enviar el código a producción.
## Naming
## Nomenclatura
### Variable Naming
### Nombres de variables
Variable names ought to precisely depict the purpose or function of the variable.
Los nombres de variables deben describir con precisión el propósito o función de la variable.
#### The issue with generic names
#### El problema con los nombres genéricos
Generic names in programming are not ideal because they lack specificity, leading to ambiguity and reduced code readability. Such names fail to convey the variable or function's purpose, making it challenging for developers to understand the code's intent without deeper investigation. This can result in increased debugging time, higher susceptibility to errors, and difficulties in maintenance and collaboration. Meanwhile, descriptive naming makes the code self-explanatory and easier to navigate, enhancing code quality and developer productivity.
Los nombres genéricos en programación no son ideales porque carecen de especificidad, lo que lleva a la ambigüedad y reduce la legibilidad del código. Tales nombres no transmiten el propósito de la variable o función, lo que dificulta a los desarrolladores entender la intención del código sin una investigación más profunda. Esto puede resultar en un aumento del tiempo de depuración, una mayor susceptibilidad a errores y dificultades en el mantenimiento y colaboración. Mientras tanto, la nomenclatura descriptiva hace que el código sea autoexplicativo y más fácil de navegar, mejorando la calidad del código y la productividad del desarrollador.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
// ❌ Malo, usa un nombre genérico que no comunica claramente su
// propósito o contenido
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
// ✅ Bueno, usa un nombre descriptivo
const [email, setEmail] = useState('');
```
#### Some words to avoid in variable names
#### Algunas palabras a evitar en los nombres de variables
* dummy
* ficticio
### Event handlers
### Manejadores de eventos
Event handler names should start with `handle`, while `on` is a prefix used to name events in components props.
Los nombres de manejadores de eventos deben comenzar con `handle`, mientras que `on` es un prefijo usado para nombrar eventos en las props de los componentes.
```tsx
// ❌ Bad
// ❌ Malo
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
// ✅ Bueno
const handleEmailChange = (val: string) => {
// ...
};
```
## Optional Props
## Props opcionales
Avoid passing the default value for an optional prop.
Evita pasar el valor predeterminado para una prop opcional.
**EXAMPLE**
**EJEMPLO**
Take the`EmailField` component defined below:
Toma el componente `EmailField` definido a continuación:
```tsx
type EmailFieldProps = {
@@ -219,28 +219,28 @@ const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
);
```
**Usage**
**Uso**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
// ❌ Malo, pasar el mismo valor que el valor predeterminado no aporta nada
const Form = () => <EmailField value="[email protected]" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
// ✅ Bueno, asume el valor predeterminado
const Form = () => <EmailField value="[email protected]" />;
```
## Component as props
## Componente como props
Try as much as possible to pass uninstantiated components as props, so children can decide on their own of what props they need to pass.
Intenta tanto como sea posible pasar componentes no instanciados como propiedades, para que los hijos puedan decidir por sí mismos qué propiedades necesitan pasar.
The most common example for that is icon components:
El ejemplo más común de esto son los componentes de icono:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
// En MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
@@ -252,25 +252,25 @@ const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
};
```
For React to understand that the component is a component, you need to use PascalCase, to later instantiate it with `<MyIcon>`
Para que React entienda que el componente es un componente, necesitas usar PascalCase, para luego instanciarlo con `<MyIcon>`
## Prop Drilling: Keep It Minimal
## Prop Drilling: Mantenlo Minimalista
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through many component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
El prop drilling, en el contexto de React, se refiere a la práctica de pasar variables de estado y sus setters a través de muchas capas de componentes, incluso si los componentes intermedios no los usan. Aunque a veces es necesario, el exceso de prop drilling puede llevar a:
1. **Decreased Readability**: Tracing where a prop originates or where it's utilized can become convoluted in a deeply nested component structure.
1. **Readabilidad Reducida**: Rastrear de dónde proviene una propiedad o dónde se utiliza puede volverse complicado en una estructura de componentes muy anidada.
2. **Maintenance Challenges**: Changes in one component's prop structure might require adjustments in several components, even if they don't directly use the prop.
2. **Desafíos de Mantenimiento**: Los cambios en la estructura de propiedades de un componente podrían requerir ajustes en varios componentes, incluso si no utilizan directamente la propiedad.
3. **Reduced Component Reusability**: A component receiving a lot of props solely for passing them down becomes less general-purpose and harder to reuse in different contexts.
3. **Reducción de la Reusabilidad del Componente**: Un componente que recibe muchas propiedades solo para pasarlas se vuelve menos general y más difícil de reutilizar en diferentes contextos.
If you feel that you are using excessive prop drilling, see [state management best practices](#state-management).
Si sientes que estás usando en exceso el prop drilling, consulta [mejores prácticas de gestión de estado](#state-management).
## Imports
## Importar
When importing, opt for the designated aliases rather than specifying complete or relative paths.
Al importar, opta por los alias designados en lugar de especificar rutas completas o relativas.
**The Aliases**
**Alias del identificador**
```js
{
@@ -282,10 +282,10 @@ When importing, opt for the designated aliases rather than specifying complete o
}
```
**Usage**
**Uso**
```tsx
// ❌ Bad, specifies the entire relative path
// ❌ Malo, especifica toda la ruta relativa
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
@@ -295,14 +295,14 @@ import {
```
```tsx
// ✅ Good, utilises the designated aliases
// ✅ Bueno, utiliza los alias designados
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## Schema Validation
## Validación de Esquema
[Zod](https://github.com/colinhacks/zod) is the schema validator for untyped objects:
[Zod](https://github.com/colinhacks/zod) es el validador de esquemas para objetos no tipados:
```js
const validationSchema = z
@@ -310,16 +310,16 @@ const validationSchema = z
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
.email('El correo electrónico debe ser válido'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
.regex(PASSWORD_REGEX, 'La contraseña debe contener al menos 8 caracteres'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Breaking Changes
## Cambios Cruciales
Always perform thorough manual testing before proceeding to guarantee that modifications havent caused disruptions elsewhere, given that tests have not yet been extensively integrated.
Siempre ejecuta pruebas manuales exhaustivas antes de proceder para garantizar que las modificaciones no hayan causado interrupciones en otras partes, dado que las pruebas aún no se han integrado extensivamente.
@@ -1,11 +1,11 @@
---
title: Folder Architecture
info: A detailed look into our folder architecture
title: Arquitectura de Carpetas
info: Una mirada detallada a nuestra arquitectura de carpetas
---
In this guide, you will explore the details of the project directory structure and how it contributes to the organization and maintainability of Twenty.
En esta guía, explorarás los detalles de la estructura del directorio del proyecto y cómo contribuye a la organización y mantenibilidad de Twenty.
By following this folder architecture convention, it's easier to find the files related to specific features and ensure that the application is scalable and maintainable.
Siguiendo esta convención de arquitectura de carpetas, es más fácil encontrar los archivos relacionados con funciones específicas y asegurar que la aplicación sea escalable y mantenible.
```
front
@@ -22,14 +22,14 @@ front
└───...
```
## Pages
## Páginas
Includes the top-level components defined by the application routes. They import more low-level components from the modules folder (more details below).
Incluye los componentes de alto nivel definidos por las rutas de la aplicación. Importan más componentes de bajo nivel de la carpeta de módulos (más detalles abajo).
## Modules
## Módulos
Each module represents a feature or a group of feature, comprising its specific components, states, and operational logic.
They should all follow the structure below. You can nest modules within modules (referred to as submodules) and the same rules will apply.
Cada módulo representa una función o un grupo de funciones, comprendiendo sus componentes específicos, estados y lógica operativa.
Todos deberían seguir la estructura siguiente. Puedes anidar módulos dentro de módulos (denominados submódulos) y se aplicarán las mismas reglas.
```
module1
@@ -50,60 +50,60 @@ module1
└───utils
```
### Contexts
### Contextos
A context is a way to pass data through the component tree without having to pass props down manually at every level.
Un contexto es una manera de pasar datos a través del árbol de componentes sin tener que pasar propiedades manualmente en cada nivel.
See [React Context](https://react.dev/reference/react#context-hooks) for more details.
Ver [React Context](https://react.dev/reference/react#context-hooks) para más detalles.
### GraphQL
Includes fragments, queries, and mutations.
Incluye fragmentos, consultas y mutaciones.
See [GraphQL](https://graphql.org/learn/) for more details.
Ver [GraphQL](https://graphql.org/learn/) para más detalles.
* Fragments
* Fragmentos
A fragment is a reusable piece of a query, which you can use in different places. By using fragments, it's easier to avoid duplicating code.
Un fragmento es una parte reutilizable de una consulta, que puedes usar en diferentes lugares. Usando fragmentos, es más fácil evitar la duplicación de código.
See [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) for more details.
Ver [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) para más detalles.
* Queries
* Consultas
See [GraphQL Queries](https://graphql.org/learn/queries/) for more details.
Ver [GraphQL Queries](https://graphql.org/learn/queries/) para más detalles.
* Mutations
* Mutaciones
See [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) for more details.
Ver [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) para más detalles.
### Hooks
See [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) for more details.
Ver [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) para más detalles.
### States
### Estados
Contains the state management logic. [RecoilJS](https://recoiljs.org) handles this.
Contiene la lógica de gestión de estado. [RecoilJS](https://recoiljs.org) maneja esto.
* Selectors: See [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) for more details.
* Selectores: Ver [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) para más detalles.
React's built-in state management still handles state within a component.
La gestión de estado incorporada de React todavía maneja el estado dentro de un componente.
### Utils
### Utilidades
Should just contain reusable pure functions. Otherwise, create custom hooks in the `hooks` folder.
Debería contener solo funciones puras reutilizables. De lo contrario, crea hooks personalizados en la carpeta `hooks`.
## UI
## Interfaz de usuario
Contains all the reusable UI components used in the application.
Contiene todos los componentes de interfaz de usuario reutilizables utilizados en la aplicación.
This folder can contain sub-folders, like `data`, `display`, `feedback`, and `input` for specific types of components. Each component should be self-contained and reusable, so that you can use it in different parts of the application.
Esta carpeta puede contener subcarpetas, como `datos`, `visualización`, `retroalimentación` y `entrada` para tipos específicos de componentes. Cada componente debe ser autónomo y reutilizable, para que puedas usarlo en diferentes partes de la aplicación.
By separating the UI components from the other components in the `modules` folder, it's easier to maintain a consistent design and to make changes to the UI without affecting other parts (business logic) of the codebase.
Al separar los componentes de la interfaz de usuario de otros componentes en la carpeta `modules`, es más fácil mantener un diseño consistente y realizar cambios en la interfaz de usuario sin afectar otras partes (lógica de negocio) del código base.
## Interface and dependencies
## Interfaz y dependencias
You can import other module code from any module except for the `ui` folder. This will keep its code easy to test.
Puedes importar otro código de módulo desde cualquier módulo excepto desde la carpeta `ui`. Esto mantendrá su código fácil de probar.
### Internal
### Interno
Each part (hooks, states, ...) of a module can have an `internal` folder, which contains parts that are just used within the module.
Cada parte (hooks, estados, ...) de un módulo puede tener una carpeta `internal`, que contiene partes que se usan solo dentro del módulo.
@@ -1,22 +1,22 @@
---
title: Frontend Commands
title: Comandos del Frontend
---
## Useful commands
## Comandos útiles
### Starting the app
### Iniciando la aplicación
```bash
npx nx start twenty-front
```
### Regenerate graphql schema based on API graphql schema
### Regenerar el esquema de graphql basado en el esquema API graphql
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
OR
O
```bash
npx nx run twenty-front:graphql:generate
@@ -25,30 +25,30 @@ npx nx run twenty-front:graphql:generate
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
npx nx run twenty-front:lint # pasar --fix para corregir errores de lint
```
## Translations
## Traducciones
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Test
### Prueba
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:test # ejecutar pruebas con Jest
npx nx run twenty-front:storybook:serve:dev # ejecutar Storybook
npx nx run twenty-front:storybook:test # ejecutar pruebas # (requiere que yarn storybook:serve:dev esté en ejecución)
npx nx run twenty-front:storybook:coverage # (requiere que yarn storybook:serve:dev esté en ejecución)
```
## Tech Stack
## Stack Tecnológico
The project has a clean and simple stack, with minimal boilerplate code.
El proyecto tiene un stack limpio y sencillo, con un código boilerplate mínimo.
**App**
**Aplicación**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
@@ -56,35 +56,35 @@ The project has a clean and simple stack, with minimal boilerplate code.
* [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
* [TypeScript](https://www.typescriptlang.org/)
**Testing**
**Pruebas**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Tooling**
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [ESLint](https://eslint.org/)
## Architecture
## Arquitectura
### Routing
### Enrutamiento
[React Router](https://reactrouter.com/) handles the routing.
[React Router](https://reactrouter.com/) maneja el enrutamiento.
To avoid unnecessary [re-renders](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) all the routing logic is in a `useEffect` in `PageChangeEffect`.
### State Management
### Gestión del Estado
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) handles state management.
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) maneja la gestión del estado.
See [best practices](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) for more information on state management.
Ver [mejores prácticas](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) para más información sobre la gestión del estado.
## Testing
## Pruebas
[Jest](https://jestjs.io/) serves as the tool for unit testing while [Storybook](https://storybook.js.org/) is for component testing.
[Jest](https://jestjs.io/) sirve como la herramienta para pruebas unitarias mientras [Storybook](https://storybook.js.org/) es para pruebas de componentes.
Jest is mainly for testing utility functions, and not components themselves.
Jest es principalmente para probar funciones utilitarias, y no los componentes en sí mismos.
Storybook is for testing the behavior of isolated components, as well as displaying the design system.
Storybook es para probar el comportamiento de componentes aislados, así como mostrar el sistema de diseño.
@@ -1,42 +1,42 @@
---
title: Hotkeys
title: Atajos de teclado
---
## Introduction
## Introducción
When you need to listen to a hotkey, you would normally use the `onKeyDown` event listener.
Cuando necesitas escuchar una tecla de acceso rápido, normalmente usarías el evento `onKeyDown`.
In `twenty-front` however, you might have conflicts between same hotkeys that are used in different components, mounted at the same time.
En `twenty-front`, sin embargo, podrías tener conflictos entre los mismos atajos de teclado utilizados en diferentes componentes, montados al mismo tiempo.
For example, if you have a page that listens for the Enter key, and a modal that listens for the Enter key, with a Select component inside that modal that listens for the Enter key, you might have a conflict when all are mounted at the same time.
Por ejemplo, si tienes una página que escucha la tecla Enter y un modal que escucha la tecla Enter, con un componente Select dentro de ese modal que también escucha la tecla Enter, podrías tener un conflicto cuando todos están montados al mismo tiempo.
## The `useScopedHotkeys` hook
## El gancho `useScopedHotkeys`
To handle this problem, we have a custom hook that makes it possible to listen to hotkeys without any conflict.
Para manejar este problema, tenemos un gancho personalizado que hace posible escuchar atajos de teclado sin ningún conflicto.
You place it in a component, and it will listen to the hotkeys only when the component is mounted AND when the specified **hotkey scope** is active.
Lo colocas en un componente, y escuchará los atajos de teclado solo cuando el componente está montado Y cuando el **ámbito del atajo de teclado** especificado está activo.
## How to listen for hotkeys in practice?
## ¿Cómo escuchar atajos de teclado en la práctica?
There are two steps involved in setting up hotkey listening :
Hay dos pasos involucrados en configurar la escucha de atajos de teclado:
1. Set the [hotkey scope](#what-is-a-hotkey-scope-) that will listen to hotkeys
2. Use the `useScopedHotkeys` hook to listen to hotkeys
1. Establece el [ámbito del atajo de teclado](#qué-es-un-ambito-de-atajo-de-teclado-) que escuchará los atajos de teclado
2. Usa el gancho `useScopedHotkeys` para escuchar atajos de teclado
Setting up hotkey scopes is required even in simple pages, because other UI elements like left menu or command menu might also listen to hotkeys.
Configurar los ámbitos de atajos de teclado es necesario incluso en páginas simples, porque otros elementos de la interfaz de usuario como el menú lateral o el menú de comandos también podrían escuchar atajos de teclado.
## Use cases for hotkeys
## Casos de uso para atajos de teclado
In general, you'll have two use cases that require hotkeys :
En general, tendrás dos casos de uso que requieren atajos de teclado:
1. In a page or a component mounted in a page
2. In a modal-type component that takes the focus due to a user action
1. En una página o un componente montado en una página
2. En un componente tipo modal que toma el enfoque debido a la acción del usuario
The second use case can happen recursively : a dropdown in a modal for example.
El segundo caso de uso puede ocurrir recursivamente: un desplegable en un modal, por ejemplo.
### Listening to hotkeys in a page
### Escuchando atajos de teclado en una página
Example :
Ejemplo:
```tsx
const PageListeningEnter = () => {
@@ -71,11 +71,11 @@ const PageListeningEnter = () => {
};
```
### Listening to hotkeys in a modal-type component
### Escuchando atajos de teclado en un componente tipo modal
For this example we'll use a modal component that listens for the Escape key to tell its parent to close it.
Para este ejemplo utilizaremos un componente modal que escucha la tecla Escape para indicar a su padre que lo cierre.
Here the user interaction is changing the scope.
Aquí la interacción del usuario está cambiando el ámbito.
```tsx
const ExamplePageWithModal = () => {
@@ -108,7 +108,7 @@ const ExamplePageWithModal = () => {
};
```
Then in the modal component :
Luego, en el componente modal:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
@@ -131,15 +131,15 @@ It's important to use this pattern when you're not sure that just using a useEff
Those conflicts can be hard to debug, and it might happen more often than not with useEffects.
## What is a hotkey scope?
## ¿Qué es un ámbito de atajo de teclado?
A hotkey scope is a string that represents a context in which the hotkeys are active. It is generally encoded as an enum.
Un ámbito de atajo de teclado es una cadena que representa un contexto en el que los atajos de teclado están activos. Por lo general, se codifica como un enum.
When you change the hotkey scope, the hotkeys that are listening to this scope will be enabled and the hotkeys that are listening to other scopes will be disabled.
Cuando cambias el ámbito del atajo de teclado, se habilitarán los atajos que están escuchando este ámbito y se deshabilitarán los atajos que escuchan otros ámbitos.
You can set only one scope at a time.
Solo puedes establecer un ámbito a la vez.
As an example, the hotkey scopes for each page are defined in the `PageHotkeyScope` enum:
Como ejemplo, los ámbitos de atajos de teclado para cada página se definen en el enum `PageHotkeyScope`:
```tsx
export enum PageHotkeyScope {
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
}
```
Internally, the currently selected scope is stored in a Recoil state that is shared across the application :
Internamente, el ámbito seleccionado se almacena en un estado de Recoil que se comparte en toda la aplicación:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
});
```
But this Recoil state should never be handled manually ! We'll see how to use it in the next section.
¡Pero este estado de Recoil nunca debe manejarse manualmente! Veremos cómo usarlo en la siguiente sección.
## How is it working internally?
## ¿Cómo funciona internamente?
We made a thin wrapper on top of [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) that makes it more performant and avoids unnecessary re-renders.
Hicimos un contenedor delgado sobre [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que lo hace más eficiente y evita renders innecesarios.
We also create a Recoil state to handle the hotkey scope state and make it available everywhere in the application.
También creamos un estado de Recoil para manejar el estado del ámbito de atajos de teclado y hacerlo disponible en toda la aplicación.
@@ -1,8 +1,8 @@
---
title: Storybook
description: Browse Twenty's UI component library
description: Navegar por la biblioteca de componentes de interfaz de Twenty
---
View our complete component library and documentation in Storybook.
Ver nuestra biblioteca completa de componentes y documentación en Storybook.
[Open Storybook →](https://storybook.twenty.com)
[Abrir Storybook →](https://storybook.twenty.com)
@@ -1,24 +1,24 @@
---
title: Style Guide
title: Guía de Estilo
---
This document includes the rules to follow when writing code.
Este documento incluye las reglas a seguir al escribir código.
The goal here is to have a consistent codebase, which is easy to read and easy to maintain.
El objetivo aquí es tener una base de código coherente, que sea fácil de leer y de mantener.
For this, it's better to be a bit more verbose than to be too concise.
Para esto, es mejor ser un poco más detallado que ser demasiado conciso.
Always keep in mind that people read code more often than they write it, specially on an open source project, where anyone can contribute.
Ten siempre en cuenta que la gente lee código más a menudo de lo que lo escribe, especialmente en un proyecto de código abierto, donde cualquiera puede contribuir.
There are a lot of rules that are not defined here, but that are automatically checked by linters.
Hay muchas reglas que no están definidas aquí, pero que son verificadas automáticamente por linters.
## React
### Use functional components
### Usar componentes funcionales
Always use TSX functional components.
Siempre usa componentes funcionales TSX.
Do not use default `import` with `const`, because it's harder to read and harder to import with code completion.
No uses `import` por defecto con `const`, porque es más difícil de leer y más difícil de importar con el autocompletado de código.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
@@ -34,11 +34,11 @@ export function MyComponent() {
};
```
### Props
### "Props"
Create the type of the props and call it `(ComponentName)Props` if there's no need to export it.
Crea el tipo de las props y llámalo `(NombreDelComponente)Props` si no hay necesidad de exportarlo.
Use props destructuring.
Usa la desestructuración de props.
```tsx
// ❌ Bad, no type
@@ -52,7 +52,7 @@ type MyComponentProps = {
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### Refrain from using `React.FC` or `React.FunctionComponent` to define prop types
#### Evita usar `React.FC` o `React.FunctionComponent` para definir tipos de props
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
@@ -67,10 +67,10 @@ const EmailField: React.FC<{
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
/* ✅ - Bueno, se define explícitamente un tipo (OwnProps) separado para las
* props del componente
* - Este método no incluye automáticamente la prop children. Si
* quieres incluirla, debes especificarla en OwnProps.
*/
type EmailFieldProps = {
value: string;
@@ -81,9 +81,9 @@ const EmailField = ({ value }: EmailFieldProps) => (
);
```
#### No Single Variable Prop Spreading in JSX Elements
#### Sin Propagación de una sola variable de Props en Elementos JSX
Avoid using single variable prop spreading in JSX elements, like `{...props}`. This practice often results in code that is less readable and harder to maintain because it's unclear which props the component is receiving.
Evita usar la propagación de una sola variable de props en elementos JSX, como `{...props}`. Esta práctica a menudo resulta en un código que es menos legible y más difícil de mantener porque no está claro qué props está recibiendo el componente.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
@@ -94,23 +94,23 @@ const MyComponent = (props: OwnProps) => {
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
/* ✅ - Bueno, enumera explícitamente todas las props
* - Mejora la legibilidad y la mantenibilidad
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
Rationale:
Razonamiento:
* At a glance, it's clearer which props the code passes down, making it easier to understand and maintain.
* It helps to prevent tight coupling between components via their props.
* Linting tools make it easier to identify misspelled or unused props when you list props explicitly.
* A simple vista, es más claro qué props se está pasando, lo que hace que sea más fácil de entender y mantener.
* Ayuda a prevenir el acoplamiento estricto entre componentes mediante sus props.
* Las herramientas de linting facilitan la identificación de props mal escritas o sin uso al listar props explícitamente.
## JavaScript
### Use nullish-coalescing operator `??`
### Usar el operador de fusión nula `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
@@ -120,21 +120,21 @@ const value = process.env.MY_VALUE || 'default';
const value = process.env.MY_VALUE ?? 'default';
```
### Use optional chaining `?.`
### Usar encadenamiento opcional `?.`
```tsx
// ❌ Bad
// ❌ Malo
onClick && onClick();
// ✅ Good
// ✅ Bueno
onClick?.();
```
## TypeScript
### Use `type` instead of `interface`
### Usar `type` en lugar de `interface`
Always use `type` instead of `interface`, because they almost always overlap, and `type` is more flexible.
Siempre usa `type` en lugar de `interface`, porque casi siempre se superponen y `type` es más flexible.
```tsx
// ❌ Bad
@@ -148,11 +148,11 @@ type MyType = {
};
```
### Use string literals instead of enums
### Usar literales de cadena en lugar de enums
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, specially with code completion.
[Los literales de cadena](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) son la manera preferida para manejar valores tipo enum en TypeScript. Son más fáciles de extender con Pick y Omit, y ofrecen una mejor experiencia de desarrollo, especialmente con la autocompletación de código.
You can see why TypeScript recommends avoiding enums [here](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
Puedes ver por qué TypeScript recomienda evitar enums [aquí](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
```tsx
// ❌ Bad, utilizes an enum
@@ -171,13 +171,13 @@ let color = Color.Red;
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL and internal libraries
#### GraphQL y bibliotecas internas
You should use enums that GraphQL codegen generates.
Deberías usar enums que genera el codegen de GraphQL.
It's also better to use an enum when using an internal library, so the internal library doesn't have to expose a string literal type that is not related to the internal API.
También es mejor usar un enum al usar una biblioteca interna, para que la biblioteca interna no tenga que exponer un tipo de literal de cadena que no está relacionado con la API interna.
Example:
Ejemplo:
```TSX
const {
@@ -190,11 +190,11 @@ setHotkeyScopeAndMemorizePreviousScope(
);
```
## Styling
## Estilo
### Use StyledComponents
### Usar StyledComponents
Style the components with [styled-components](https://emotion.sh/docs/styled).
Estiliza los componentes con [styled-components](https://emotion.sh/docs/styled).
```tsx
// ❌ Bad
@@ -208,7 +208,7 @@ const StyledTitle = styled.div`
`;
```
Prefix styled components with "Styled" to differentiate them from "real" components.
Prefija los componentes estilizados con "Styled" para diferenciarlos de los componentes "reales".
```tsx
// ❌ Bad
@@ -224,17 +224,17 @@ const StyledTitle = styled.div`
`;
```
### Theming
### Tematización
Utilizing the theme for the majority of component styling is the preferred approach.
Utilizar el tema para la mayor parte de la estilización de los componentes es el enfoque preferido.
#### Units of measurement
#### Unidades de medida
Avoid using `px` or `rem` values directly within the styled components. The necessary values are generally already defined in the theme, so its recommended to make use of the theme for these purposes.
Evita usar valores `px` o `rem` directamente dentro de los componentes estilizados. Los valores necesarios suelen estar ya definidos en el tema, por lo que se recomienda usar el tema para estos fines.
#### Colors
#### Colores
Refrain from introducing new colors; instead, use the existing palette from the theme. Should there be a situation where the palette does not align, please leave a comment so that the team can rectify it.
Abstente de introducir nuevos colores; en su lugar, utiliza la paleta existente del tema. Si hay una situación en la que la paleta no se ajusta, deja un comentario para que el equipo pueda corregirlo.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
@@ -258,9 +258,9 @@ const StyledButton = styled.button`
`;
```
## Enforcing No-Type Imports
## Aplicando No-Type Imports
Avoid type imports. To enforce this standard, an ESLint rule checks for and reports any type imports. This helps maintain consistency and readability in the TypeScript code.
Evita las importaciones de tipo. Para reforzar este estándar, una regla de ESLint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
```tsx
// ❌ Bad
@@ -273,18 +273,18 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
```
### Why No-Type Imports
### Por qué No-Type Imports
* **Consistency**: By avoiding type imports and using a single approach for both type and value imports, the codebase remains consistent in its module import style.
* **Consistencia**: Al evitar las importaciones de tipo y usar un solo enfoque tanto para las importaciones de tipo como de valor, la base de código se mantiene consistente en su estilo de importación de módulos.
* **Readability**: No-type imports improve code readability by making it clear when you're importing values or types. This reduces ambiguity and makes it easier to understand the purpose of imported symbols.
* **Legibilidad**: Las no-importaciones de tipo mejoran la legibilidad del código al dejar claro cuándo se están importando valores o tipos. Esto reduce la ambigüedad y hace más fácil entender el propósito de los símbolos importados.
* **Maintainability**: It enhances codebase maintainability because developers can identify and locate type-only imports when reviewing or modifying code.
* **Mantenibilidad**: Mejora la mantenibilidad de la base de código porque los desarrolladores pueden identificar y localizar importaciones solo de tipo al revisar o modificar el código.
### ESLint Rule
### Regla de ESLint
An ESLint rule, `@typescript-eslint/consistent-type-imports`, enforces the no-type import standard. This rule will generate errors or warnings for any type import violations.
Una regla de ESLint, `@typescript-eslint/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla genera errores o advertencias sobre cualquier violación de importación de tipo.
Please note that this rule specifically addresses rare edge cases where unintentional type imports occur. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
Por favor, ten en cuenta que esta regla específicamente aborda extraños casos límite donde ocurren importaciones de tipo no intencionadas. TypeScript en sí mismo desaconseja esta práctica, como se menciona en las [notas de lanzamiento de TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). En la mayoría de situaciones, no deberías necesitar usar importaciones solo de tipo.
To ensure your code complies with this rule, make sure to run ESLint as part of your development workflow.
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar ESLint como parte de tu flujo de trabajo de desarrollo.
@@ -1,59 +1,59 @@
---
title: Work with Figma
info: Learn how you can collaborate with Twenty's Figma
title: Trabajar con Figma
info: Aprende cómo puedes colaborar con Twenty en Figma
---
Figma is a collaborative interface design tool that aids in bridging the communication barrier between designers and developers.
This guide explains how you can collaborate with Figma.
Figma es una herramienta de diseño de interfaces colaborativa que ayuda a superar la barrera de comunicación entre diseñadores y desarrolladores.
Esta guía explica cómo puedes colaborar con Figma.
## Access
## Acceso
1. **Access the shared link:** You can access the project's Figma file [here](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Sign in:** If you're not already signed in, Figma will prompt you to do so.
Key features are only available to logged-in users, such as the developer mode and the ability to select a dedicated frame.
1. **Accede al enlace compartido:** Puedes acceder al archivo de Figma del proyecto [aquí](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Inicia sesión:** Si aún no has iniciado sesión, Figma te pedirá que lo hagas.
Las características clave solo están disponibles para usuarios registrados, como el modo desarrollador y la capacidad de seleccionar un marco dedicado.
<Warning>
You will not be able to collaborate effectively without an account.
No podrás colaborar efectivamente sin una cuenta.
</Warning>
## Figma structure
## Estructura de Figma
On the left sidebar, you can access the different pages of Twenty's Figma. This is how they're organized:
En la barra lateral izquierda, puedes acceder a las diferentes páginas del Figma de Twenty. Así es como están organizadas:
* **Components page:** This is the first page. The designer uses it to create and organize the reusable design elements used throughout the design file. For example, buttons, icons, symbols, or any other reusable components. It serves to maintain consistency across the design.
* **Main page:** The second page is the main page, which shows the complete user interface of the project. You can press ***Play*** to use the full app prototype.
* **Features pages:** The other pages are typically dedicated to features in progress. They contain the design of specific features or modules of the application or website. They are typically still in progress.
* **Página de componentes:** Esta es la primera página. El diseñador la utiliza para crear y organizar los elementos de diseño reutilizables que se usan en todo el archivo de diseño. Por ejemplo, botones, íconos, símbolos u otros componentes reutilizables. Sirve para mantener la consistencia a lo largo del diseño.
* **Página principal:** La segunda página es la página principal, que muestra la interfaz de usuario completa del proyecto. Puedes presionar ***Play*** para usar el prototipo completo de la aplicación.
* **Páginas de características:** Las otras páginas están típicamente dedicadas a características en progreso. Contienen el diseño de características específicas o módulos de la aplicación o sitio web. Normalmente, aún están en desarrollo.
## Useful Tips
## Consejos útiles
With read-only access, you can't edit the design, but you can access all features that will be useful to convert the designs into code.
Con acceso de solo lectura, no puedes editar el diseño, pero puedes acceder a todas las características que serán útiles para convertir los diseños en código.
### Use the Dev mode
### Usa el modo Dev
Figma's Dev Mode enhances developers' productivity by providing easy design navigation, effective asset management, efficient communication tools, toolbox integrations, quick code snippets, and key layer information, bridging the gap between design and development. You can learn more about Dev Mode [here](https://www.figma.com/dev-mode/).
El Modo Dev de Figma mejora la productividad de los desarrolladores al proporcionar una navegación fácil por el diseño, una gestión eficaz de assets, herramientas de comunicación eficientes, integraciones de caja de herramientas, fragmentos de código rápidos e información clave de capas, superando la brecha entre diseño y desarrollo. Puedes aprender más sobre el Modo Dev [aquí](https://www.figma.com/dev-mode/).
Switch to the "Developer" mode in the right part of the toolbar to see design specs, copy CSS, and access assets.
Cambia al modo "Desarrollador" en la parte derecha de la barra de herramientas para ver las especificaciones de diseño, copiar CSS y acceder a los assets.
### Use the Prototype
### Usar el prototipo
Click on any element on the canvas and press the “Play” button at the top right edge of the interface to access the prototype view. Prototype mode allows you to interact with the design as if it were the final product. It demonstrates the flow between screens and how interface elements like buttons, links, or menus behave when interacted with.
Haz clic en cualquier elemento del lienzo y presiona el botón "Play" en el extremo superior derecho de la interfaz para acceder a la vista del prototipo. El modo de prototipo te permite interactuar con el diseño como si fuera el producto final. Demuestra el flujo entre pantallas y cómo los elementos de la interfaz, como botones, enlaces o menús, se comportan al interactuar con ellos.
1. **Understanding transitions and animations:** In the Prototype mode, you can view any transitions or animations added by a designer between screens or UI elements, providing clear visual instructions to developers on the intended behavior and style.
2. **Implementation clarification:** A prototype can also help reduce ambiguities. Developers can interact with it to gain a better understanding of the functionality or appearance of particular elements.
1. **Entendiendo las transiciones y animaciones:** En el modo Prototipo, puedes ver cualquier transición o animación añadida por un diseñador entre pantallas o elementos de UI, proporcionando instrucciones visuales claras a los desarrolladores sobre la conducta y el estilo intencionados.
2. **Aclaración de implementación:** Un prototipo también puede ayudar a reducir las ambigüedades. Los desarrolladores pueden interactuar con él para obtener una mejor comprensión de la funcionalidad o apariencia de elementos particulares.
For more comprehensive details and guidance on learning the Figma platform, you can visit the official [Figma Documentation](https://help.figma.com/hc/en-us).
Para más detalles y orientación sobre el aprendizaje de la plataforma Figma, puedes visitar la [Documentación Oficial de Figma](https://help.figma.com/hc/en-us).
### Measure distances
### Medir distancias
Select an element, hold `Option` key (Mac) or `Alt` key (Windows), then hover over another element to see the distance between them.
Selecciona un elemento, mantén presionada la tecla `Option` (Mac) o `Alt` (Windows), luego pasa el cursor sobre otro elemento para ver la distancia entre ellos.
### Figma extension for VSCode (Recommended)
### Extensión Figma para VSCode (Recomendado)
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
lets you navigate and inspect design files, collaborate with designers, track changes, and speed up implementation - all without leaving your text editor.
It's part of our recommended extensions.
[Figma para VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
te permite navegar e inspeccionar archivos de diseño, colaborar con diseñadores, rastrear cambios y acelerar la implementación, todo sin salir de tu editor de texto.
Forma parte de nuestras extensiones recomendadas.
## Collaboration
## Colaboración
1. **Using Comments:** You are welcome to use the comment feature by clicking on the bubble icon in the left part of the toolbar.
2. **Cursor chat:** A nice feature of Figma is the Cursor chat. Just press `;` on Mac and `/` on Windows to send a message if you see someone else using Figma as the same time as you.
1. **Uso de Comentarios:** Puedes utilizar la función de comentarios haciendo clic en el ícono de burbuja en la parte izquierda de la barra de herramientas.
2. **Chat de Cursor:** Una característica agradable de Figma es el Chat de Cursor. Simplemente presiona `;` en Mac y `/` en Windows para enviar un mensaje si ves a alguien más usando Figma al mismo tiempo que tú.
@@ -1,13 +1,13 @@
---
title: Local Setup
description: The guide for contributors (or curious developers) who want to run Twenty locally.
title: Configuración Local
description: La guía para los colaboradores (o desarrolladores curiosos) que quieren ejecutar Twenty localmente.
---
## Prerequisites
## Prerrequisitos
<Tabs>
<Tab title="Linux and MacOS">
Before you can install and use Twenty, make sure you install the following on your computer:
<Tab title="Linux y MacOS">
Antes de que puedas instalar y usar Twenty, asegúrate de instalar lo siguiente en tu computadora:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
@@ -15,25 +15,25 @@ description: The guide for contributors (or curious developers) who want to run
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm` won't work, you should use `yarn` instead. Yarn is now shipped with Node.js, so you don't need to install it separately.
You only have to run `corepack enable` to enable Yarn if you haven't done it yet.
`npm` no funcionará, deberías usar `yarn` en su lugar. Yarn ahora se envía con Node.js, por lo que no necesitas instalarlo por separado.
Solo tienes que ejecutar `corepack enable` para habilitar Yarn si aún no lo has hecho.
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. Install WSL
Open PowerShell as Administrator and run:
1. Instalar WSL
Abre PowerShell como Administrador y ejecuta:
```powershell
wsl --install
```
You should now see a prompt to restart your computer. If not, restart it manually.
Ahora deberías ver un aviso para reiniciar tu computadora. Si no, reiníciala manualmente.
Upon restart, a powershell window will open and install Ubuntu. This may take up some time.
You'll see a prompt to create a username and password for your Ubuntu installation.
Al reiniciar, se abrirá una ventana de PowerShell e instalará Ubuntu. Esto puede tomar algo de tiempo.
Verás un aviso para crear un nombre de usuario y contraseña para tu instalación de Ubuntu.
2. Install and configure git
2. Instalar y configurar git
```bash
sudo apt-get install git
@@ -43,10 +43,10 @@ description: The guide for contributors (or curious developers) who want to run
git config --global user.email "[email protected]"
```
3. Install nvm, node.js and yarn
3. Instalar nvm, node.js y yarn
<Warning>
Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contributors use the same version.
Usa `nvm` para instalar la versión correcta de `node`. El `.nvmrc` asegura que todos los colaboradores usen la misma versión.
</Warning>
```bash
@@ -55,7 +55,7 @@ description: The guide for contributors (or curious developers) who want to run
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
Close and reopen your terminal to use nvm. Then run the following commands.
Cierra y vuelve a abrir tu terminal para usar nvm. Luego ejecuta los siguientes comandos.
```bash
@@ -70,13 +70,13 @@ description: The guide for contributors (or curious developers) who want to run
---
## Step 1: Git Clone
## Paso 1: Clonar con Git
In your terminal, run the following command.
En tu terminal, ejecuta el siguiente comando.
<Tabs>
<Tab title="SSH (Recommended)">
If you haven't already set up SSH keys, you can learn how to do so [here](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
<Tab title="SSH (Recomendado)">
Si aún no has configurado claves SSH, puedes aprender cómo hacerlo [aquí](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
```bash
git clone [email protected]:twentyhq/twenty.git
@@ -90,36 +90,36 @@ In your terminal, run the following command.
</Tab>
</Tabs>
## Step 2: Position yourself at the root
## Paso 2: Ubícate en la raíz
```bash
cd twenty
```
You should run all commands in the following steps from the root of the project.
Debes ejecutar todos los comandos de los siguientes pasos desde la raíz del proyecto.
## Step 3: Set up a PostgreSQL Database
## Paso 3: Configurar una Base de Datos PostgreSQL
<Tabs>
<Tab title="Linux">
**Option 1 (preferred):** To provision your database locally:
Use the following link to install Postgresql on your Linux machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
**Opción 1 (preferido):** Para aprovisionar tu base de datos localmente:
Usa el siguiente enlace para instalar PostgreSQL en tu máquina Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Note: You might need to add `sudo -u postgres` to the command before `psql` to avoid permission errors.
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
**Option 2:** If you have docker installed:
**Opción 2:** Si tienes Docker instalado:
```bash
make postgres-on-docker
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
**Option 1 (preferred):** To provision your database locally with `brew`:
**Opción 1 (preferido):** Para aprovisionar tu base de datos localmente con `brew`:
```bash
brew install postgresql@16
@@ -128,16 +128,16 @@ You should run all commands in the following steps from the root of the project.
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
You can verify if the PostgreSQL server is running by executing:
Puedes verificar si el servidor PostgreSQL está corriendo ejecutando:
```bash
brew services list
```
The installer might not create the `postgres` user by default when installing
via Homebrew on MacOS. Instead, it creates a PostgreSQL role that matches your macOS
username (e.g., "john").
To check and create the `postgres` user if necessary, follow these steps:
El instalador puede que no cree el usuario `postgres` por defecto al instalar
vía Homebrew en macOS. En cambio, crea un rol de PostgreSQL que coincide con tu
nombre de usuario de macOS (por ejemplo, "john").
Para comprobar y crear el usuario `postgres` si es necesario, sigue estos pasos:
```bash
# Connect to PostgreSQL
@@ -146,14 +146,14 @@ You should run all commands in the following steps from the root of the project.
psql -U $(whoami) -d postgres
```
Once at the psql prompt (postgres=#), run:
Una vez en el comando psql (postgres=#), ejecuta:
```bash
# List existing PostgreSQL roles
\du
```
You'll see output similar to:
Verás una salida similar a:
```bash
Role name | Attributes | Member of
@@ -161,98 +161,105 @@ You should run all commands in the following steps from the root of the project.
john | Superuser | {}
```
If you do not see a `postgres` role listed, proceed to the next step.
Create the `postgres` role manually:
Si no ves un rol `postgres` listado, procede al siguiente paso.
Crea el rol `postgres` manualmente:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
This creates a superuser role named `postgres` with login access.
**Option 2:** If you have docker installed:
Esto crea un rol de superusuario llamado `postgres` con acceso de inicio de sesión.
```bash
make postgres-on-docker
Nombre del rol | Atributos | Miembro de
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
All the following steps are to be run in the WSL terminal (within your virtual machine)
Todos los siguientes pasos deben ejecutarse en la terminal de WSL (dentro de tu máquina virtual)
**Option 1:** To provision your Postgresql locally:
Use the following link to install Postgresql on your Linux virtual machine: [Postgresql Installation](https://www.postgresql.org/download/linux/)
**Opción 1:** Para aprovisionar tu PostgreSQL localmente:
Usa el siguiente enlace para instalar PostgreSQL en tu máquina virtual Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Note: You might need to add `sudo -u postgres` to the command before `psql` to avoid permission errors.
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
**Option 2:** If you have docker installed:
Running Docker on WSL adds an extra layer of complexity.
Only use this option if you are comfortable with the extra steps involved, including turning on [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
**Opción 2:** Si tienes Docker instalado:
Ejecutar Docker en WSL agrega una capa extra de complejidad.
Solo usa esta opción si estás cómodo con los pasos extras involucrados, incluyendo activar [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
```bash
make postgres-on-docker
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
You can now access the database at [localhost:5432](localhost:5432), with user `postgres` and password `postgres` .
Ahora puedes acceder a la base de datos en [localhost:5432](localhost:5432), con usuario `postgres` y contraseña `postgres`.
## Step 4: Set up a Redis Database (cache)
## Paso 4: Configurar una Base de Datos Redis (cache)
Twenty requires a redis cache to provide the best performance
Twenty requiere un caché de redis para proporcionar el mejor rendimiento
<Tabs>
<Tab title="Linux">
**Option 1:** To provision your Redis locally:
Use the following link to install Redis on your Linux machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opción 1:** Para aprovisionar tu Redis localmente:
Usa el siguiente enlace para instalar Redis en tu máquina Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Option 2:** If you have docker installed:
**Opción 2:** Si tienes Docker instalado:
```bash
make redis-on-docker
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**Option 1 (preferred):** To provision your Redis locally with `brew`:
**Opción 1 (preferido):** Para aprovisionar tu Redis localmente con `brew`:
```bash
brew install redis
```
Start your redis server:
Inicia tu servidor redis:
`brew services start redis`
**Option 2:** If you have docker installed:
**Opción 2:** Si tienes Docker instalado:
```bash
make redis-on-docker
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
**Option 1:** To provision your Redis locally:
Use the following link to install Redis on your Linux virtual machine: [Redis Installation](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opción 1:** Para aprovisionar tu Redis localmente:
Usa el siguiente enlace para instalar Redis en tu máquina virtual Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Option 2:** If you have docker installed:
**Opción 2:** Si tienes Docker instalado:
```bash
make redis-on-docker
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
If you need a Client GUI, we recommend [redis insight](https://redis.io/insight/) (free version available)
Si necesitas una interfaz gráfica de cliente, recomendamos [Redis Insight](https://redis.io/insight/) (versión gratuita disponible)
## Step 5: Setup environment variables
## Paso 5: Configurar las variables de entorno
Use environment variables or `.env` files to configure your project. More info [here](/l/es/developers/self-host/capabilities/setup)
Usa variables de entorno o archivos `.env` para configurar tu proyecto. Más información [aquí](/l/es/developers/self-host/capabilities/setup)
Copy the `.env.example` files in `/front` and `/server`:
Copia los archivos `.env.example` en `/front` y `/server`:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
@@ -260,29 +267,29 @@ cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**Multi-Workspace Mode:** By default, Twenty runs in single-workspace mode where only one workspace can be created. To enable multi-workspace support (useful for testing subdomain-based features), set `IS_MULTIWORKSPACE_ENABLED=true` in your server `.env` file. See [Multi-Workspace Mode](/l/es/developers/self-host/capabilities/setup#multi-workspace-mode) for details.
**Modo de múltiples espacios de trabajo:** De forma predeterminada, Twenty se ejecuta en modo de un solo espacio de trabajo, donde solo se puede crear un espacio de trabajo. Para habilitar la compatibilidad con múltiples espacios de trabajo (útil para probar funciones basadas en subdominios), establece `IS_MULTIWORKSPACE_ENABLED=true` en el archivo `.env` de tu servidor. Consulta [Modo de múltiples espacios de trabajo](/l/es/developers/self-host/capabilities/setup#multi-workspace-mode) para más detalles.
</Info>
## Step 6: Installing dependencies
## Paso 6: Instalación de dependencias
To build Twenty server and seed some data into your database, run the following command:
Para compilar el servidor de Twenty e ingresar algunos datos en tu base de datos, ejecuta el siguiente comando:
```bash
yarn
```
Note that `npm` or `pnpm` won't work
Ten en cuenta que `npm` o `pnpm` no funcionarán
## Step 7: Running the project
## Paso 7: Ejecutar el proyecto
<Tabs>
<Tab title="Linux">
Depending on your Linux distribution, Redis server might be started automatically.
If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro.
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
Si no, revisa la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para tu distribución.
</Tab>
<Tab title="Mac OS">
Redis should already be running. If not, run:
Redis ya debería estar funcionando. Si no, ejecuta:
```bash
brew services start redis
@@ -290,18 +297,18 @@ Note that `npm` or `pnpm` won't work
</Tab>
<Tab title="Windows (WSL)">
Depending on your Linux distribution, Redis server might be started automatically.
If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro.
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
Si no es así, consulte la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para su distribución.
</Tab>
</Tabs>
Set up your database with the following command:
Configure su base de datos con el siguiente comando:
```bash
npx nx database:reset twenty-server
```
Start the server, the worker and the frontend services:
Inicie el servidor, el trabajador y los servicios frontend:
```bash
npx nx start twenty-server
@@ -309,25 +316,25 @@ npx nx worker twenty-server
npx nx start twenty-front
```
Alternatively, you can start all services at once:
Alternativamente, puede iniciar todos los servicios a la vez:
```bash
npx nx start
```
## Step 8: Use Twenty
## Paso 8: Use Twenty
**Frontend**
Twenty's frontend will be running at [http://localhost:3001](http://localhost:3001).
You can log in using the default demo account: `[email protected]` (password: `[email protected]`)
El frontend de Twenty estará ejecutándose en [http://localhost:3001](http://localhost:3001).
Puede iniciar sesión usando la cuenta demo por defecto: `[email protected]` (contraseña: `[email protected]`)
**Backend**
* Twenty's server will be up and running at [http://localhost:3000](http://localhost:3000)
* The GraphQL API can be accessed at [http://localhost:3000/graphql](http://localhost:3000/graphql)
* The REST API can be reached at [http://localhost:3000/rest](http://localhost:3000/rest)
* El servidor de Twenty estará operativo en [http://localhost:3000](http://localhost:3000)
* La API GraphQL puede ser accedida en [http://localhost:3000/graphql](http://localhost:3000/graphql)
* La API REST puede ser alcanzada en [http://localhost:3000/rest](http://localhost:3000/rest)
## Troubleshooting
## Solución de Problemas
If you encounter any problem, check [Troubleshooting](/l/es/developers/self-host/capabilities/troubleshooting) for solutions.
Si encuentras algún problema, consulta [Solución de Problemas](/l/es/developers/self-host/capabilities/troubleshooting) para ver soluciones.
@@ -1,32 +1,32 @@
---
title: Contribute
description: Contribute to Twenty's open-source development.
title: Contribuir
description: Contribuye al desarrollo de código abierto de Twenty.
---
<Frame>
<img src="/images/user-guide/github/github-header.png" alt="AI" />
<img src="/images/user-guide/github/github-header.png" alt="IA" />
</Frame>
## Overview
## Resumen
Twenty is open-source and welcomes contributions from the community. Whether you're fixing bugs, adding features, or improving documentation, your contributions help make Twenty better for everyone.
Twenty es de código abierto y da la bienvenida a las contribuciones de la comunidad. Ya sea que estés solucionando errores, agregando funciones o mejorando la documentación, tus contribuciones ayudan a que Twenty sea mejor para todos.
## Ways to Contribute
## Formas de contribuir
* **Report bugs**: Help identify and document issues
* **Submit features**: Propose and implement new functionality
* **Improve documentation**: Make our docs clearer and more helpful
* **Frontend development**: Work on the React-based UI
* **Backend development**: Contribute to the NestJS server
* **Reporta errores**: Ayuda a identificar y documentar problemas
* **Propón funcionalidades**: Propón e implementa nuevas funcionalidades
* **Mejora la documentación**: Haz que nuestra documentación sea más clara y útil
* **Desarrollo de frontend**: Trabaja en la interfaz de usuario basada en React
* **Desarrollo de backend**: Contribuye al servidor NestJS
## Getting Started
## Primeros pasos
<CardGroup cols={2}>
<Card title="Bug Reports & Requests" icon="bug" href="/l/es/developers/contribute/capabilities/bug-and-requests">
Report issues or request features
<Card title="Informes de errores y solicitudes" icon="bug" href="/l/es/developers/contribute/capabilities/bug-and-requests">
Reporta problemas o solicita funcionalidades
</Card>
<Card title="Frontend Development" icon="browser" href="/l/es/developers/contribute/capabilities/frontend-development">
Contribute to the UI
<Card title="Desarrollo Frontend" icon="browser" href="/l/es/developers/contribute/capabilities/frontend-development">
Contribuye a la interfaz de usuario
</Card>
</CardGroup>
@@ -1,147 +1,147 @@
---
title: APIs
description: Query and modify your CRM data programmatically using REST or GraphQL.
description: Consulta y modifica tus datos de CRM de forma programática usando REST o GraphQL.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Twenty was built to be developer-friendly, offering powerful APIs that adapt to your custom data model. We provide four distinct API types to meet different integration needs.
Twenty fue creado para ser amigable con los desarrolladores, ofreciendo APIs potentes que se adaptan a tu modelo de datos personalizado. Proveemos cuatro tipos de API distintos para satisfacer diferentes necesidades de integración.
## Developer-First Approach
## Enfoque centrado en el desarrollador
Twenty generates APIs specifically for your data model:
Twenty genera APIs específicamente para tu modelo de datos:
* **No long IDs required**: Use your object and field names directly in endpoints
* **Standard and custom objects treated equally**: Your custom objects get the same API treatment as built-in ones
* **Dedicated endpoints**: Each object and field gets its own API endpoint
* **Custom documentation**: Generated specifically for your workspace's data model
* **No se requieren IDs largos**: Usa los nombres de tus objetos y campos directamente en los endpoints.
* **Objetos estándar y personalizados tratados por igual**: Tus objetos personalizados reciben el mismo tratamiento de API que los incorporados.
* **Endpoints dedicados**: Cada objeto y campo recibe su propio endpoint de API.
* **Documentación personalizada**: Generada específicamente para el modelo de datos de tu espacio de trabajo.
<Note>
Your personalized API documentation is available under **Settings → API & Webhooks** after creating an API key. Since Twenty generates APIs that match your custom data model, the documentation is unique to your workspace.
Tu documentación personalizada de la API está disponible en **Configuración → API & Webhooks** después de crear una clave de API. Como Twenty genera APIs que coinciden con tu modelo de datos personalizado, la documentación es única para tu espacio de trabajo.
</Note>
## The Two API Types
## Los dos tipos de API
### Core API
### API Principal
Accessed on `/rest/` or `/graphql/`
Accesible en `/rest/` o `/graphql/`
Work with your actual **records** (the data):
Trabaja con tus **registros** reales (los datos):
* Create, read, update, delete People, Companies, Opportunities, etc.
* Query and filter data
* Manage record relationships
* Crear, leer, actualizar y eliminar Personas, Empresas, Oportunidades, etc.
* Consultar y filtrar datos
* Gestionar relaciones de registros
### Metadata API
### API de Metadatos
Accessed on `/rest/metadata/` or `/metadata/`
Accesible en `/rest/metadata/` o `/metadata/`
Manage your **workspace and data model**:
Administra tu **espacio de trabajo y modelo de datos**:
* Create, modify, or delete objects and fields
* Configure workspace settings
* Define relationships between objects
* Crear, modificar o eliminar objetos y campos
* Configurar ajustes del espacio de trabajo
* Define relaciones entre objetos
## REST vs GraphQL
Both Core and Metadata APIs are available in REST and GraphQL formats:
Tanto las API Core como las de Metadatos están disponibles en formatos REST y GraphQL:
| Format | Available Operations |
| ----------- | ---------------------------------------------------------- |
| **REST** | CRUD, batch operations, upserts |
| **GraphQL** | Same + **batch upserts**, relationship queries in one call |
| Formato | Operaciones disponibles |
| ----------- | ----------------------------------------------------------------------------- |
| **REST** | CRUD, operaciones por lotes, upserts |
| **GraphQL** | Lo mismo + **upserts por lotes**, consultas de relaciones en una sola llamada |
Choose based on your needs — both formats access the same data.
Elige según tus necesidades — ambos formatos acceden a los mismos datos.
## API Endpoints
## Puntos de Acceso de API
| Environment | Base URL |
| --------------- | ------------------------- |
| **Cloud** | `https://api.twenty.com/` |
| **Self-Hosted** | `https://{your-domain}/` |
| Entorno | URL base |
| ------------------- | ------------------------- |
| **Nube** | `https://api.twenty.com/` |
| **Autoalojamiento** | `https://{your-domain}/` |
## Authentication
## Autenticación
Every API request requires an API key in the header:
Cada solicitud a la API requiere una clave de API en el encabezado:
```
Authorization: Bearer YOUR_API_KEY
```
### Create an API Key
### Crear una Clave de API
1. Go to **Settings → APIs & Webhooks**
2. Click **+ Create key**
3. Configure:
* **Name**: Descriptive name for the key
* **Expiration Date**: When the key expires
4. Click **Save**
5. **Copy immediately** — the key is only shown once
1. Ve a **Configuración → APIs y Webhooks**
2. Haz clic en **+ Crear clave**
3. Configurar:
* **Nombre**: Nombre descriptivo para la clave
* **Fecha de vencimiento**: Cuándo expira la clave
4. Haga clic en **Guardar**
5. **Copia de inmediato** — la clave solo se muestra una vez
<VimeoEmbed videoId="928786722" title="Creating API key" />
<VimeoEmbed videoId="928786722" title="Creación de clave de API" />
<Warning>
Your API key grants access to sensitive data. Don't share it with untrusted services. If compromised, disable it immediately and generate a new one.
Tu clave de API concede acceso a datos sensibles. No la compartas con servicios no confiables. Si se ve comprometida, desactívala de inmediato y genera una nueva.
</Warning>
### Assign a Role to an API Key
### Asignar un rol a una clave de API
For better security, assign a specific role to limit access:
Para mayor seguridad, asigna un rol específico para limitar el acceso:
1. Go to **Settings → Roles**
2. Click on the role to assign
3. Open the **Assignment** tab
4. Under **API Keys**, click **+ Assign to API key**
5. Select the API key
1. Ve a **Configuración → Roles**
2. Haz clic en el rol que deseas asignar
3. Abre la pestaña **Asignación**
4. En **Claves de API**, haz clic en **+ Asignar a clave de API**
5. Selecciona la clave de API
The key will inherit that role's permissions. See [Permissions](/l/es/user-guide/permissions-access/capabilities/permissions) for details.
La clave heredará los permisos de ese rol. Consulta [Permisos](/l/es/user-guide/permissions-access/capabilities/permissions) para más detalles.
### Manage API Keys
### Gestionar Claves de API
**Regenerate**: Settings → APIs & Webhooks → Click key → **Regenerate**
**Regenerar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Regenerar**
**Delete**: Settings → APIs & Webhooks → Click key → **Delete**
**Eliminar**: Configuración → APIs & Webhooks → Haz clic en la clave → **Eliminar**
## API Playground
## Playground de la API
Test your APIs directly in the browser with our built-in playground — available for both **REST** and **GraphQL**.
Prueba tus API directamente en el navegador con nuestro playground integrado — disponible tanto para **REST** como para **GraphQL**.
### Access the Playground
### Accede al Playground
1. Go to **Settings → APIs & Webhooks**
2. Create an API key (required)
3. Click on **REST API** or **GraphQL API** to open the playground
1. Ve a **Configuración → APIs y Webhooks**
2. Crea una clave de API (obligatorio)
3. Haz clic en **REST API** o **GraphQL API** para abrir el playground
### What You Get
### Lo que obtienes
* **Interactive documentation**: Generated for your specific data model
* **Live testing**: Execute real API calls against your workspace
* **Schema explorer**: Browse available objects, fields, and relationships
* **Request builder**: Construct queries with autocomplete
* **Documentación interactiva**: Generada para tu modelo de datos específico
* **Pruebas en vivo**: Ejecuta llamadas reales a la API en tu espacio de trabajo
* **Explorador de esquemas**: Navega por los objetos, campos y relaciones disponibles
* **Constructor de solicitudes**: Crea consultas con autocompletado
The playground reflects your custom objects and fields, so documentation is always accurate for your workspace.
El playground refleja tus objetos y campos personalizados, por lo que la documentación siempre es precisa para tu espacio de trabajo.
## Batch Operations
## Operaciones por Lotes
Both REST and GraphQL support batch operations:
Tanto REST como GraphQL admiten operaciones por lotes:
* **Batch size**: Up to 60 records per request
* **Operations**: Create, update, delete multiple records
* **Tamaño del lote**: Hasta 60 registros por solicitud
* **Operaciones**: Crear, actualizar y eliminar múltiples registros
**GraphQL-only features:**
**Características exclusivas de GraphQL:**
* **Batch Upsert**: Create or update in one call
* Use plural object names (e.g., `CreateCompanies` instead of `CreateCompany`)
* **Upsert por lotes**: Crear o actualizar en una llamada
* Usa nombres de objetos en plural (por ejemplo, `CreateCompanies` en lugar de `CreateCompany`)
## Rate Limits
## Límites de tasa
API requests are throttled to ensure platform stability:
Las solicitudes a la API se limitan para garantizar la estabilidad de la plataforma:
| Limit | Value |
| -------------- | -------------------- |
| **Requests** | 100 calls per minute |
| **Batch size** | 60 records per call |
| Límite | Valor |
| ------------------- | -------------------------- |
| **Solicitudes** | 100 solicitudes por minuto |
| **Tamaño del lote** | 60 registros por llamada |
<Tip>
Use batch operations to maximize throughput — process up to 60 records in a single API call instead of making individual requests.
Usa operaciones por lotes para maximizar el rendimiento — procesa hasta 60 registros en una sola llamada a la API en lugar de hacer solicitudes individuales.
</Tip>
@@ -1,81 +1,85 @@
---
title: Twenty Apps
description: Build and manage Twenty customizations as code.
title: Aplicaciones de Twenty
description: Crea y gestiona personalizaciones de Twenty como código.
---
<Warning>
Apps are currently in alpha testing. The feature is functional but still evolving.
Las aplicaciones están actualmente en pruebas alfa. La funcionalidad es operativa, pero sigue evolucionando.
</Warning>
## What Are Apps?
## ¿Qué son las aplicaciones?
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
Las aplicaciones te permiten crear y administrar personalizaciones de Twenty **como código**. En lugar de configurar todo a través de la interfaz de usuario, defines tu modelo de datos y funciones de lógica en código, lo que hace más rápido crear, mantener y desplegar en múltiples espacios de trabajo.
**What you can do today:**
**Lo que puedes hacer hoy:**
* Define custom objects and fields as code (managed data model)
* Build serverless functions with custom triggers
* Deploy the same app across multiple workspaces
* Define objetos y campos personalizados como código (modelo de datos gestionado)
* Crea funciones de lógica con desencadenadores personalizados
* Despliega la misma aplicación en múltiples espacios de trabajo
**Coming soon:**
**Próximamente:**
* Custom UI layouts and components
* Diseños y componentes de la interfaz de usuario personalizados
## Prerequisites
## Prerrequisitos
* Node.js 24+ and Yarn 4
* A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
* Node.js 24+ y Yarn 4
* Un espacio de trabajo de Twenty y una clave de API (créala en https://app.twenty.com/settings/api-webhooks)
## Getting Started
## Primeros pasos
Create a new app using the official scaffolder, then authenticate and start developing:
Crea una aplicación nueva usando el generador oficial, luego autentícate y comienza a desarrollar:
```bash filename="Terminal"
# Scaffold a new app
# Crear la estructura de una nueva aplicación
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Authenticate using your API key (you'll be prompted)
yarn auth
# Si no usas yarn@4
corepack enable
yarn install
# Start dev mode: automatically syncs local changes to your workspace
yarn dev
# Autentícate con tu clave de API (se te pedirá)
yarn auth:login
# Inicia el modo de desarrollo: sincroniza automáticamente los cambios locales con tu espacio de trabajo
yarn app:dev
```
From here you can:
Desde aquí usted puede:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn create-entity
# Añade una nueva entidad a tu aplicación (guiado)
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn generate
# Genera un cliente tipado de Twenty y tipos de entidad del espacio de trabajo
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn sync
# Supervisa los registros de funciones de tu aplicación
yarn function:logs
# Watch your application's functions logs
yarn logs
# Ejecuta una función por nombre
yarn function:execute -n my-function -p '{\"name\": \"test\"}'
# Uninstall the application from the current workspace
yarn uninstall
# Desinstala la aplicación del espacio de trabajo actual
yarn app:uninstall
# Display commands' help
# Muestra la ayuda de los comandos
yarn help
```
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Consulta también: las páginas de referencia de la CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) y [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
## Project structure (scaffolded)
## Estructura del proyecto (generada)
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
Cuando ejecutas `npx create-twenty-app@latest my-twenty-app`, el generador:
* Copies a minimal base application into `my-twenty-app/`
* Adds a local `twenty-sdk` dependency and Yarn 4 configuration
* Creates config files and scripts wired to the `twenty` CLI
* Generates a default application config and a default function role
* Copia una aplicación base mínima en `my-twenty-app/`
* Añade una dependencia local de `twenty-sdk` y la configuración de Yarn 4
* Crea archivos de configuración y scripts vinculados a la CLI `twenty`
* Genera una configuración de aplicación predeterminada y un rol de función predeterminado
A freshly scaffolded app looks like this:
Una aplicación recién generada se ve así:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -85,79 +89,150 @@ my-twenty-app/
.nvmrc
.yarnrc.yml
.yarn/
releases/
yarn-4.9.2.cjs
install-state.gz
eslint.config.mjs
tsconfig.json
README.md
public/ # Carpeta de recursos públicos (imágenes, fuentes, etc.)
src/
application.config.ts
role.config.ts
// your entities, actions, and other app files
application.config.ts # Requerido - configuración principal de la aplicación
default-function.role.ts # Rol predeterminado para funciones sin servidor
hello-world.function.ts # Ejemplo de función sin servidor
hello-world.front-component.tsx # Ejemplo de componente frontal
// tus entidades (*.object.ts, *.function.ts, *.front-component.tsx, *.role.ts)
```
At a high level:
### Convención sobre configuración
* **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
* **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
* **.nvmrc**: Pins the Node.js version expected by the project.
* **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your apps TypeScript sources.
* **README.md**: A short README in the app root with basic instructions.
* **src/**: The main place where you define your application-as-code:
* `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See “Application config” below.
* `role.config.ts`: Default function role used by your serverless functions. See “Default function role” below.
* Future entities, actions/functions, and any supporting code you add.
Las aplicaciones usan un enfoque de **convención sobre configuración** en el que las entidades se detectan por su sufijo de archivo. Esto permite una organización flexible dentro de la carpeta `src/app/`:
Later commands will add more files and folders:
| Sufijo de archivo | Tipo de entidad |
| ----------------------- | --------------------------------------- |
| `*.object.ts` | Definiciones de objetos personalizados |
| `*.function.ts` | Definiciones de funciones sin servidor |
| `*.front-component.tsx` | Definiciones de componentes de interfaz |
| `*.role.ts` | Definiciones de roles |
* `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
* `yarn create-entity` will add entity definition files under `src/` for your custom objects.
### Organizaciones de carpetas compatibles
## Authentication
Puedes organizar tus entidades con cualquiera de estos patrones:
The first time you run `yarn auth`, you'll be prompted for:
**Tradicional (por tipo):**
* API URL (defaults to http://localhost:3000 or your current workspace profile)
* API key
```text
src/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
├── components/
│ └── card.front-component.tsx
└── roles/
└── admin.role.ts
```
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
**Basado en funcionalidades:**
Examples:
```text
src/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
├── card.front-component.tsx
└── postCardAdmin.role.ts
```
**Plano:**
```text
src/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
├── card.front-component.tsx
└── admin.role.ts
```
A grandes rasgos:
* **package.json**: Declara el nombre de la aplicación, la versión, los entornos (Node 24+, Yarn 4) y agrega `twenty-sdk` además de scripts como `app:dev`, `app:generate`, `entity:add`, `function:logs`, `function:execute`, `app:uninstall` y `auth:login` que delegan en la CLI local `twenty`.
* **.gitignore**: Ignora artefactos comunes como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, carpetas de cobertura, archivos de registro y archivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloquean y configuran la cadena de herramientas Yarn 4 utilizada por el proyecto.
* **.nvmrc**: Fija la versión de Node.js esperada por el proyecto.
* **eslint.config.mjs** y **tsconfig.json**: Proporcionan linting y configuración de TypeScript para las fuentes de TypeScript de tu aplicación.
* **README.md**: Un README breve en la raíz de la aplicación con instrucciones básicas.
* **public/**: Una carpeta para almacenar recursos públicos (imágenes, fuentes, archivos estáticos) que se servirán con tu aplicación. Los archivos colocados aquí se cargan durante la sincronización y son accesibles en tiempo de ejecución.
* **src/**: El lugar principal donde defines tu aplicación como código:
* `application.config.ts`: Configuración global de tu aplicación (metadatos y vinculación en tiempo de ejecución). Consulta "Configuración de la aplicación" más abajo.
* `*.role.ts`: Definiciones de roles usadas por tus funciones de lógica. Consulta "Rol de función predeterminado" más abajo.
* `*.object.ts`: Definiciones de objetos personalizados.
* `*.function.ts`: Definiciones de funciones de lógica.
* `*.front-component.tsx`: Definiciones de componentes de interfaz.
Comandos posteriores añadirán más archivos y carpetas:
* `yarn app:generate` creará una carpeta `generated/` (cliente tipado de Twenty + tipos del espacio de trabajo).
* `yarn entity:add` añadirá archivos de definición de entidades en `src/` para tus objetos, funciones, componentes de interfaz o roles personalizados.
## Autenticación
La primera vez que ejecutes `yarn auth:login`, se te solicitará:
* URL de la API (por defecto http://localhost:3000 o el perfil de tu espacio de trabajo actual)
* Clave de API
Tus credenciales se almacenan por usuario en `~/.twenty/config.json`. Puedes mantener varios perfiles y cambiar entre ellos.
### Gestión de espacios de trabajo
```bash filename="Terminal"
# Login interactively (recommended)
yarn auth
yarn auth:login
# Use a specific workspace profile
yarn auth --workspace my-custom-workspace
# Login to a specific workspace profile
yarn auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn auth:list
# Switch the default workspace (interactive)
yarn auth:switch
# Switch to a specific workspace
yarn auth:switch production
# Check current authentication status
yarn auth:status
```
## Use the SDK resources (types & config)
Una vez que hayas cambiado de espacio de trabajo con `auth:switch`, todos los comandos posteriores usarán ese espacio de trabajo de forma predeterminada. Aún puedes anularlo temporalmente con `--workspace <name>`.
The twenty-sdk provides typed building blocks you use inside your app. Below are the key pieces you'll touch most often.
## Usa los recursos del SDK (tipos y configuración)
### Defining objects
El twenty-sdk proporciona bloques de construcción tipados y funciones auxiliares que utilizas dentro de tu aplicación. A continuación, las partes clave que usarás con más frecuencia.
Custom objects are regular TypeScript classes annotated with decorators from `twenty-sdk`. They live under `src/objects/` in your app and describe both schema and behavior for records in your workspace.
### Funciones auxiliares
Here is an example `postCard` object from the Hello World app:
El SDK proporciona cuatro funciones auxiliares con validación incorporada para definir las entidades de tu aplicación:
| Función | Propósito |
| --------------------- | ---------------------------------------------- |
| `defineApplication()` | Configura los metadatos de la aplicación |
| `defineObject()` | Define objetos personalizados con campos |
| `defineFunction()` | Define funciones de lógica con controladores |
| `defineRole()` | Configura permisos de roles y acceso a objetos |
Estas funciones validan tu configuración en tiempo de ejecución y proporcionan un mejor autocompletado en el IDE y seguridad de tipos.
### Definir objetos
Los objetos personalizados describen tanto el esquema como el comportamiento de los registros en tu espacio de trabajo. Usa `defineObject()` para definir objetos con validación incorporada:
```typescript
import { type Note } from '../../generated';
import {
type AddressField,
Field,
FieldType,
type FullNameField,
Object,
OnDeleteAction,
Relation,
RelationType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
// src/app/postCard.object.ts
import { defineObject, FieldType } from 'twenty-sdk';
enum PostCardStatus {
DRAFT = 'DRAFT',
@@ -166,176 +241,135 @@ enum PostCardStatus {
RETURNED = 'RETURNED',
}
@Object({
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post card',
labelPlural: 'Post cards',
description: ' A post card object',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
})
export class PostCard {
@Field({
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
})
content: string;
@Field({
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
})
recipientName: FullNameField;
@Field({
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
})
recipientAddress: AddressField;
@Field({
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
})
status: PostCardStatus;
@Relation({
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
type: RelationType.ONE_TO_MANY,
label: 'Notes',
icon: 'IconComment',
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
onDelete: OnDeleteAction.CASCADE,
})
notes: Note[];
@Field({
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
})
deliveredAt?: Date;
}
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
Key points:
Puntos clave:
* The `@Object` decorator defines the object identity and labels used across the workspace; its `universalIdentifier` must be unique and stable across deployments.
* Each `@Field` decorator defines a field on the object with a type, label, and its own stable `universalIdentifier`.
* `@Relation` wires this object to other objects (standard or custom) and controls cascade behavior with `onDelete`.
* You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships, then generates object files similar to the `postCard` example.
* Usa `defineObject()` para validación incorporada y mejor soporte del IDE.
* El `universalIdentifier` debe ser único y estable entre implementaciones.
* Cada campo requiere `name`, `type`, `label` y su propio `universalIdentifier` estable.
* La matriz `fields` es opcional: puedes definir objetos sin campos personalizados.
* Puedes generar nuevos objetos usando `yarn entity:add`, que te guía por el nombrado, los campos y las relaciones.
### Application config (application.config.ts)
<Note>
**Los campos base se crean automáticamente.** Cuando defines un objeto personalizado, Twenty añade automáticamente campos estándar como `name`, `createdAt`, `updatedAt`, `createdBy`, `position` y `deletedAt`. No necesitas definir estos en tu matriz `fields` — solo agrega tus campos personalizados.
</Note>
Every app has a single `application.config.ts` file that describes:
### Configuración de la aplicación (application.config.ts)
* **Who the app is**: identifiers, display name, and description.
* **How its functions run**: which role they use for permissions.
* **(Optional) variables**: keyvalue pairs exposed to your functions as environment variables.
Cada aplicación tiene un único archivo `application.config.ts` que describe:
When you scaffold a new app, you start with a minimal config:
* **Qué es la aplicación**: identificadores, nombre para mostrar y descripción.
* **Cómo se ejecutan sus funciones**: qué rol usan para permisos.
* **Variables (opcionales)**: pares clavevalor expuestos a tus funciones como variables de entorno.
Use `defineApplication()` to define your application configuration:
```typescript
import { type ApplicationConfig } from 'twenty-sdk';
// src/app/application.config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
const config: ApplicationConfig = {
universalIdentifier: '<generated-app-uuid>',
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
functionRoleUniversalIdentifier: '<generated-role-uuid>',
};
export default config;
```
You can gradually extend this file as your app grows. For example, you can add an icon and application-scoped variables:
```typescript
import { type ApplicationConfig } from 'twenty-sdk';
const config: ApplicationConfig = {
universalIdentifier: '<your-app-uuid>',
displayName: 'My App',
description: 'What your app does',
icon: 'IconWorld', // Choose an icon by name
icon: 'IconWorld',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '<uuid>',
description: 'Default recipient used by functions',
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
functionRoleUniversalIdentifier: '<your-role-uuid>',
};
export default config;
roleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
});
```
Notes:
Notas:
* `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
* `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` must match the role you define in `role.config.ts` (see below).
* Los campos `universalIdentifier` son ID deterministas bajo tu control; genéralos una vez y mantenlos estables entre sincronizaciones.
* Las `applicationVariables` se convierten en variables de entorno para tus funciones (por ejemplo, `DEFAULT_RECIPIENT_NAME` está disponible como `process.env.DEFAULT_RECIPIENT_NAME`).
* `roleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
#### Roles and permissions
#### Roles y permisos
Applications can define roles that encapsulate permissions on your workspaces objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your apps serverless functions.
Las aplicaciones pueden definir roles que encapsulan permisos sobre los objetos y acciones de tu espacio de trabajo. The field `roleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's logic functions.
* The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
* The typed client will be restricted to the permissions granted to that role.
* Follow leastprivilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
* La clave de API en tiempo de ejecución inyectada como `TWENTY_API_KEY` se deriva de este rol de función predeterminado.
* El cliente tipado estará restringido a los permisos otorgados a ese rol.
* Sigue el principio de mínimo privilegio: crea un rol dedicado con solo los permisos que necesitan tus funciones y luego referencia su identificador universal.
##### Default function role (role.config.ts)
##### Rol de función predeterminado (\*.role.ts)
When you scaffold a new app, the CLI also creates `src/role.config.ts`. This file exports the default role your serverless functions will use at runtime:
Cuando generas una nueva aplicación, la CLI también crea un archivo de rol predeterminado. Usa `defineRole()` para definir roles con validación incorporada:
```typescript
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
// src/app/default-function.role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
export const functionRole: RoleConfig = {
universalIdentifier: '<generated-role-uuid>',
label: 'My Twenty App default function role',
description: 'My Twenty App default function role',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: false,
};
```
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
The `universalIdentifier` of this role is automatically wired into `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
* **role.config.ts** defines what the default function role can do.
* **application.config.ts** points to that role so your functions inherit its permissions.
As you move beyond the initial scaffold, you should tighten this role and make it explicit about what it can access. A more production-ready role might look closer to:
```typescript
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
export const functionRole: RoleConfig = {
universalIdentifier: '<your-role-uuid>',
export default defineRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: false,
@@ -348,7 +382,7 @@ export const functionRole: RoleConfig = {
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -357,47 +391,42 @@ export const functionRole: RoleConfig = {
],
fieldPermissions: [
{
objectNameSingular: 'postCard',
fieldName: 'content',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlags: ['APPLICATIONS'],
};
permissionFlags: [PermissionFlag.APPLICATIONS],
});
```
Notes:
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `roleUniversalIdentifier`. En otras palabras:
* Start from the scaffolded role, then progressively restrict it following leastprivilege.
* Replace the `objectPermissions` and `fieldPermissions` with the objects/fields your functions need.
* `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
* See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
* **\*.role.ts** define lo que puede hacer el rol de función predeterminado.
* **application.config.ts** apunta a ese rol para que tus funciones hereden sus permisos.
### Serverless function config and entrypoint
Notas:
Each function exports a main handler and a config describing its triggers. You can mix multiple trigger types.
* Parte del rol generado y luego restríngele progresivamente siguiendo el principio de mínimo privilegio.
* Reemplaza `objectPermissions` y `fieldPermissions` con los objetos/campos que necesitan tus funciones.
* `permissionFlags` controla el acceso a capacidades a nivel de plataforma. Mantenlos al mínimo; agrega solo lo que necesites.
* Consulta un ejemplo funcional en la aplicación Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Configuración y punto de entrada de funciones de lógica
Cada archivo de función usa `defineFunction()` para exportar una configuración con un controlador y desencadenadores opcionales. Usa el sufijo de archivo `*.function.ts` para la detección automática.
```typescript
// src/actions/create-new-post-card.ts
import type {
FunctionConfig,
DatabaseEventPayload,
ObjectRecordCreateEvent,
CronPayload,
} from 'twenty-sdk';
import Twenty, { type Person } from '../generated';
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';
// main handler can accept parameters from route, cron, or database events
export const main = async (
params:
| { name?: string }
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
| CronPayload,
) => {
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const name = 'name' in params
? params.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
: 'Hello world';
const result = await client.mutation({
@@ -410,113 +439,216 @@ export const main = async (
return result;
};
export const config: FunctionConfig = {
universalIdentifier: '<function-uuid>',
export default defineFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
handler,
triggers: [
// Public HTTP route trigger '/s/post-card/create'
{
universalIdentifier: '<route-trigger-uuid>',
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/post-card/create',
httpMethod: 'GET',
isAuthRequired: false,
},
// Cron trigger (CRON pattern)
{
universalIdentifier: '<cron-trigger-uuid>',
type: 'cron',
pattern: '0 0 1 1 *',
},
// {
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
// type: 'cron',
// pattern: '0 0 1 1 *',
// },
// Database event trigger
{
universalIdentifier: '<db-trigger-uuid>',
type: 'databaseEvent',
eventName: 'person.created',
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
Tipos de desencadenadores comunes:
* **route**: Expone tu función en una ruta y método HTTP **bajo el endpoint `/s/`**:
> p. ej. `path: '/post-card/create',` -> llamar en `<APP_URL>/s/post-card/create`
* **cron**: Ejecuta tu función en un horario usando una expresión CRON.
* **databaseEvent**: Se ejecuta en eventos del ciclo de vida de objetos del espacio de trabajo. Cuando la operación del evento es `updated`, se pueden especificar campos específicos que se deben escuchar en el arreglo `updatedFields`. Si se deja sin definir o vacío, cualquier actualización activará la función.
> p. ej., `person.updated`
Notas:
* La matriz `triggers` es opcional. Las funciones sin desencadenadores pueden usarse como funciones utilitarias llamadas por otras funciones.
* Puedes combinar múltiples tipos de desencadenadores en una sola función.
### Carga útil del disparador de ruta
<Warning>
**Cambio no retrocompatible (v1.16, enero de 2026):** El formato de la carga útil del disparador de ruta ha cambiado. Antes de la v1.16, los parámetros de consulta, los parámetros de ruta y el cuerpo se enviaban directamente como la carga útil. A partir de la v1.16, están anidados dentro de un objeto `RoutePayload` estructurado.
**Antes de la v1.16:**
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
**Después de la v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**Para migrar las funciones existentes:** Actualiza tu controlador para desestructurar desde `event.body`, `event.queryStringParameters` o `event.pathParameters` en lugar de hacerlo directamente desde el objeto params.
</Warning>
Cuando un disparador de ruta invoca tu función de lógica, esta recibe un objeto `RoutePayload` que sigue el formato de AWS HTTP API v2. Importa el tipo desde `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
const { headers, queryStringParameters, pathParameters, body } = event;
// HTTP method and path are available in requestContext
const { method, path } = event.requestContext.http;
return { message: 'Success' };
};
```
Common trigger types:
El tipo `RoutePayload` tiene la siguiente estructura:
* route: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
| Propiedad | Tipo | Descripción |
| ---------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
| `headers` | `Record<string, string \| undefined>` | Encabezados HTTP (solo aquellos listados en `forwardedRequestHeaders`) |
| `queryStringParameters` | `Record<string, string \| undefined>` | Parámetros de consulta (valores múltiples unidos con comas) |
| `pathParameters` | `Record<string, string \| undefined>` | Parámetros de ruta extraídos del patrón de ruta (p. ej., `/users/:id` → `{ id: '123' }`) |
| `cuerpo` | `object \| null` | Cuerpo de la solicitud analizado (JSON) |
| `isBase64Encoded` | `booleano` | Indica si el cuerpo está codificado en base64 |
| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) |
| `requestContext.http.path` | `string` | Ruta de la solicitud sin procesar |
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
### Reenvío de encabezados HTTP
* cron: Runs your function on a schedule using a CRON expression.
* databaseEvent: Runs on workspace object lifecycle events
> e.g. `person.created`
You can create new functions in two ways:
* **Scaffolded**: Run `yarn create-entity --path <custom-path>` and choose the option to add a new function. This generates a starter file under `<custom-path>` with a `main` handler and a `config` block similar to the example above.
* **Manual**: Create a new file and export `main` and `config` yourself, following the same pattern.
### Generated typed client
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
De forma predeterminada, los encabezados HTTP de las solicitudes entrantes **no** se pasan a tu función de lógica por razones de seguridad. Para acceder a encabezados específicos, enuméralos explícitamente en el arreglo `forwardedRequestHeaders`:
```typescript
import Twenty from './generated';
export default defineFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
triggers: [
{
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
type: 'route',
path: '/webhook',
httpMethod: 'POST',
isAuthRequired: false,
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
},
],
});
```
En tu controlador, luego puedes acceder a estos encabezados:
```typescript
const handler = async (event: RoutePayload) => {
const signature = event.headers['x-webhook-signature'];
const contentType = event.headers['content-type'];
// Validate webhook signature...
return { received: true };
};
```
<Note>
Los nombres de los encabezados se normalizan a minúsculas. Accede a ellos usando claves en minúsculas (por ejemplo, `event.headers['content-type']`).
</Note>
Puedes crear funciones nuevas de dos maneras:
* **Generado**: Ejecuta `yarn entity:add` y elige la opción para añadir una nueva función. Esto genera un archivo inicial con un controlador y configuración.
* **Manual**: Crea un nuevo archivo `*.function.ts` y usa `defineFunction()`, siguiendo el mismo patrón.
### Cliente tipado generado
Ejecuta yarn app:generate para crear un cliente tipado local en generated/ basado en el esquema de tu espacio de trabajo. Úsalo en tus funciones:
```typescript
import Twenty from '~/generated';
const client = new Twenty();
const { me } = await client.query({ me: { id: true, displayName: true } });
```
The client is re-generated by `yarn generate`. Re-run after changing your objects and `yarn sync` or when onboarding to a new workspace.
El cliente se vuelve a generar con `yarn app:generate`. Vuelve a ejecutarlo después de cambiar tus objetos o al incorporarte a un nuevo espacio de trabajo.
#### Runtime credentials in serverless functions
#### Credenciales en tiempo de ejecución en funciones de lógica
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
Cuando tu función se ejecuta en Twenty, la plataforma inyecta credenciales como variables de entorno antes de que tu código se ejecute:
* `TWENTY_API_URL`: Base URL of the Twenty API your app targets.
* `TWENTY_API_KEY`: Shortlived key scoped to your applications default function role.
* `TWENTY_API_URL`: URL base de la API de Twenty a la que apunta tu aplicación.
* `TWENTY_API_KEY`: Clave de corta duración con alcance al rol de función predeterminado de tu aplicación.
Notes:
Notas:
* You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
* The API keys permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
* Applications can define roles to follow leastprivilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that roles universal identifier.
* No necesitas pasar la URL ni la clave de API al cliente generado. Lee `TWENTY_API_URL` y `TWENTY_API_KEY` de process.env en tiempo de ejecución.
* The API key's permissions are determined by the role referenced in your `application.config.ts` via `roleUniversalIdentifier`. Este es el rol predeterminado que usan las funciones de lógica de tu aplicación.
* Las aplicaciones pueden definir roles para seguir el principio de mínimo privilegio. Grant only the permissions your functions need, then point `roleUniversalIdentifier` to that role's universal identifier.
### Hello World example
### Ejemplo Hello World
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
Explora un ejemplo mínimo de extremo a extremo que demuestra objetos, funciones y múltiples desencadenadores [aquí](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Manual setup (without the scaffolder)
## Configuración manual (sin el generador)
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
Aunque recomendamos usar `create-twenty-app` para la mejor experiencia de inicio, también puedes configurar un proyecto manualmente. No instales la CLI globalmente. En su lugar, agrega `twenty-sdk` como dependencia local y conecta scripts en tu package.json:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
Then add scripts like these:
Luego agrega scripts como estos:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"auth:login": "twenty auth:login",
"auth:logout": "twenty auth:logout",
"auth:status": "twenty auth:status",
"auth:switch": "twenty auth:switch",
"auth:list": "twenty auth:list",
"app:dev": "twenty app:dev",
"app:generate": "twenty app:generate",
"app:uninstall": "twenty app:uninstall",
"entity:add": "twenty entity:add",
"function:logs": "twenty function:logs",
"function:execute": "twenty function:execute",
"help": "twenty help"
}
}
```
Now you can run the same commands via Yarn, e.g. `yarn dev`, `yarn sync`, etc.
Ahora puedes ejecutar los mismos comandos mediante Yarn, p. ej., `yarn app:dev`, `yarn app:generate`, etc.
## Troubleshooting
## Solución de problemas
* Authentication errors: run `yarn auth` and ensure your API key has the required permissions.
* Cannot connect to server: verify the API URL and that the Twenty server is reachable.
* Types or client missing/outdated: run `yarn generate` and then `yarn dev`.
* Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
* Errores de autenticación: ejecuta `yarn auth:login` y asegúrate de que tu clave de API tenga los permisos necesarios.
* No se puede conectar al servidor: verifica la URL de la API y que el servidor de Twenty sea accesible.
* Tipos o cliente faltantes/obsoletos: ejecuta `yarn app:generate`.
* El modo de desarrollo no sincroniza: asegúrate de que `yarn app:dev` esté ejecutándose y de que los cambios no sean ignorados por tu entorno.
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
Canal de ayuda en Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -1,44 +1,44 @@
---
title: Webhooks
description: Receive real-time notifications when events occur in your CRM.
description: Recibe notificaciones en tiempo real cuando ocurran eventos en tu CRM.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Webhooks push data to your systems in real-time when events occur in Twenty — no polling required. Use them to keep external systems in sync, trigger automations, or send alerts.
Los webhooks envían datos a tus sistemas en tiempo real cuando ocurren eventos en Twenty — no se requiere sondeo. Úsalos para mantener sincronizados los sistemas externos, activar automatizaciones o enviar alertas.
## Create a Webhook
## Crear un Webhook
1. Go to **Settings → APIs & Webhooks → Webhooks**
2. Click **+ Create webhook**
3. Enter your webhook URL (must be publicly accessible)
4. Click **Save**
1. Ve a **Configuración → APIs y Webhooks → Webhooks**
2. Haga clic en **+ Crear webhook**
3. Introduce la URL de tu webhook (debe ser públicamente accesible)
4. Haga clic en **Guardar**
The webhook activates immediately and starts sending notifications.
El webhook se activa de inmediato y comienza a enviar notificaciones.
<VimeoEmbed videoId="928786708" title="Creating a webhook" />
<VimeoEmbed videoId="928786708" title="Crear un webhook" />
### Manage Webhooks
### Gestionar Webhooks
**Edit**: Click the webhook → Update URL → **Save**
**Editar**: Haz clic en el webhook → Actualizar la URL → **Guardar**
**Delete**: Click the webhook → **Delete** → Confirm
**Eliminar**: Haz clic en el webhook → **Eliminar** → Confirmar
## Events
## Eventos
Twenty sends webhooks for these event types:
Twenty envía webhooks para estos tipos de eventos:
| Event | Example |
| ------------------ | ---------------------------------------------------------- |
| **Record Created** | `person.created`, `company.created`, `note.created` |
| **Record Updated** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Record Deleted** | `person.deleted`, `company.deleted` |
| Evento | Ejemplo |
| ---------------------------- | ---------------------------------------------------------- |
| **Se crea un registro** | `person.created`, `company.created`, `note.created` |
| **Se actualiza un registro** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Se elimina un registro** | `person.deleted`, `company.deleted` |
All event types are sent to your webhook URL. Event filtering may be added in future releases.
Todos los tipos de eventos se envían a la URL de tu webhook. Es posible que se agregue el filtrado de eventos en versiones futuras.
## Payload Format
## Formato de la carga útil
Each webhook sends an HTTP POST with a JSON body:
Cada webhook envía una solicitud HTTP POST con un cuerpo JSON:
```json
{
@@ -55,35 +55,35 @@ Each webhook sends an HTTP POST with a JSON body:
}
```
| Field | Description |
| ----------- | ------------------------------------------------ |
| `event` | What happened (e.g., `person.created`) |
| `data` | The full record that was created/updated/deleted |
| `timestamp` | When the event occurred (UTC) |
| Campo | Descripción |
| ----------------- | -------------------------------------------------- |
| `evento` | Qué ocurrió (p. ej., `person.created`) |
| `datos` | El registro completo que se creó/actualizó/eliminó |
| `marca de tiempo` | Cuándo ocurrió el evento (UTC) |
<Note>
Respond with a **2xx HTTP status** (200-299) to acknowledge receipt. Non-2xx responses are logged as delivery failures.
Responde con un **estado HTTP 2xx** (200-299) para confirmar la recepción. Las respuestas que no sean 2xx se registran como errores de entrega.
</Note>
## Webhook Validation
## Validación de Webhook
Twenty signs each webhook request for security. Validate signatures to ensure requests are authentic.
Twenty firma cada solicitud de webhook por seguridad. Valida las firmas para garantizar que las solicitudes sean auténticas.
### Headers
### Encabezados
| Header | Description |
| ---------------------------- | --------------------- |
| `X-Twenty-Webhook-Signature` | HMAC SHA256 signature |
| `X-Twenty-Webhook-Timestamp` | Request timestamp |
| Encabezado | Descripción |
| ---------------------------- | ------------------------------- |
| `X-Twenty-Webhook-Signature` | Firma HMAC SHA256 |
| `X-Twenty-Webhook-Timestamp` | Marca de tiempo de la solicitud |
### Validation Steps
### Pasos de validación
1. Get the timestamp from `X-Twenty-Webhook-Timestamp`
2. Create the string: `{timestamp}:{JSON payload}`
3. Compute HMAC SHA256 using your webhook secret
4. Compare with `X-Twenty-Webhook-Signature`
1. Obtén la marca de tiempo de `X-Twenty-Webhook-Timestamp`
2. Crea la cadena: `{timestamp}:{JSON payload}`
3. Calcula HMAC SHA256 usando tu secreto de webhook
4. Compara con `X-Twenty-Webhook-Signature`
### Example (Node.js)
### Ejemplo (Node.js)
```javascript
const crypto = require("crypto");
@@ -101,12 +101,12 @@ const expectedSignature = crypto
const isValid = expectedSignature === req.headers["x-twenty-webhook-signature"];
```
## Webhooks vs Workflows
## Webhooks vs flujos de trabajo
| Method | Direction | Use Case |
| ---------------------------- | --------- | ---------------------------------------------------------- |
| **Webhooks** | OUT | Automatically notify external systems of any record change |
| **Workflow + HTTP Request** | OUT | Send data out with custom logic (filters, transformations) |
| **Workflow Webhook Trigger** | IN | Receive data into Twenty from external systems |
| Método | Dirección | Caso de uso |
| --------------------------------------------- | --------- | --------------------------------------------------------------------------------- |
| **Webhooks** | SALIDA | Notificar automáticamente a los sistemas externos cualquier cambio en un registro |
| **Flujo de trabajo + solicitud HTTP** | SALIDA | Enviar datos con lógica personalizada (filtros, transformaciones) |
| **Disparador de webhook de flujo de trabajo** | ENTRADA | Recibir datos en Twenty desde sistemas externos |
For receiving external data, see [Set Up a Webhook Trigger](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
Para recibir datos externos, consulta [Configurar un disparador de webhook](/l/es/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).
@@ -1,34 +1,34 @@
---
title: Extend
description: Extend Twenty's functionality with APIs, webhooks, and custom apps.
title: Ampliar
description: Amplía la funcionalidad de Twenty con APIs, webhooks y aplicaciones personalizadas.
---
<Frame>
<img src="/images/user-guide/integrations/plug.png" alt="AI" />
<img src="/images/user-guide/integrations/plug.png" alt="IA" />
</Frame>
## Overview
## Resumen
Twenty is designed to be extensible. Use our APIs, webhooks, and app framework to integrate with your existing tools and build custom functionality.
Twenty está diseñado para ser extensible. Usa nuestras APIs, webhooks y el framework de aplicaciones para integrarte con tus herramientas existentes y crear funcionalidades personalizadas.
## What You Can Do
## Lo que puedes hacer
* **APIs**: Query and modify your CRM data programmatically using REST or GraphQL
* **Webhooks**: Receive real-time notifications when events occur in Twenty
* **Apps**: Build custom applications that extend Twenty's capabilities - Coming soon!
* **APIs**: Consulta y modifica tus datos de CRM de forma programática usando REST o GraphQL
* **Webhooks**: Recibe notificaciones en tiempo real cuando ocurran eventos en Twenty
* **Aplicaciones**: Crea aplicaciones personalizadas que amplíen las capacidades de Twenty - Próximamente.
## Getting Started
## Primeros pasos
<CardGroup cols={2}>
<Card title="APIs" icon="code" href="/l/es/developers/extend/capabilities/apis">
Connect to Twenty programmatically
<Card title="APIs" icon="código" href="/l/es/developers/extend/capabilities/apis">
Conéctate a Twenty de forma programática
</Card>
<Card title="Webhooks" icon="bell" href="/l/es/developers/extend/capabilities/webhooks">
Get notified of events in real-time
Recibe notificaciones de eventos en tiempo real
</Card>
<Card title="Apps" icon="puzzle-piece" href="/l/es/developers/extend/capabilities/apps">
Build customizations as code (Alpha)
<Card title="Aplicaciones" icon="puzzle-piece" href="/l/es/developers/extend/capabilities/apps">
Crea personalizaciones como código (Alpha)
</Card>
</CardGroup>
@@ -1,23 +1,23 @@
---
title: Getting Started
description: Welcome to Twenty Developer Documentation, your resources for extending, self-hosting, and contributing to Twenty.
title: Primeros pasos
description: Bienvenido a la documentación para desarrolladores de Twenty, tus recursos para ampliar, autoalojar y contribuir a Twenty.
---
import { CardTitle } from "/snippets/card-title.mdx"
<CardGroup cols={3}>
<Card href="/l/es/developers/extend/extend" img="/images/user-guide/integrations/plug.png">
<CardTitle>Extend</CardTitle>
Build integrations with APIs, webhooks, and custom apps.
<CardTitle>Ampliar</CardTitle>
Crea integraciones con APIs, webhooks y aplicaciones personalizadas.
</Card>
<Card href="/l/es/developers/self-host/self-host" img="/images/user-guide/what-is-twenty/20.png">
<CardTitle>Self-Host</CardTitle>
Deploy and manage Twenty on your own infrastructure.
<CardTitle>Autoalojar</CardTitle>
Despliega y administra Twenty en tu propia infraestructura.
</Card>
<Card href="/l/es/developers/contribute/contribute" img="/images/user-guide/github/github-header.png">
<CardTitle>Contribute</CardTitle>
Join our open-source community and contribute to Twenty.
<CardTitle>Contribuir</CardTitle>
Únete a nuestra comunidad de código abierto y contribuye a Twenty.
</Card>
</CardGroup>
@@ -1,45 +1,45 @@
---
title: Other methods
title: Otros métodos
---
<Warning>
This document is maintained by the community. It might contain issues.
Este documento es mantenido por la comunidad. Podría contener problemas.
</Warning>
## Kubernetes via Terraform and Manifests
## Kubernetes vía Terraform y Manifests
Community-led documentation for Kubernetes deployment is available [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
Documentación comunitaria para la implementación de Kubernetes está disponible [aquí](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)
### Coolify
Deploy Twenty on servers using Coolify. (official image on Coolify will be available soon)
Despliega Twenty en servidores usando Coolify. (la imagen oficial en Coolify estará disponible pronto)
[Coolify documentation](https://coolify.io/docs/get-started/introduction)
[Documentación de Coolify](https://coolify.io/docs/get-started/introduction)
### EasyPanel
Deploy Twenty on EasyPanel with the community maintained template below.
Despliega Twenty en EasyPanel con la plantilla mantenida por la comunidad a continuación.
[Deploy on EasyPanel](https://easypanel.io/docs/templates/twenty)
[Desplegar en EasyPanel](https://easypanel.io/docs/templates/twenty)
### Elest.io
Deploy Twenty on servers with Elest.io using link below.
Despliega Twenty en servidores con Elest.io utilizando el enlace a continuación.
[Deploy on Elest.io](https://elest.io/open-source/twenty)
[Desplegar en Elest.io](https://elest.io/open-source/twenty)
### Twenty on Railway
### Twenty en Railway
Deploy Twenty on Railway with the community maintained template below.
Despliega Twenty en Railway con la plantilla mantenida por la comunidad a continuación.
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/nAL3hA)
[![Desplegar en Railway](https://railway.com/button.svg)](https://railway.com/deploy/nAL3hA)
### Twenty on Sealos
### Twenty en Sealos
Deploy Twenty on Sealos with the community maintained template below.
Despliega Twenty en Sealos con la plantilla mantenida por la comunidad a continuación.
[![Deploy on Sealos](https://sealos.io/Deploy-on-Sealos.svg)](https://sealos.io/products/app-store/twenty)
[![Desplegar en Sealos](https://sealos.io/Deploy-on-Sealos.svg)](https://sealos.io/products/app-store/twenty)
## Others
## Otros
Please feel free to Open a PR to add more Cloud Provider options.
No dudes en abrir un PR para añadir más opciones de proveedores de la nube.
@@ -1,253 +1,253 @@
---
title: 1-Click w/ Docker Compose
title: 1-Clic con Docker Compose
---
<Warning>
Docker containers are for production hosting or self-hosting, for the contribution please check the [Local Setup](/l/es/developers/contribute/capabilities/local-setup).
Los contenedores de Docker son para alojamiento en producción o autoalojamiento, para la contribución por favor revise la [Configuración Local](/l/es/developers/contribute/capabilities/local-setup).
</Warning>
## Overview
## Resumen
This guide provides step-by-step instructions to install and configure the Twenty application using Docker Compose. The aim is to make the process straightforward and prevent common pitfalls that could break your setup.
Esta guía proporciona instrucciones paso a paso para instalar y configurar la aplicación Twenty utilizando Docker Compose. El objetivo es simplificar el proceso y prevenir errores comunes que podrían arruinar tu configuración.
**Important:** Only modify settings explicitly mentioned in this guide. Altering other configurations may lead to issues.
**Importante:** Solo modifica configuraciones explícitamente mencionadas en esta guía. Alterar otras configuraciones puede causar problemas.
See docs [Setup Environment Variables](/l/es/developers/self-host/capabilities/setup) for advanced configuration. All environment variables must be declared in the docker-compose.yml file at the server and / or worker level depending on the variable.
Consulta los documentos [Configurar Variables de Entorno](/l/es/developers/self-host/capabilities/setup) para configuraciones avanzadas. Todas las variables de entorno deben ser declaradas en el archivo docker-compose.yml en el nivel del servidor y/o trabajador dependiendo de la variable.
## System Requirements
## Requisitos del sistema
* RAM: Ensure your environment has at least 2GB of RAM. Insufficient memory can cause processes to crash.
* Docker & Docker Compose: Make sure both are installed and up-to-date.
* RAM: Asegúrate de que tu entorno tenga al menos 2GB de RAM. La memoria insuficiente puede causar que los procesos se bloqueen.
* Docker & Docker Compose: Asegúrate de que ambos estén instalados y actualizados.
## Option 1: One-line script
## Opción 1: Script de una línea
Install the latest stable version of Twenty with a single command:
Instala la última versión estable de Twenty con un solo comando:
```bash
bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
To install a specific version or branch:
Para instalar una versión o rama específica:
```bash
VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
* Replace x.y.z with the desired version number.
* Replace branch-name with the name of the branch you want to install.
* Reemplace x.y.z con el número de versión deseado.
* Reemplace branch-name con el nombre de la rama que desea instalar.
## Option 2: Manual steps
## Opción 2: Pasos manuales
Follow these steps for a manual setup.
Sigue estos pasos para una configuración manual.
### Step 1: Set Up the Environment File
### Paso 1: Configurar el archivo de entorno
1. **Create the .env File**
1. **Crea el archivo .env**
Copy the example environment file to a new .env file in your working directory:
Copia el archivo de entorno de ejemplo a tu directorio de trabajo a un nuevo archivo .env:
```bash
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Generate Secret Tokens**
2. **Generar tokens secretos**
Run the following command to generate a unique random string:
Ejecuta el siguiente comando para generar una cadena única aleatoria:
```bash
openssl rand -base64 32
```
**Important:** Keep this value secret / do not share it.
**Importante:** Mantén este valor en secreto / no lo compartas.
3. **Update the `.env`**
3. **Actualiza el `.env`**
Replace the placeholder value in your .env file with the generated token:
Reemplaza el valor de marcador de posición en tu archivo .env con el token generado:
```ini
APP_SECRET=first_random_string
```
4. **Set the Postgres Password**
4. **Establecer la contraseña de Postgres**
Update the `PG_DATABASE_PASSWORD` value in the .env file with a strong password without special characters.
Actualiza el valor de `PG_DATABASE_PASSWORD` en el archivo .env con una contraseña fuerte sin caracteres especiales.
```ini
PG_DATABASE_PASSWORD=my_strong_password
```
### Step 2: Obtain the Docker Compose File
### Paso 2: Obtener el archivo Docker Compose
Download the `docker-compose.yml` file to your working directory:
Descarga el archivo `docker-compose.yml` en tu directorio de trabajo:
```bash
curl -o docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/docker-compose.yml
```
### Step 3: Launch the Application
### Paso 3: Lanza la aplicación
Start the Docker containers:
Inicia los contenedores Docker:
```bash
docker compose up -d
```
### Step 4: Access the Application
### Paso 4: Acceder a la aplicación
If you host twentyCRM on your own computer, open your browser and navigate to [http://localhost:3000](http://localhost:3000).
Si alojas twentyCRM en tu propia computadora, abre tu navegador y navega a [http://localhost:3000](http://localhost:3000).
If you host it on a server, check that the server is running and that everything is ok with
Si lo alojas en un servidor, verifica que el servidor esté en funcionamiento y que todo esté bien con
```bash
curl http://localhost:3000
```
## Configuration
## Configuración
### Expose Twenty to External Access
### Exponer Twenty para acceso externo
By default, Twenty runs on `localhost` at port `3000`. To access it via an external domain or IP address, you need to configure the `SERVER_URL` in your `.env` file.
Por defecto, Twenty se ejecuta en `localhost` en el puerto `3000`. Para acceder a él mediante un dominio externo o dirección IP, necesitas configurar `SERVER_URL` en tu archivo `.env`.
#### Understanding `SERVER_URL`
#### Entendiendo `SERVER_URL`
* **Protocol:** Use `http` or `https` depending on your setup.
* Use `http` if you haven't set up SSL.
* Use `https` if you have SSL configured.
* **Domain/IP:** This is the domain name or IP address where your application is accessible.
* **Port:** Include the port number if you're not using the default ports (`80` for `http`, `443` for `https`).
* **Protocolo:** Usa `http` o `https` dependiendo de tu configuración.
* Usa `http` si no has configurado SSL.
* Usa `https` si tienes SSL configurado.
* **Dominio/IP:** Este es el nombre de dominio o dirección IP donde tu aplicación es accesible.
* **Puerto:** Incluye el número de puerto si no estás usando los puertos predeterminados (`80` para `http`, `443` para `https`).
### SSL Requirements
### Requisitos de SSL
SSL (HTTPS) is required for certain browser features to work properly. While these features might work during local development (as browsers treat localhost differently), a proper SSL setup is needed when hosting Twenty on a regular domain.
SSL (HTTPS) es requerido para que ciertas características del navegador funcionen correctamente. Aunque estas características podrían funcionar durante el desarrollo local (ya que los navegadores tratan localhost de manera diferente), se requiere una configuración SSL adecuada al alojar Twenty en un dominio regular.
For example, the clipboard API might require a secure context - some features like copy buttons throughout the application might not work without HTTPS enabled.
Por ejemplo, es posible que la API del portapapeles requiera un contexto seguro: algunas características como los botones de copia en toda la aplicación pueden no funcionar sin HTTPS habilitado.
We strongly recommend setting up Twenty behind a reverse proxy with SSL termination for optimal security and functionality.
Recomendamos encarecidamente configurar Twenty detrás de un proxy inverso con terminación SSL para una seguridad y funcionalidad óptimas.
#### Configuring `SERVER_URL`
#### Configurando `SERVER_URL`
1. **Determine Your Access URL**
* **Without Reverse Proxy (Direct Access):**
1. **Determine su URL de acceso**
* **Sin proxy inverso (Acceso directo):**
If you're accessing the application directly without a reverse proxy:
Si estás accediendo a la aplicación directamente sin un proxy inverso:
```ini
SERVER_URL=http://your-domain-or-ip:3000
```
* **With Reverse Proxy (Standard Ports):**
* **Con proxy inverso (Puertos estándar):**
If you're using a reverse proxy like Nginx or Traefik and have SSL configured:
Si estás usando un proxy inverso como Nginx o Traefik y tienes SSL configurado:
```ini
SERVER_URL=https://your-domain-or-ip
```
* **With Reverse Proxy (Custom Ports):**
* **Con proxy inverso (Puertos personalizados):**
If you're using non-standard ports:
Si estás usando puertos no estándar:
```ini
SERVER_URL=https://your-domain-or-ip:custom-port
```
2. **Update the `.env` File**
2. **Actualiza el archivo `.env`**
Open your `.env` file and update the `SERVER_URL`:
Abre tu archivo `.env` y actualiza el `SERVER_URL`:
```ini
SERVER_URL=http(s)://your-domain-or-ip:your-port
```
**Examples:**
**Ejemplos:**
* Direct access without SSL:
* Acceso directo sin SSL:
```ini
SERVER_URL=http://123.45.67.89:3000
```
* Access via domain with SSL:
* Acceso vía dominio con SSL:
```ini
SERVER_URL=https://mytwentyapp.com
```
3. **Restart the Application**
3. **Reiniciar la aplicación**
For changes to take effect, restart the Docker containers:
Para que los cambios surtan efecto, reinicia los contenedores Docker:
```bash
docker compose down
docker compose up -d
```
#### Considerations
#### Consideraciones
* **Reverse Proxy Configuration:**
* **Configuración del Proxy Inverso:**
Ensure your reverse proxy forwards requests to the correct internal port (`3000` by default). Configure SSL termination and any necessary headers.
Asegúrese de que su proxy inverso envíe las solicitudes al puerto interno correcto (`3000` por defecto). Configure la terminación SSL y cualquier cabecera necesaria.
* **Firewall Settings:**
* **Configuración del Cortafuegos:**
Open necessary ports in your firewall to allow external access.
Abra los puertos necesarios en su cortafuegos para permitir el acceso externo.
* **Consistency:**
* **Consistencia:**
The `SERVER_URL` must match how users access your application in their browsers.
La `SERVER_URL` debe coincidir con cómo los usuarios acceden a su aplicación en sus navegadores.
#### Persistence
#### Persistencia
* **Data Volumes:**
* **Volúmenes de Datos:**
The Docker Compose configuration uses volumes to persist data for the database and server storage.
La configuración de Docker Compose utiliza volúmenes para persistir datos para la base de datos y el almacenamiento del servidor.
* **Stateless Environments:**
* **Entornos Sin Estado:**
If deploying to a stateless environment (e.g., certain cloud services), configure external storage to persist data.
Si se despliega en un entorno sin estado (por ejemplo, ciertos servicios en la nube), configure un almacenamiento externo para persistir los datos.
## Backup and Restore
## Copia de seguridad y restauración
Regular backups protect your CRM data from loss.
Las copias de seguridad periódicas protegen los datos de tu CRM contra la pérdida.
### Create a Database Backup
### Crea una copia de seguridad de la base de datos
```bash
docker exec twenty-postgres pg_dump -U postgres twenty > backup_$(date +%Y%m%d).sql
```
### Automate Daily Backups
### Automatiza las copias de seguridad diarias
Add to your crontab (`crontab -e`):
Añade a tu crontab (`crontab -e`):
```bash
0 2 * * * docker exec twenty-postgres pg_dump -U postgres twenty > /backups/twenty_$(date +\%Y\%m\%d).sql
```
### Restore from Backup
### Restaura desde una copia de seguridad
1. Stop the application:
1. Detén la aplicación:
```bash
docker compose stop twenty-server twenty-front
```
2. Restore the database:
2. Restaura la base de datos:
```bash
docker exec -i twenty-postgres psql -U postgres twenty < backup_20240115.sql
```
3. Restart services:
3. Reinicia los servicios:
```bash
docker compose up -d
```
### Backup Best Practices
### Mejores prácticas de copias de seguridad
* **Test restores regularly** — verify backups actually work
* **Store backups off-site** — use cloud storage (S3, GCS, etc.)
* **Encrypt sensitive data** — protect backups with encryption
* **Retain multiple copies** — keep daily, weekly, and monthly backups
* **Prueba las restauraciones con regularidad** — verifica que las copias de seguridad realmente funcionen
* **Almacena las copias de seguridad fuera del sitio** — usa almacenamiento en la nube (S3, GCS, etc.)
* **Cifra los datos sensibles** — protege las copias de seguridad con cifrado
* **Conserva múltiples copias** — mantén copias de seguridad diarias, semanales y mensuales
## Troubleshooting
## Solución de Problemas
If you encounter any problem, check [Troubleshooting](/l/es/developers/self-host/capabilities/troubleshooting) for solutions.
Si encuentras algún problema, consulta [Solución de Problemas](/l/es/developers/self-host/capabilities/troubleshooting) para ver soluciones.
@@ -1,146 +1,146 @@
---
title: Setup
title: Configuración
---
# Configuration Management
# Gestión de Configuración
<Warning>
**First time installing?** Follow the [Docker Compose installation guide](/l/es/developers/self-host/capabilities/docker-compose) to get Twenty running, then return here for configuration.
**¿Instalando por primera vez?** Siga la [guía de instalación de Docker Compose](/l/es/developers/self-host/capabilities/docker-compose) para ejecutar Twenty, luego regrese aquí para la configuración.
</Warning>
Twenty offers **two configuration modes** to suit different deployment needs:
Twenty ofrece **dos modos de configuración** para adaptarse a diferentes necesidades de implementación:
**Admin panel access:** Only users with admin privileges (`canAccessFullAdminPanel: true`) can access the configuration interface.
**Acceso al panel de administración:** Solo los usuarios con privilegios de administrador (`canAccessFullAdminPanel: true`) pueden acceder a la interfaz de configuración.
## 1. Admin Panel Configuration (Default)
## 1. Configuración del Panel de Administración (Predeterminado)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**Most configuration happens through the UI** after installation:
**La mayoría de las configuraciones se realizan a través de la interfaz** después de la instalación:
1. Access your Twenty instance (usually `http://localhost:3000`)
2. Go to **Settings / Admin Panel / Configuration Variables**
3. Configure integrations, email, storage, and more
4. Changes take effect immediately (within 15 seconds for multi-container deployments)
1. Acceda a su instancia de Twenty (normalmente `http://localhost:3000`)
2. Vaya a **Configuración / Panel de Administración / Variables de Configuración**
3. Configure integraciones, correo electrónico, almacenamiento y más
4. Los cambios se aplican inmediatamente (dentro de 15 segundos para implementaciones multicontenedor)
<Warning>
**Multi-Container Deployments:** When using database configuration (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`), both server and worker containers read from the same database. Admin panel changes affect both automatically, eliminating the need to duplicate environment variables between containers (except for infrastructure variables).
**Implementaciones Multicontenedor:** Al usar la configuración de base de datos (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`), tanto los contenedores del servidor como los de trabajo leen de la misma base de datos. Los cambios en el panel de administración afectan a ambos automáticamente, eliminando la necesidad de duplicar las variables de entorno entre contenedores (excepto para las variables de infraestructura).
</Warning>
**What you can configure through the admin panel:**
**Qué se puede configurar a través del panel de administración:**
* **Authentication** - Google/Microsoft OAuth, password settings
* **Email** - SMTP settings, templates, verification
* **Storage** - S3 configuration, local storage paths
* **Integrations** - Gmail, Google Calendar, Microsoft services
* **Workflow & Rate Limiting** - Execution limits, API throttling
* **And much more...**
* **Autenticación** - OAuth de Google/Microsoft, configuración de contraseñas
* **Correo Electrónico** - Configuración de SMTP, plantillas, verificación
* **Almacenamiento** - Configuración S3, rutas de almacenamiento local
* **Integraciones** - Gmail, Google Calendar, servicios de Microsoft
* **Flujo de Trabajo y Limitación de Tasas** - Límites de ejecución, restricción de API
* **Y mucho más...**
![Admin Panel Configuration Variables](/images/user-guide/setup/admin-panel-config-variables.png)
![Variables de Configuración del Panel de Administración](/images/user-guide/setup/admin-panel-config-variables.png)
<Warning>
Each variable is documented with descriptions in your admin panel at **Settings → Admin Panel → Configuration Variables**.
Some infrastructure settings like database connections (`PG_DATABASE_URL`), server URLs (`SERVER_URL`), and app secrets (`APP_SECRET`) can only be configured via `.env` file.
Cada variable está documentada con descripciones en su panel de administración en **Configuración → Panel de Administración → Variables de Configuración**.
Algunas configuraciones de infraestructura como las conexiones de base de datos (`PG_DATABASE_URL`), URLs del servidor (`SERVER_URL`), y secretos de la aplicación (`APP_SECRET`) solo se pueden configurar a través del archivo `.env`.
[Complete technical reference →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
[Referencia técnica completa →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## 2. Environment-Only Configuration
## 2. Configuración Solo de Entorno
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
```
**All configuration managed through `.env` files:**
**Toda la configuración se gestiona a través de archivos `.env`:**
1. Set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` in your `.env` file
2. Add all configuration variables to your `.env` file
3. Restart containers for changes to take effect
4. Admin panel will show current values but cannot modify them
1. Establezca `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` en su archivo `.env`
2. Agregue todas las variables de configuración a su archivo `.env`
3. Reinicie los contenedores para que los cambios tengan efecto
4. El panel de administración mostrará los valores actuales pero no podrá modificarlos
## Multi-Workspace Mode
## Modo de múltiples espacios de trabajo
By default, Twenty runs in **single-workspace mode** — ideal for most self-hosted deployments where you need one CRM instance for your organization.
De forma predeterminada, Twenty se ejecuta en **modo de un solo espacio de trabajo** — ideal para la mayoría de las implementaciones autoalojadas en las que necesitas una instancia de CRM para tu organización.
### Single-Workspace Mode (Default)
### Modo de un solo espacio de trabajo (predeterminado)
```bash
IS_MULTIWORKSPACE_ENABLED=false # default
```
* One workspace per Twenty instance
* First user automatically becomes admin with full privileges (`canImpersonate` and `canAccessFullAdminPanel`)
* New signups are disabled after the first workspace is created
* Simple URL structure: `https://your-domain.com`
* Un espacio de trabajo por instancia de Twenty
* El primer usuario se convierte automáticamente en administrador con privilegios completos (`canImpersonate` y `canAccessFullAdminPanel`)
* Los nuevos registros se deshabilitan después de crear el primer espacio de trabajo
* Estructura de URL simple: `https://your-domain.com`
### Enabling Multi-Workspace Mode
### Habilitar el modo de múltiples espacios de trabajo
```bash
IS_MULTIWORKSPACE_ENABLED=true
DEFAULT_SUBDOMAIN=app # default value
```
Enable multi-workspace mode for SaaS-like deployments where multiple independent teams need their own workspaces on the same Twenty instance.
Habilita el modo de múltiples espacios de trabajo para implementaciones tipo SaaS en las que varios equipos independientes necesitan sus propios espacios de trabajo en la misma instancia de Twenty.
**Key differences from single-workspace mode:**
**Diferencias clave respecto al modo de un solo espacio de trabajo:**
* Multiple workspaces can be created on the same instance
* Each workspace gets its own subdomain (e.g., `sales.your-domain.com`, `marketing.your-domain.com`)
* Users sign up and log in at `{DEFAULT_SUBDOMAIN}.your-domain.com` (e.g., `app.your-domain.com`)
* No automatic admin privileges — first user in each workspace is a regular user
* Workspace-specific settings like subdomain and custom domain become available in workspace settings
* Se pueden crear varios espacios de trabajo en la misma instancia
* Cada espacio de trabajo obtiene su propio subdominio (p. ej., `sales.your-domain.com`, `marketing.your-domain.com`)
* Los usuarios se registran e inician sesión en `{DEFAULT_SUBDOMAIN}.your-domain.com` (p. ej., `app.your-domain.com`)
* Sin privilegios de administrador automáticos — el primer usuario de cada espacio de trabajo es un usuario normal
* Configuraciones específicas del espacio de trabajo, como subdominio y dominio personalizado, están disponibles en la configuración del espacio de trabajo
<Warning>
**Environment-only setting:** `IS_MULTIWORKSPACE_ENABLED` can only be configured via `.env` file and requires a restart. It cannot be changed through the admin panel.
**Configuración solo por entorno:** `IS_MULTIWORKSPACE_ENABLED` solo se puede configurar mediante el archivo `.env` y requiere un reinicio. No se puede cambiar a través del panel de administración.
</Warning>
### DNS Configuration for Multi-Workspace
### Configuración de DNS para múltiples espacios de trabajo
When using multi-workspace mode, configure your DNS with a wildcard record to allow dynamic subdomain creation:
Al usar el modo de múltiples espacios de trabajo, configura tu DNS con un registro comodín para permitir la creación dinámica de subdominios:
```
*.your-domain.com -> your-server-ip
```
This enables automatic subdomain routing for new workspaces without manual DNS configuration.
Esto habilita el enrutamiento automático de subdominios para nuevos espacios de trabajo sin configuración manual de DNS.
### Restricting Workspace Creation
### Restricción de la creación de espacios de trabajo
In multi-workspace mode, you may want to limit who can create new workspaces:
En el modo de múltiples espacios de trabajo, es posible que quieras limitar quién puede crear nuevos espacios de trabajo:
```bash
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
```
When enabled, only users with `canAccessFullAdminPanel` can create additional workspaces. Users can still create their first workspace during initial signup.
Cuando está habilitado, solo los usuarios con `canAccessFullAdminPanel` pueden crear espacios de trabajo adicionales. Los usuarios aún pueden crear su primer espacio de trabajo durante el registro inicial.
## Gmail & Google Calendar Integration
## Integración con Gmail y Google Calendar
### Create Google Cloud Project
### Crear Proyecto en Google Cloud
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing one
3. Enable these APIs:
1. Vaya a [Google Cloud Console](https://console.cloud.google.com/)
2. Cree un nuevo proyecto o seleccione uno existente
3. Habilite estas APIs:
* [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)
* [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)
* [People API](https://console.cloud.google.com/apis/library/people.googleapis.com)
### Configure OAuth
### Configurar OAuth
1. Go to [Credentials](https://console.cloud.google.com/apis/credentials)
2. Create OAuth 2.0 Client ID
3. Add these redirect URIs:
* `https://{your-domain}/auth/google/redirect` (for SSO)
* `https://{your-domain}/auth/google-apis/get-access-token` (for integrations)
1. Vaya a [Credenciales](https://console.cloud.google.com/apis/credentials)
2. Cree un ID de Cliente OAuth 2.0
3. Agregue estas URIs de redirección:
* `https://{your-domain}/auth/google/redirect` (para SSO)
* `https://{your-domain}/auth/google-apis/get-access-token` (para integraciones)
### Configure in Twenty
### Configurar en Twenty
1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Google Auth** section
3. Set these variables:
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
2. Encuentre la sección **Google Auth**
3. Establezca estas variables:
* `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
* `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
* `AUTH_GOOGLE_CLIENT_ID={client-id}`
@@ -149,35 +149,35 @@ When enabled, only users with `canAccessFullAdminPanel` can create additional wo
* `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
</Warning>
**Required scopes** (automatically configured):
[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
**Ámbitos requeridos** (configurados automáticamente):
[Ver código fuente relevante](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
* `https://www.googleapis.com/auth/calendar.events`
* `https://www.googleapis.com/auth/gmail.readonly`
* `https://www.googleapis.com/auth/profile.emails.read`
### If your app is in test mode
### Si su aplicación está en modo de prueba
If your app is in test mode, you will need to add test users to your project.
Si su aplicación está en modo de prueba, deberá agregar usuarios de prueba a su proyecto.
Under [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent), add your test users to the "Test users" section.
En [Pantalla de consentimiento OAuth](https://console.cloud.google.com/apis/credentials/consent), agregue sus usuarios de prueba en la sección "Usuarios de prueba".
## Microsoft 365 Integration
## Integración con Microsoft 365
<Warning>
Users must have a [Microsoft 365 Licence](https://admin.microsoft.com/Adminportal/Home) to be able to use the Calendar and Messaging API. They will not be able to sync their account on Twenty without one.
Los usuarios deben tener una [licencia de Microsoft 365](https://admin.microsoft.com/Adminportal/Home) para poder usar la API de Calendar y Messaging. No podrán sincronizar su cuenta en Twenty sin una.
</Warning>
### Create a project in Microsoft Azure
### Cree un proyecto en Microsoft Azure
You will need to create a project in [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) and get the credentials.
Necesitará crear un proyecto en [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) y obtener las credenciales.
### Enable APIs
### Habilitar APIs
On Microsoft Azure Console enable the following APIs in "Permissions":
En la Consola de Microsoft Azure habilite las siguientes APIs en "Permisos":
* Microsoft Graph: Mail.ReadWrite
* Microsoft Graph: Mail.Send
@@ -188,20 +188,20 @@ On Microsoft Azure Console enable the following APIs in "Permissions":
* Microsoft Graph: profile
* Microsoft Graph: offline_access
Note: "Mail.ReadWrite" and "Mail.Send" are only mandatory if you want to send emails using our workflow actions. You can use "Mail.Read" instead if you only want to receive emails.
Nota: "Mail.ReadWrite" y "Mail.Send" solo son obligatorios si desea enviar correos electrónicos usando nuestras acciones de flujo de trabajo. Puede usar "Mail.Read" en su lugar si solo desea recibir correos electrónicos.
### Authorized redirect URIs
### URIs de redirección autorizadas
You need to add the following redirect URIs to your project:
Necesita agregar las siguientes URIs de redirección a su proyecto:
* `https://{your-domain}/auth/microsoft/redirect` if you want to use Microsoft SSO
* `https://{your-domain}/auth/microsoft/redirect` si quieres usar el inicio de sesión único de Microsoft
* `https://{your-domain}/auth/microsoft-apis/get-access-token`
### Configure in Twenty
### Configurar en Twenty
1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Microsoft Auth** section
3. Set these variables:
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
2. Encuentre la sección **Microsoft Auth**
3. Establezca estas variables:
* `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
* `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
* `AUTH_MICROSOFT_ENABLED=true`
@@ -211,32 +211,32 @@ You need to add the following redirect URIs to your project:
* `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
</Warning>
### Configure scopes
### Configurar ámbitos
[See relevant source code](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
[Ver código fuente relevante](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
* 'openid'
* 'email'
* 'profile'
* 'correo Electrónico'
* 'perfil'
* 'offline_access'
* 'Mail.ReadWrite'
* 'Mail.Send'
* 'Calendars.Read'
### If your app is in test mode
### Si su aplicación está en modo de prueba
If your app is in test mode, you will need to add test users to your project.
Si su aplicación está en modo de prueba, deberá agregar usuarios de prueba a su proyecto.
Add your test users to the "Users and groups" section.
Agregue sus usuarios de prueba a la sección "Usuarios y grupos".
## Background Jobs for Calendar & Messaging
## Trabajos en segundo plano para Calendarios y Mensajes
After configuring Gmail, Google Calendar, or Microsoft 365 integrations, you need to start the background jobs that sync data.
Después de configurar las integraciones de Gmail, Google Calendar, o Microsoft 365, necesita iniciar los trabajos en segundo plano que sincronizan los datos.
Register the following recurring jobs in your worker container:
Registre los siguientes trabajos recurrentes en su contenedor de trabajo:
```bash
# from your worker container
@@ -249,15 +249,15 @@ yarn command:prod cron:calendar:ongoing-stale
yarn command:prod cron:workflow:automated-cron-trigger
```
## Email Configuration
## Configuración de Correo Electrónico
1. Go to **Settings → Admin Panel → Configuration Variables**
2. Find the **Email** section
3. Configure your SMTP settings:
1. Vaya a **Configuración → Panel de Administración → Variables de Configuración**
2. Encuentre la sección **Correo Electrónico**
3. Configure su configuración SMTP:
<ArticleTabs label1="Gmail" label2="Office365" label3="Smtp4dev">
<ArticleTab>
You will need to provision an [App Password](https://support.google.com/accounts/answer/185833).
Necesitará proporcionar una [Contraseña de Aplicación](https://support.google.com/accounts/answer/185833).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.gmail.com
@@ -267,7 +267,7 @@ yarn command:prod cron:workflow:automated-cron-trigger
</ArticleTab>
<ArticleTab>
Keep in mind that if you have 2FA enabled, you will need to provision an [App Password](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
Tenga en cuenta que si tiene la autenticación de dos factores habilitada, necesitará proporcionar una [Contraseña de Aplicación](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.office365.com
@@ -277,11 +277,11 @@ yarn command:prod cron:workflow:automated-cron-trigger
</ArticleTab>
<ArticleTab>
**smtp4dev** is a fake SMTP email server for development and testing.
**smtp4dev** es un servidor de correo SMTP falso para desarrollo y pruebas.
* Run the smtp4dev image: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* Access the smtp4dev ui here: [http://localhost:8090](http://localhost:8090)
* Set the following variables:
* Ejecute la imagen de smtp4dev: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* Acceda a la interfaz de smtp4dev aquí: [http://localhost:8090](http://localhost:8090)
* Establezca las siguientes variables:
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=localhost
* EMAIL_SMTP_PORT=2525
@@ -289,5 +289,49 @@ yarn command:prod cron:workflow:automated-cron-trigger
</ArticleTabs>
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
**Modo solo de entorno:** Si establece `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, agregue estas variables a su archivo `.env` en su lugar.
</Warning>
## Funciones de lógica
Twenty admite funciones de lógica para flujos de trabajo y lógica personalizada. El entorno de ejecución se configura mediante la variable de entorno `SERVERLESS_TYPE`.
<Warning>
**Aviso de seguridad:** El controlador local (`SERVERLESS_TYPE=LOCAL`) ejecuta código directamente en el host en un proceso de Node.js sin aislamiento. Solo debe utilizarse para código de confianza en desarrollo. Para implementaciones de producción que manejen código no confiable, recomendamos encarecidamente usar `SERVERLESS_TYPE=LAMBDA` o `SERVERLESS_TYPE=DISABLED`.
</Warning>
### Controladores disponibles
| Controlador | Variable de entorno | Caso de uso | Nivel de seguridad |
| ----------- | -------------------------- | ------------------------------------------------ | -------------------------------------- |
| Desactivado | `SERVERLESS_TYPE=DISABLED` | Desactivar completamente las funciones de lógica | N/A |
| Local | `SERVERLESS_TYPE=LOCAL` | Entornos de desarrollo y de confianza | Bajo (sin aislamiento) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Producción con código no confiable | Alto (aislamiento a nivel de hardware) |
### Configuración recomendada
**Para desarrollo:**
```bash
SERVERLESS_TYPE=LOCAL # default
```
**Para producción (AWS):**
```bash
SERVERLESS_TYPE=LAMBDA
SERVERLESS_LAMBDA_REGION=us-east-1
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**Para desactivar las funciones de lógica:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
Al usar `SERVERLESS_TYPE=DISABLED`, cualquier intento de ejecutar una función de lógica devolverá un error. Esto es útil si desea ejecutar Twenty sin capacidades de funciones de lógica.
</Note>
@@ -1,26 +1,25 @@
---
title: Troubleshooting
title: Solución de problemas
---
## Troubleshooting
## Solución de Problemas
If you encounter any problem while setting up environment for development, upgrading your instance or self-hosting,
here are some solutions for common problems.
Si encuentra algún problema al configurar el entorno para el desarrollo, al actualizar su instancia o al autoalojar, aquí hay algunas soluciones para problemas comunes.
### Self-hosting
### Autoalojamiento
#### First install results in `password authentication failed for user "postgres"`
#### La primera instalación resulta en `fallo de autenticación de contraseña para el usuario "postgres"`
🚨 **IMPORTANT: This solution is ONLY for fresh installations** 🚨
If you have an existing Twenty instance with production data, **DO NOT** follow these steps as they will permanently delete your database!
🚨 **IMPORTANTE: Esta solución es SOLO para instalaciones nuevas** 🚨
Si tiene una instancia de Twenty existente con datos de producción, **NO** siga estos pasos ya que borrarán permanentemente su base de datos.
While installing Twenty for the first time, you might want to change the default database password.
The password you set during the first installation becomes permanently stored in the database volume. If you later try to change this password in your configuration without removing the old volume, you'll get authentication errors because the database is still using the original password.
Al instalar Twenty por primera vez, es posible que desee cambiar la contraseña predeterminada de la base de datos.
La contraseña que establezca durante la primera instalación se almacena permanentemente en el volumen de base de datos. Si más tarde intenta cambiar esta contraseña en su configuración sin eliminar el volumen anterior, obtendrá errores de autenticación porque la base de datos todavía está usando la contraseña original.
⚠️ WARNING: Following steps will PERMANENTLY DELETE all database data! ⚠️
Only proceed if this is a fresh installation with no important data.
⚠️ ADVERTENCIA: ¡Seguir los pasos borrará PERMANENTEMENTE todos los datos de la base de datos! ⚠️
Prosiga solo si se trata de una instalación nueva sin datos importantes.
In order to update the `PG_DATABASE_PASSWORD` you need to:
Para actualizar el `PG_DATABASE_PASSWORD` necesita:
```sh
# Update the PG_DATABASE_PASSWORD in .env
@@ -28,33 +27,33 @@ docker compose down --volumes
docker compose up -d
```
#### CR line breaks found [Windows]
#### Rupturas de línea de CR encontradas [Windows]
This is due to the line break characters of Windows and the git configuration. Try running:
Esto se debe a los caracteres de salto de línea de Windows y a la configuración de Git. Intente ejecutar:
```
git config --global core.autocrlf false
```
Then delete the repository and clone it again.
Luego elimine el repositorio y clónelo de nuevo.
#### Missing metadata schema
#### Esquema de metadatos faltante
During Twenty installation, you need to provision your postgres database with the right schemas, extensions, and users.
If you're successful in running this provisioning, you should have `default` and `metadata` schemas in your database.
If you don't, make sure you don't have more than one postgres instance running on your computer.
Durante la instalación de Twenty, debe aprovisionar su base de datos postgres con los esquemas, extensiones y usuarios correctos.
Si ha ejecutado con éxito este aprovisionamiento, debería tener esquemas `default` y `metadata` en su base de datos.
Si no los tiene, asegúrese de no tener más de una instancia de postgres ejecutándose en su computadora.
#### Cannot find module 'twenty-emails' or its corresponding type declarations.
#### No se puede encontrar el módulo 'twenty-emails' o sus declaraciones de tipo correspondientes.
You have to build the package `twenty-emails` before running the initialization of the database with `npx nx run twenty-emails:build`
Tienes que construir el paquete `twenty-emails` antes de ejecutar la inicialización de la base de datos con `npx nx run twenty-emails:build`
#### Missing twenty-x package
#### Falta el paquete twenty-x
Make sure to run yarn in the root directory and then run `npx nx server:dev twenty-server`. If this still doesn't work try building the missing package manually.
Asegúrese de ejecutar yarn en el directorio raíz y luego ejecute `npx nx server:dev twenty-server`. Si esto aún no funciona, intente construir el paquete faltante manualmente.
#### Lint on Save not working
#### Lint on Save no funciona
This should work out of the box with the eslint extension installed. If this doesn't work try adding this to your vscode setting (on the dev container scope):
Esto debería funcionar sin configuración adicional con la extensión de ESLint instalada. Si esto no funciona, intente agregar esto a su configuración de vscode (en el ámbito del contenedor de desarrollo):
```
"editor.codeActionsOnSave": {
@@ -64,85 +63,85 @@ This should work out of the box with the eslint extension installed. If this doe
}
```
#### While running `npx nx start` or `npx nx start twenty-front`, Out of memory error is thrown
#### Al ejecutar `npx nx start` o `npx nx start twenty-front`, se produce un error de falta de memoria
In `packages/twenty-front/.env` uncomment `VITE_DISABLE_TYPESCRIPT_CHECKER=true` to disable background checks thus reducing amount of needed RAM.
En `packages/twenty-front/.env` descomente `VITE_DISABLE_TYPESCRIPT_CHECKER=true` y `VITE_DISABLE_ESLINT_CHECKER=true` para deshabilitar las comprobaciones en segundo plano y así reducir la cantidad de RAM necesaria.
**If it does not work:**
Run only the services you need, instead of `npx nx start`. For instance, if you work on the server, run only `npx nx worker twenty-server`
**Si no funciona:**
Ejecute solo los servicios que necesite, en lugar de `npx nx start`. Por ejemplo, si trabaja en el servidor, ejecute solo `npx nx worker twenty-server`
**If it does not work:**
If you tried to run only `npx nx run twenty-server:start` on WSL and it's failing with the below memory error:
**Si no funciona:**
Si intentó ejecutar solo `npx nx run twenty-server:start` en WSL y falla con el siguiente error de memoria:
`FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`
`ERROR FATAL: Las marcas compactas ineficaces cercanas al límite del montón Falló la asignación - JavaScript heap out of memory`
Workaround is to execute below command in terminal or add it in .bashrc profile to get setup automatically:
La solución es ejecutar el siguiente comando en el terminal o agregarlo en el perfil .bashrc para configurarlo automáticamente:
`export NODE_OPTIONS="--max-old-space-size=8192"`
The --max-old-space-size=8192 flag sets an upper limit of 8GB for the Node.js heap; usage scales with application demand.
Reference: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
La bandera --max-old-space-size=8192 establece un límite superior de 8GB para el montón de Node.js; su uso escala con la demanda de la aplicación.
Referencia: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
**If it does not work:**
Investigate which processes are taking you most of your machine RAM. At Twenty, we noticed that some VScode extensions were taking a lot of RAM so we temporarily disable them.
**Si no funciona:**
Investigue qué procesos están ocupando la mayor parte de la RAM de su máquina. En Twenty, notamos que algunas extensiones de VScode estaban ocupando mucha RAM, por lo que las desactivamos temporalmente.
**If it does not work:**
Restart your machine helps to clean up ghost processes.
**Si no funciona:**
Reiniciar su máquina ayuda a limpiar procesos fantasma.
#### While running `npx nx start` there are weird [0] and [1] in logs
#### Mientras ejecuta `npx nx start` hay [0] y [1] extraños en los registros
That's expected as command `npx nx start` is running more commands under the hood
Es esperado, ya que el comando `npx nx start` está ejecutando más comandos detrás de escena.
#### No emails are sent
#### No se envían correos electrónicos
Most of the time, it's because the `worker` is not running in the background. Try to run
La mayoría de las veces, se debe a que el `worker` no se está ejecutando en segundo plano. Intente ejecutar
```
npx nx worker twenty-server
```
#### Cannot connect my Microsoft 365 account
#### No se puede conectar mi cuenta de Microsoft 365
Most of the time, it's because your admin has not enabled the Microsoft 365 Licence for your account. Check [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
La mayoría de las veces, se debe a que su administrador no ha habilitado la licencia de Microsoft 365 para su cuenta. Verifique [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
If you have an error code `AADSTS50020`, it probably means that you are using a personal Microsoft account. This is not supported yet. More info [here](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
Si tiene un código de error `AADSTS50020`, probablemente significa que está usando una cuenta de Microsoft personal. Esto aún no es compatible. Más información [aquí](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
#### While running `yarn` warnings appear in console
#### Mientras ejecuta `yarn` aparecen advertencias en la consola
Warnings are informing about pulling additional dependencies which aren't explicitly stated in `package.json`, so as long as no breaking error appears, everything should work as expected.
Las advertencias informan sobre la carga de dependencias adicionales que no están explicitadas en `package.json`, así que mientras no aparezca un error crítico, todo debería funcionar como se espera.
#### When user accesses login page, error about unauthorized user trying to access workspace appears in logs
#### Cuando el usuario accede a la página de inicio de sesión, aparece un error sobre un usuario no autorizado que intenta acceder al espacio de trabajo en los registros
That's expected as user is unauthorized when logged out since its identity is not verified.
Es esperado ya que el usuario no está autorizado cuando cierra sesión porque su identidad no está verificada.
#### How to check if your worker is running?
#### ¿Cómo comprobar si su worker está funcionando?
* Go to [webhook-test.com](https://webhook-test.com/) and copy **Your Unique Webhook URL**.
* Vaya a [webhook-test.com](https://webhook-test.com/) y copie **Su URL de Webhook Única**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Webhook test" />
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Prueba de webhook" />
</div>
* Open your Twenty app, navigate to `/settings`, and enable the **Advanced** toggle at the bottom left of the screen.
* Create a new webhook.
* Paste **Your Unique Webhook URL** in the **Endpoint Url** field in Twenty. Set the **Filters** to `Companies` and `Created`.
* Abra la aplicación Twenty, navegue a `/settings` y active el interruptor **Avanzado** en la parte inferior izquierda de la pantalla.
* Cree un nuevo webhook.
* Pegue **Su URL de Webhook Única** en el campo **Url de EndPoint** en Twenty. Establezca los **Filtros** en `Companies` y `Created`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Webhook settings" />
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Configuraciones del webhook" />
</div>
* Go to `/objects/companies` and create a new company record.
* Return to [webhook-test.com](https://webhook-test.com/) and check if a new **POST request** has been received.
* Vaya a `/objects/companies` y cree un nuevo registro de empresa.
* Regrese a [webhook-test.com](https://webhook-test.com/) y verifique si se ha recibido una nueva **solicitud POST**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Webhook test result" />
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Resultado de la prueba del webhook" />
</div>
* If a **POST request** is received, your worker is running successfully. Otherwise, you need to troubleshoot your worker.
* Si se recibe una **solicitud POST**, su worker está funcionando con éxito. De lo contrario, debe solucionar problemas de su worker.
#### Front-end fails to start and returns error TS5042: Option 'project' cannot be mixed with source files on a command line
#### El front-end no comienza y devuelve el error TS5042: La opción 'project' no se puede mezclar con archivos fuente en una línea de comando
Comment out checker plugin in `packages/twenty-ui/vite-config.ts` like in example below
Comente el plugin checker en `packages/twenty-ui/vite-config.ts` como en el ejemplo a continuación
```
plugins: [
@@ -166,62 +165,62 @@ plugins: [
],
```
#### Admin panel not accessible
#### Panel de administración no accesible
Run `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = '[email protected]';` in database container to get access to admin panel.
Ejecute `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = '[email protected]';` en el contenedor de la base de datos para obtener acceso al panel de administración.
### 1-click Docker compose
### 1-clic con Docker Compose
#### Unable to Log In
#### No se puede iniciar sesión
If you can't log in after setup:
Si no puedes iniciar sesión después de la configuración:
1. Run the following commands:
1. Ejecución de los siguientes comandos:
```bash
docker exec -it twenty-server-1 yarn
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
```
2. Restart the Docker containers:
2. Reinicie los contenedores de Docker:
```bash
docker compose down
docker compose up -d
```
Note the database:reset command will completely erase your database and recreate it from scratch.
Tenga en cuenta que el comando database:reset borrará toda su base de datos y la recreará desde cero.
#### Connection Issues Behind a Reverse Proxy
#### Problemas de conexión detrás de un Proxy Reverso
If you're running Twenty behind a reverse proxy and experiencing connection issues:
Si está ejecutando Twenty detrás de un proxy inverso y experimenta problemas de conexión:
1. **Verify SERVER_URL:**
1. **Verifique SERVER_URL:**
Ensure `SERVER_URL` in your `.env` file matches your external access URL, including `https` if SSL is enabled.
Asegúrese de que `SERVER_URL` en su archivo `.env` coincida con su URL de acceso externa, incluyendo `https` si SSL está habilitado.
2. **Check Reverse Proxy Settings:**
2. **Verifique la configuración del Proxy Reverso:**
* Confirm that your reverse proxy is correctly forwarding requests to the Twenty server.
* Ensure headers like `X-Forwarded-For` and `X-Forwarded-Proto` are properly set.
* Confirme que su proxy reverso está reenviando correctamente las solicitudes al servidor de Twenty.
* Asegúrese de que los encabezados como `X-Forwarded-For` y `X-Forwarded-Proto` estén configurados correctamente.
3. **Restart Services:**
3. **Reinicie los Servicios:**
After making changes, restart both the reverse proxy and Twenty containers.
Después de hacer cambios, reinicie tanto el proxy inverso como los contenedores de Twenty.
#### Error when uploading an image - permission denied
#### Error al cargar una imagen - permiso denegado
Switching the data folder ownership on the host from root to another user and group resolves this problem.
Cambiar la propiedad de la carpeta de datos en el host de raíz a otro usuario y grupo resuelve este problema.
## Getting Help
## Obtención de Ayuda
If you encounter issues not covered in this guide:
Si enfrenta problemas no cubiertos en esta guía:
* Check Logs:
* Verifique los Registros:
View container logs for error messages:
Vea los registros del contenedor por mensajes de error:
```bash
docker compose logs
```
* Community Support:
* Soporte Comunitario:
Reach out to the [Twenty community](https://github.com/twentyhq/twenty/issues) or [support channels](https://discord.gg/cx5n4Jzs57) for assistance.
Póngase en contacto con la [comunidad de Twenty](https://github.com/twentyhq/twenty/issues) o [los canales de soporte](https://discord.gg/cx5n4Jzs57) para obtener asistencia.
@@ -1,40 +1,40 @@
---
title: Upgrade guide
title: Guía de actualización
---
## General guidelines
## Guías generales
**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`.
**Asegúrese siempre de respaldar su base de datos antes de iniciar el proceso de actualización** ejecutando `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`.
To restore backup, run `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`.
Para restaurar el respaldo, ejecute `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`.
If you used Docker Compose, follow these steps:
Si usó Docker Compose, siga estos pasos:
1. In a terminal, on the host where Twenty is running, turn off Twenty: `docker compose down`
1. En una terminal, en el host donde Twenty está funcionando, apague Twenty: `docker compose down`
2. Upgrade the version by changing the `TAG` value in the .env file near your docker-compose. ( We recommend consuming `major.minor` version such as `v0.53` )
2. Actualice la versión cambiando el valor de `TAG` en el archivo .env cerca de su docker-compose. ( Recomendamos consumir la versión `major.minor` como `v0.53` )
3. Bring Twenty back online with `docker compose up -d`
3. Vuelva a conectar Twenty con `docker compose up -d`
If you want to upgrade your instance by few versions, e.g. from v0.33.0 to v0.35.0, you have to upgrade your instance sequentially, in this example from v0.33.0 to v0.34.0, then from v0.34.0 to v0.35.0.
Si desea actualizar su instancia por algunas versiones, por ejemplo de v0.33.0 a v0.35.0, debe actualizar su instancia secuencialmente, en este ejemplo de v0.33.0 a v0.34.0, luego de v0.34.0 a v0.35.0.
**Make sure that after each upgraded version you have non-corrupted backup.**
**Asegúrese de que después de cada versión actualizada tenga una copia de respaldo no corrupta.**
## Version-specific upgrade steps
## Pasos de actualización específicos por versión
## v1.0
Hello Twenty v1.0! 🎉
¡Hola Twenty v1.0! 🎉
## v0.60
### Performance Enhancements
### Mejoras de rendimiento
All interactions with the metadata API have been optimized for better performance, particularly for object metadata manipulation and workspace creation operations.
Todas las interacciones con la API de metadatos han sido optimizadas para un mejor rendimiento, particularmente para la manipulación de metadatos de objetos y operaciones de creación de espacios de trabajo.
We've refactored our caching strategy to prioritize cache hits over database queries when possible, significantly improving the performance of metadata API operations.
Hemos reestructurado nuestra estrategia de almacenamiento en caché para priorizar los aciertos de caché sobre las consultas de base de datos cuando sea posible, mejorando significativamente el rendimiento de las operaciones de la API de metadatos.
If you encounter any runtime issues after upgrading, you may need to flush your cache to ensure it's synchronized with the latest changes. Run this command in your twenty-server container:
Si encuentra problemas de ejecución después de actualizar, es posible que deba vaciar su caché para asegurar que esté sincronizado con los cambios más recientes. Ejecute este comando en su contenedor del servidor de twenty:
```bash
yarn command:prod cache:flush
@@ -42,113 +42,113 @@ yarn command:prod cache:flush
### v0.55
Upgrade your Twenty instance to use v0.55 image
Actualice su instancia de Twenty para usar la imagen v0.55
You don't need to run any command anymore, the new image will automatically care about running all required migrations.
Ya no necesita ejecutar ningún comando, la nueva imagen se encargará automáticamente de ejecutar todas las migraciones necesarias.
### `User does not have permission` error
### Error: `El usuario no tiene permiso`
If you encounter authorization errors on most requests after upgrading, you may need to flush your cache to recompute the latest permissions.
Si encuentra errores de autorización en la mayoría de solicitudes después de actualizar, es posible que deba vaciar su caché para recalcular los permisos más recientes.
In your `twenty-server` container, run:
En su contenedor `twenty-server`, ejecute:
```bash
yarn command:prod cache:flush
```
This issue is specific to this Twenty version and should not be required for future upgrades.
Este problema es específico de esta versión de Twenty y no debería ser necesario para futuras actualizaciones.
### v0.54
Since version `0.53`, no manual actions needed.
Desde la versión `0.53`, no se necesitan acciones manuales.
#### Metadata schema deprecation
#### Desaparición del esquema de metadatos
We've merged the `metadata` schema into the `core` one to simplify data retrieval from `TypeORM`.
We have merged the `migrate` command step within the `upgrade` command. We do not recommend running `migrate` manually within any of your server/worker containers.
Hemos fusionado el esquema `metadata` en el esquema `core` para simplificar la recuperación de datos desde `TypeORM`.
Hemos fusionado el paso del comando `migrate` dentro del comando `upgrade`. No recomendamos ejecutar `migrate` manualmente dentro de ninguno de sus contenedores de servidor/trabajador.
### Since v0.53
### Desde v0.53
Starting from `0.53`, upgrade is programmatically done within the `DockerFile`, this means from now on, you shouldn't have to run any command manually anymore.
A partir de `0.53`, la actualización se realiza de forma programática dentro del `DockerFile`, esto significa que de ahora en adelante, no debería necesitar ejecutar ningún comando manualmente.
Make sure to keep upgrading your instance sequentially, without skipping any major version (e.g. `0.43.3` to `0.44.0` is allowed, but `0.43.1` to `0.45.0` isn't), else could lead to workspace version desynchronization that could result in runtime error and missing functionality.
Asegúrese de seguir actualizando su instancia secuencialmente, sin omitir ninguna versión principal (por ejemplo, de `0.43.3` a `0.44.0` está permitido, pero de `0.43.1` a `0.45.0` no lo está), de lo contrario, podría provocar un desincronización de la versión del espacio de trabajo que podría resultar en errores de ejecución y funciones faltantes.
To check if a workspace has been correctly migrated you can review its version in database in `core.workspace` table.
Para verificar si un espacio de trabajo se ha migrado correctamente, puede revisar su versión en la base de datos en la tabla `core.workspace`.
It should always be in the range of your current Twenty's instance `major.minor` version, you can view your instance version in the admin panel (at `/settings/admin-panel`, accessible if your user has `canAccessFullAdminPanel` property set to true in the database) or by running `echo $APP_VERSION` in your `twenty-server` container.
Siempre debería estar dentro del rango de la versión `major.minor` actual de su instancia de Twenty; puede ver la versión de su instancia en el panel de administración (en `/settings/admin-panel`, accesible si su usuario tiene la propiedad `canAccessFullAdminPanel` establecida en verdadero en la base de datos) o ejecutando `echo $APP_VERSION` en su contenedor `twenty-server`.
To fix a desynchronized workspace version, you will have to upgrade from the corresponding twenty's version following related upgrade guide sequentially and so on until it reaches desired version.
Para corregir una versión de espacio de trabajo desincronizada, tendrá que actualizar desde la correspondiente versión de twenty siguiendo la guía de actualización relacionada secuencialmente, y así sucesivamente hasta alcanzar la versión deseada.
#### `auditLog` removal
#### Eliminación de `auditLog`
We've removed the auditLog standard object, which means your backup size might be significantly reduced after this migration.
Hemos eliminado el objeto estándar auditLog, lo que significa que el tamaño de su copia de seguridad podría reducirse significativamente después de esta migración.
### v0.51 to v0.52
### v0.51 a v0.52
Upgrade your Twenty instance to use v0.52 image
Actualice su instancia de Twenty para usar la imagen v0.52
```
yarn database:migrate:prod
yarn command:prod upgrade
```
#### I have a workspace blocked in version between `0.52.0` and `0.52.6`
#### Tengo un espacio de trabajo bloqueado en la versión entre `0.52.0` y `0.52.6`
Unfortunately `0.52.0` and `0.52.6` have been completely removed from dockerHub.
You will have to manually update your workspace version to `0.51.0` in database and upgrade using twenty version `0.52.11` following its just above upgrade guide.
Desafortunadamente, `0.52.0` y `0.52.6` se han eliminado completamente de dockerHub.
Tendrá que actualizar manualmente la versión de su espacio de trabajo a `0.51.0` en la base de datos y actualizar usando la versión twenty `0.52.11` siguiendo su guía de actualización justo arriba.
### v0.50 to v0.51
### v0.50 a v0.51
Upgrade your Twenty instance to use v0.51 image
Actualice su instancia de Twenty para usar la imagen v0.51
```
yarn database:migrate:prod
yarn command:prod upgrade
```
### v0.44.0 to v0.50.0
### v0.44.0 a v0.50.0
Upgrade your Twenty instance to use v0.50.0 image
Actualice su instancia de Twenty para usar la imagen v0.50.0
```
yarn database:migrate:prod
yarn command:prod upgrade
```
#### Docker-compose.yml mutation
#### Mutación del docker-compose.yml
This version includes a `docker-compose.yml` mutation to give `worker` service access to the `server-local-data` volume.
Please update your local `docker-compose.yml` with [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
Esta versión incluye una mutación del `docker-compose.yml` para dar acceso al servicio `worker` al volumen `server-local-data`.
Actualice su `docker-compose.yml` local con el [docker-compose.yml de v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
### v0.43.0 to v0.44.0
### v0.43.0 a v0.44.0
Upgrade your Twenty instance to use v0.44.0 image
Actualice su instancia de Twenty para usar la imagen v0.44.0
```
yarn database:migrate:prod
yarn command:prod upgrade
```
### v0.42.0 to v0.43.0
### v0.42.0 a v0.43.0
Upgrade your Twenty instance to use v0.43.0 image
Actualice su instancia de Twenty para usar la imagen v0.43.0
```
yarn database:migrate:prod
yarn command:prod upgrade
```
In this version, we have also switched to postgres:16 image in docker-compose.yml.
En esta versión, también hemos cambiado a la imagen de postgres:16 en docker-compose.yml.
#### (Option 1) Database migration
#### (Opción 1) Migración de base de datos
Keeping the existing postgres-spilo image is fine, but you will have to freeze the version in your docker-compose.yml to be 0.43.0.
Mantener la imagen postgres-spilo existente está bien, pero tendrá que congelar la versión en su docker-compose.yml a 0.43.0.
#### (Option 2) Database migration
#### (Opción 2) Migración de base de datos
If you want to migrate your database to the new postgres:16 image, please follow these steps:
Si desea migrar su base de datos a la nueva imagen de postgres:16, siga estos pasos:
1. Dump your database from the old postgres-spilo container
1. Descargue su base de datos del contenedor antiguo de postgres-spilo
```
docker exec -it twenty-db-1 sh
@@ -157,11 +157,11 @@ exit
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
```
Make sure your dump file is not empty.
Asegúrese de que su archivo de respaldo no esté vacío.
2. Upgrade your docker-compose.yml to use postgres:16 image as in the [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) file.
2. Actualice su docker-compose.yml para usar la imagen de postgres:16 como en el archivo [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml).
3. Restore the database to the new postgres:16 container
3. Restaure la base de datos al nuevo contenedor postgres:16
```
docker cp databases_backup.sql twenty-db-1:/databases_backup.sql
@@ -170,86 +170,86 @@ psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql
exit
```
### v0.41.0 to v0.42.0
### v0.41.0 a v0.42.0
Upgrade your Twenty instance to use v0.42.0 image
Actualice su instancia de Twenty para usar la imagen v0.42.0
```
yarn database:migrate:prod
yarn command:prod upgrade-0.42
```
**Environment Variables**
**Variables del entorno**
* Removed: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
* Added: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
* Removidos: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
* Agregados: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
### v0.40.0 to v0.41.0
### v0.40.0 a v0.41.0
Upgrade your Twenty instance to use v0.41.0 image
Actualice su instancia de Twenty para usar la imagen v0.41.0
```
yarn database:migrate:prod
yarn command:prod upgrade-0.41
```
**Environment Variables**
**Variables del entorno**
* Removed: `AUTH_MICROSOFT_TENANT_ID`
* Removido: `AUTH_MICROSOFT_TENANT_ID`
### v0.35.0 to v0.40.0
### v0.35.0 a v0.40.0
Upgrade your Twenty instance to use v0.40.0 image
Actualice su instancia de Twenty para usar la imagen v0.40.0
```
yarn database:migrate:prod
yarn command:prod upgrade-0.40
```
**Environment Variables**
**Variables del entorno**
* Added: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
* Agregados: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
### v0.34.0 to v0.35.0
### v0.34.0 a v0.35.0
Upgrade your Twenty instance to use v0.35.0 image
Actualice su instancia de Twenty para usar la imagen v0.35.0
```
yarn database:migrate:prod
yarn command:prod upgrade-0.35
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.35` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.35` se encarga de la migración de datos de todos los espacios de trabajo.
**Environment Variables**
**Variables del entorno**
* We replaced `ENABLE_DB_MIGRATIONS` with `DISABLE_DB_MIGRATIONS` (default value is now `false`, you probably don't have to set anything)
* Reemplazamos `ENABLE_DB_MIGRATIONS` por `DISABLE_DB_MIGRATIONS` (valor predeterminado ahora es `false`, probablemente no tenga que establecer nada)
### v0.33.0 to v0.34.0
### v0.33.0 a v0.34.0
Upgrade your Twenty instance to use v0.34.0 image
Actualice su instancia de Twenty para usar la imagen v0.34.0
```
yarn database:migrate:prod
yarn command:prod upgrade-0.34
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.34` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.34` se encarga de la migración de datos de todos los espacios de trabajo.
**Environment Variables**
**Variables del entorno**
* Removed: `FRONT_BASE_URL`
* Added: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
* Removido: `FRONT_BASE_URL`
* Agregados: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
We have updated the way we handle the frontend URL.
You can now set the frontend URL using the `FRONT_DOMAIN`, `FRONT_PROTOCOL` and `FRONT_PORT` variables.
If FRONT_DOMAIN is not set, the frontend URL will fall back to `SERVER_URL`.
Hemos actualizado la forma en que manejamos la URL del frontend.
Ahora puede configurar la URL del frontend usando las variables `FRONT_DOMAIN`, `FRONT_PROTOCOL` y `FRONT_PORT`.
Si FRONT_DOMAIN no está configurado, la URL del frontend volverá a `SERVER_URL`.
### v0.32.0 to v0.33.0
### v0.32.0 a v0.33.0
Upgrade your Twenty instance to use v0.33.0 image
Actualice su instancia de Twenty para usar la imagen v0.33.0
```
yarn command:prod cache:flush
@@ -257,68 +257,68 @@ yarn database:migrate:prod
yarn command:prod upgrade-0.33
```
The `yarn command:prod cache:flush` command will flush the Redis cache.
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.33` takes care of the data migration of all workspaces.
El comando `yarn command:prod cache:flush` eliminará la caché de Redis.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.33` se encarga de la migración de datos de todos los espacios de trabajo.
Starting from this version, twenty-postgres image for DB became deprecated and twenty-postgres-spilo is used instead.
If you want to keep using twenty-postgres image, simply replace `twentycrm/twenty-postgres:${TAG}` with `twentycrm/twenty-postgres` in docker-compose.yml.
A partir de esta versión, la imagen twenty-postgres para DB quedó obsoleta y ahora se usa twenty-postgres-spilo.
Si desea seguir usando la imagen twenty-postgres, simplemente reemplace `twentycrm/twenty-postgres:${TAG}` con `twentycrm/twenty-postgres` en docker-compose.yml.
### v0.31.0 to v0.32.0
### v0.31.0 a v0.32.0
Upgrade your Twenty instance to use v0.32.0 image
Actualice su instancia de Twenty para usar la imagen v0.32.0
**Schema and data migration**
**Migración de esquemas y datos**
```
yarn database:migrate:prod
yarn command:prod upgrade-0.32
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.32` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.32` se encarga de la migración de datos de todos los espacios de trabajo.
**Environment Variables**
**Variables del entorno**
We have updated the way we handle the Redis connection.
Hemos actualizado la forma en que manejamos la conexión Redis.
* Removed: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
* Added: `REDIS_URL`
* Removidos: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
* Agregado: `REDIS_URL`
Update your `.env` file to use the new `REDIS_URL` variable instead of the individual Redis connection parameters.
Actualice su archivo `.env` para usar la nueva variable `REDIS_URL` en lugar de los parámetros de conexión individuales de Redis.
We have also simplified the way we handle the JWT tokens.
También hemos simplificado la forma en que manejamos los tokens JWT.
* Removed: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
* Added: `APP_SECRET`
* Removidos: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
* Agregado: `APP_SECRET`
Update your `.env` file to use the new `APP_SECRET` variable instead of the individual tokens secrets (you can use the same secret as before or generate a new random string)
Actualice su archivo `.env` para usar la nueva variable `APP_SECRET` en lugar de los secretos de tokens individuales (puede usar el mismo secreto que antes o generar una nueva cadena aleatoria)
**Connected Account**
**Cuenta conectada**
If you are using connected account to synchronize your Google emails and calendars, you will need to activate the [People API](https://developers.google.com/people) on your Google Admin console.
Si está utilizando cuentas conectadas para sincronizar sus correos electrónicos y calendarios de Google, deberá activar la [API de People](https://developers.google.com/people) en su consola de administración de Google.
### v0.30.0 to v0.31.0
### v0.30.0 a v0.31.0
Upgrade your Twenty instance to use v0.31.0 image
Actualice su instancia de Twenty para usar la imagen v0.31.0
**Schema and data migration**:
**Migración de esquemas y datos:**
```
yarn database:migrate:prod
yarn command:prod upgrade-0.31
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.31` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.31` se encarga de la migración de datos de todos los espacios de trabajo.
### v0.24.0 to v0.30.0
### v0.24.0 a v0.30.0
Upgrade your Twenty instance to use v0.30.0 image
Actualice su instancia de Twenty para usar la imagen v0.30.0
**Breaking change**:
To enhance performances, Twenty now requires redis cache to be configured. We have updated our [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) to reflect this.
Make sure to update your configuration and to update your environment variables accordingly:
**Cambio importante**:
Para mejorar el rendimiento, Twenty ahora requiere que la caché redis esté configurada. Hemos actualizado nuestro [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) para reflejar esto.
Asegúrese de actualizar su configuración y sus variables de entorno en consecuencia:
```
REDIS_HOST={your-redis-host}
@@ -326,49 +326,49 @@ REDIS_PORT={your-redis-port}
CACHE_STORAGE_TYPE=redis
```
**Schema and data migration**:
**Migración de esquemas y datos:**
```
yarn database:migrate:prod
yarn command:prod upgrade-0.30
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.30` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.30` se encarga de la migración de datos de todos los espacios de trabajo.
### v0.23.0 to v0.24.0
### v0.23.0 a v0.24.0
Upgrade your Twenty instance to use v0.24.0 image
Actualice su instancia de Twenty para usar la imagen v0.24.0
Run the following commands:
Ejecución de los siguientes comandos:
```
yarn database:migrate:prod
yarn command:prod upgrade-0.24
```
The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas)
The `yarn command:prod upgrade-0.24` takes care of the data migration of all workspaces.
El comando `yarn database:migrate:prod` aplicará las migraciones a la estructura de la base de datos (esquemas core y metadata)
El `yarn command:prod upgrade-0.24` se encarga de la migración de datos de todos los espacios de trabajo.
### v0.22.0 to v0.23.0
### v0.22.0 a v0.23.0
Upgrade your Twenty instance to use v0.23.0 image
Actualice su instancia de Twenty para usar la imagen v0.23.0
Run the following commands:
Ejecución de los siguientes comandos:
```
yarn database:migrate:prod
yarn command:prod upgrade-0.23
```
The `yarn database:migrate:prod` command will apply the migrations to the Database.
The `yarn command:prod upgrade-0.23` takes care of the data migration, including transferring activities to tasks/notes.
El comando `yarn database:migrate:prod` aplicará las migraciones a la base de datos.
El `yarn command:prod upgrade-0.23` se encarga de la migración de datos, incluyendo la transferencia de actividades a tareas/notas.
### v0.21.0 to v0.22.0
### v0.21.0 a v0.22.0
Upgrade your Twenty instance to use v0.22.0 image
Actualice su instancia de Twenty para usar la imagen v0.22.0
Run the following commands:
Ejecución de los siguientes comandos:
```
yarn database:migrate:prod
@@ -376,6 +376,6 @@ yarn command:prod workspace:sync-metadata -f
yarn command:prod upgrade-0.22
```
The `yarn database:migrate:prod` command will apply the migrations to the Database.
The `yarn command:prod workspace:sync-metadata -f` command will sync the definition of standard objects to the metadata tables and apply to required migrations to existing workspaces.
The `yarn command:prod upgrade-0.22` command will apply specific data transformations to adapt to the new object defaultRequestInstrumentationOptions.
El comando `yarn database:migrate:prod` aplicará las migraciones a la base de datos.
El comando `yarn command:prod workspace:sync-metadata -f` sincronizará la definición de objetos estándar a las tablas de metadatos y aplicará las migraciones requeridas a los espacios de trabajo existentes.
El comando `yarn command:prod upgrade-0.22` aplicará transformaciones de datos específicas para adaptarse a las nuevas opciones predeterminadas de instrumentación de solicitud de objetos.
@@ -1,30 +1,30 @@
---
title: Self-Host
description: Deploy and manage Twenty on your own infrastructure.
title: Autoalojamiento
description: Despliega y administra Twenty en tu propia infraestructura.
---
<Frame>
<img src="/images/user-guide/what-is-twenty/20.png" alt="AI" />
<img src="/images/user-guide/what-is-twenty/20.png" alt="IA" />
</Frame>
## Overview
## Resumen
Twenty can be self-hosted on your own infrastructure, giving you full control over your data and deployment.
Puedes autoalojar Twenty en tu propia infraestructura, lo que te brinda control total sobre tus datos y el despliegue.
## Why Self-Host?
## ¿Por qué autoalojar?
* **Data ownership**: Keep all CRM data on your own servers
* **Compliance**: Meet regulatory requirements for data residency
* **Customization**: Full access to modify and extend the platform
* **Propiedad de los datos**: Mantén todos los datos de CRM en tus propios servidores
* **Cumplimiento**: Cumple los requisitos normativos de residencia de datos
* **Personalización**: Acceso completo para modificar y ampliar la plataforma
## Getting Started
## Primeros pasos
<CardGroup cols={2}>
<Card title="Docker Compose" icon="docker" href="/l/es/developers/self-host/capabilities/docker-compose">
Quick setup with Docker
Configuración rápida con Docker
</Card>
<Card title="Cloud Providers" icon="cloud" href="/l/es/developers/self-host/capabilities/cloud-providers">
Deploy on AWS, GCP, or Azure
<Card title="Proveedores de la nube" icon="cloud" href="/l/es/developers/self-host/capabilities/cloud-providers">
Despliega en AWS, GCP o Azure
</Card>
</CardGroup>
+52 -52
View File
@@ -1,197 +1,197 @@
{
"tabs": {
"userGuide": {
"label": "User Guide",
"label": "Guía de usuario",
"groups": {
"discoverTwenty": {
"label": "Discover Twenty",
"label": "Descubre Twenty",
"groups": {
"gettingStartedCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"gettingStartedHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"dataModel": {
"label": "Data Model",
"label": "Modelo de datos",
"groups": {
"dataModelCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"dataModelHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"dataMigration": {
"label": "Data Migration",
"label": "Migración de datos",
"groups": {
"dataMigrationCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"dataMigrationHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"calendarEmails": {
"label": "Calendar & Emails",
"label": "Calendario y correos electrónicos",
"groups": {
"calendarEmailsCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"calendarEmailsHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"workflows": {
"label": "Workflows",
"label": "Flujos de trabajo",
"groups": {
"workflowsCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"workflowsHowTos": {
"label": "How-Tos",
"label": "Guías prácticas",
"groups": {
"crmAutomations": {
"label": "CRM Automations"
"label": "Automatizaciones de CRM"
},
"connectToOtherTools": {
"label": "Connect to Other Tools"
"label": "Conectar con otras herramientas"
},
"advancedConfigurations": {
"label": "Advanced Configurations"
"label": "Configuraciones avanzadas"
},
"needMoreHelp": {
"label": "Need More Help"
"label": "¿Necesitas más ayuda?"
}
}
}
}
},
"ai": {
"label": "AI",
"label": "IA",
"groups": {
"aiCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"aiHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"viewsPipelines": {
"label": "Views & Pipelines",
"label": "Vistas y canalizaciones",
"groups": {
"viewsPipelinesCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"viewsPipelinesHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"dashboards": {
"label": "Dashboards",
"label": "Tableros",
"groups": {
"dashboardsCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"dashboardsHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"permissionsAccess": {
"label": "Permissions & Access",
"label": "Permisos y acceso",
"groups": {
"permissionsAccessCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"permissionsAccessHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"billing": {
"label": "Billing",
"label": "Facturación",
"groups": {
"billingCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"billingHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
},
"settings": {
"label": "Settings",
"label": "Configuración",
"groups": {
"settingsCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
},
"settingsHowTos": {
"label": "How-Tos"
"label": "Guías prácticas"
}
}
}
}
},
"developers": {
"label": "Developers",
"label": "Desarrolladores",
"groups": {
"developersGroup": {
"label": "Developers"
"label": "Desarrolladores"
},
"extend": {
"label": "Extend",
"label": "Ampliar",
"groups": {
"extendCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
}
}
},
"selfHost": {
"label": "Self-Host",
"label": "Autoalojamiento",
"groups": {
"selfHostCapabilities": {
"label": "Capabilities"
"label": "Capacidades"
}
}
},
"contribute": {
"label": "Contribute",
"label": "Contribuir",
"groups": {
"contributeCapabilities": {
"label": "Capabilities",
"label": "Capacidades",
"groups": {
"frontendDevelopment": {
"label": "Frontend Development",
"label": "Desarrollo Frontend",
"groups": {
"twentyUi": {
"label": "Twenty UI",
"groups": {
"display": {
"label": "Display"
"label": "Mostrar"
},
"feedback": {
"label": "Feedback"
"label": "Retroalimentación"
},
"input": {
"label": "Input"
"label": "Entrada"
},
"navigation": {
"label": "Navigation"
"label": "Navegación"
}
}
}
}
},
"backendDevelopment": {
"label": "Backend Development"
"label": "Desarrollo Backend"
}
}
}

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