Compare commits

..
350 Commits
Author SHA1 Message Date
Marie Stoppa 41fcd692a6 Revert "Fix spaces in code step + suggestion overflow (#17664)"
This reverts commit 99aab9f40d.
2026-02-03 14:05:28 +01:00
MarieandMarie Stoppa b9bc1264b3 Allow twenty standard app to access messaging items (#17659) 2026-02-03 13:38:30 +01:00
Thomas TrompetteandMarie Stoppa be358e398e 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:38:22 +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 <charles@macbook-pro-de-charles-1.home>
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 <github-actions@twenty.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <github-actions@twenty.com>
2026-02-02 15:40:19 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>DevessierBaptiste Devessier
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] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>
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 <noreply@anthropic.com>
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 <github-actions@twenty.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <github-actions@twenty.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <raphael.bosi@gmail.com>
2026-02-02 07:42:20 +00:00
1976ad58c4 i18n - docs translations (#17599)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-31 14:22:36 +01:00
be8629d8f6 i18n - docs translations (#17597)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
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 <bordeau.lucas@gmail.com>
2026-01-30 18:35:52 +00:00
394b6f5717 i18n - translations (#17594)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-30 19:43:46 +01:00
a97ca14289 i18n - docs translations (#17593)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <45695613+etiennejouan@users.noreply.github.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
2026-01-30 17:25:52 +01:00
febab2760a i18n - translations (#17585)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-30 17:17:49 +01:00
9eabfc628a i18n - translations (#17584)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <charles@twenty.com>
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 <github-actions@twenty.com>
2026-01-30 15:20:13 +01:00
9c55313590 i18n - translations (#17578)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>DevessierDevessier
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] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
2026-01-29 14:02:16 +00:00
efbbfe5570 i18n - translations (#17558)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
2026-01-29 12:12:40 +01:00
c9a826aba1 i18n - docs translations (#17542)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
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 <45695613+etiennejouan@users.noreply.github.com>
2026-01-29 09:39:14 +00:00
63bf46703d i18n - translations (#17538)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <45695613+etiennejouan@users.noreply.github.com>
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] <support@github.com>
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] <support@github.com>
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 <bordeau.lucas@gmail.com>
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 <github-actions@twenty.com>
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 <charles@twenty.com>
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 <cursoragent@cursor.com>
2026-01-28 17:36:36 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Devessier
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] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
2026-01-28 15:09:19 +00:00
f7908de4c1 i18n - docs translations (#17502)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
2026-01-28 01:50:22 +01:00
9fbd05ead7 i18n - docs translations (#17500)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <tomlarson@Toms-MacBook-Pro.local>
2026-01-27 13:16:03 +01:00
43c9a75402 i18n - translations (#17472)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <felix.malfait@gmail.com>
Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-27 10:42:27 +00:00
c79f633f17 i18n - translations (#17468)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <bordeau.lucas@gmail.com>
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 <daniel@example.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
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 <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
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 <github-actions@twenty.com>
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 <github-actions@twenty.com>
2026-01-26 09:06:17 +01:00
2353bc62cc i18n - docs translations (#17433)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-26 07:11:01 +01:00
c8c821a692 i18n - docs translations (#17426)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
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 <github-actions@twenty.com>
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 <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
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 <felix@twenty.com>
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 <felix.malfait@gmail.com>
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 <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
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
nitinandGitHub 4c94e650a7 fix backfill command (#17402) 2026-01-23 14:46:39 +00:00
nitinandGitHub 8255c51910 [Dashboards] Improve bar chart performance (#17399) 2026-01-23 13:03:36 +00:00
0091ef5f6c Sync built files (#17379)
as title, upload built files to local storage

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-23 13:43:53 +01:00
Raphaël BosiandGitHub 30620c79fd Add footer to iFrame widget settings (#17397)
Add footer to iFrame widget settings
2026-01-23 11:17:46 +00:00
c02227472e Fix dashboard animations (#17389)
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
2026-01-23 10:36:13 +00:00
Raphaël BosiandGitHub 78dd43dbb0 Release dashboards V1 (#17386)
Remove `IS_PAGE_LAYOUT_ENABLED` feature flag entirely
2026-01-23 10:30:05 +00:00
b8da15d9d5 i18n - translations (#17391)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-23 11:39:49 +01:00
Raphaël BosiandGitHub c76ddfd6d1 [DASHBOARDS] Fix pie chart gap when there is only one slice (#17388)
## Before
<img width="1290" height="764" alt="CleanShot 2026-01-23 at 11 00 44@2x"
src="https://github.com/user-attachments/assets/f276f42b-7f2d-4c2a-98cf-cb693cda1242"
/>

## After
<img width="1292" height="762" alt="CleanShot 2026-01-23 at 11 00 28@2x"
src="https://github.com/user-attachments/assets/a2d3df70-7965-4d4d-851b-729199a52510"
/>
2026-01-23 10:11:26 +00:00
Félix MalfaitandGitHub 41dd9856e6 fix(twenty-front): fix tsconfig to properly typecheck all files with tsgo (#17380)
## Summary

This PR fixes the `tsconfig` setup in `twenty-front` so that `tsgo -p
tsconfig.json` properly type-checks all files.

### Root Cause

The previous setup used TypeScript project references with `files: []`
in the main `tsconfig.json`. When running `tsgo -p tsconfig.json`, this
checks nothing because `tsgo` requires the `-b` (build) flag for project
references, but the configs weren't set up for composite mode.

### Changes

**Simplified tsconfig architecture (4 files → 2):**
- `tsconfig.json` - All files (dev, tests, stories) for
typecheck/IDE/lint
- `tsconfig.build.json` - Production files only (excludes tests/stories)

**Removed redundant configs:**
- `tsconfig.dev.json`
- `tsconfig.spec.json` 
- `tsconfig.storybook.json`

**Updated references:**
- `jest.config.mjs` → uses `tsconfig.json`
- `eslint.config.mjs` → uses `tsconfig.json`
- `vite.config.ts` → uses `tsconfig.json` for dev

**Type fixes (pre-existing errors revealed by proper typechecking):**
- Made `applicationId` optional in `FieldMetadataItem` and
`ObjectMetadataItem`
- Added missing `navigationMenuItem` translation
- Added `objectLabelSingular` to Search GraphQL query
- Fixed `sortMorphItems.test.ts` mock data

## Test plan

- [ ] Run `npx nx typecheck twenty-front` - should pass
- [ ] Run `npx nx lint twenty-front` - should work
- [ ] Run `npx nx test twenty-front` - should work
- [ ] Run `npx nx build twenty-front` - should work
- [ ] Verify IDE type checking works correctly
2026-01-23 11:22:23 +01:00
nitinandGitHub cb9fe604e4 [Dashboards] Fix rich text widget empty side menu bug (#17377)
before - 



https://github.com/user-attachments/assets/d0f9efc2-a94a-41b5-8e46-c4c92b96c1da




after - 


https://github.com/user-attachments/assets/48018856-5166-490d-93d5-03abefa0eeb3
2026-01-23 09:26:45 +00:00
Baptiste DevessierandGitHub 183cf6c803 Shrink table rows (#17360)
We often use the `fr` unit in a CSS grid thinking that it will force the
max-width of the grid's items. However, [`1fr` is always equal to
`minmax(auto, 1fr)`](https://stackoverflow.com/a/52861514). The
consequences are:

- By default, the width of the element will be `1fr`.
- However, if the size of the element becomes wider than the resolved
dimension of `1fr`, the item will expand to match its `auto` width. This
can be easily encountered when an item has a really long text. The
`auto` size will be the dimension of the long text displayed on a single
line.

If you want your element not to overflow, you can use `minmax(0, 1fr)`
instead of `1fr`.

This is a known behavior in the community. CSS frameworks like Tailwind
CSS even made their utility classes setting grid columns use `minmax(0,
xfr)` by default.

<img width="1380" height="84" alt="CleanShot 2026-01-22 at 16 11 00@2x"
src="https://github.com/user-attachments/assets/ace3c862-90ef-4a47-9ad5-9ae8b6e0fe40"
/>

See:
https://www.bigbinary.com/blog/understanding-the-automatic-minimum-size-of-flex-items.

Another fix is to use `min-width: 0;` on items themselves, instead of
using `minmax(0, 1fr)` at the grid level. This would make it possible to
create a reusable `Td` component.

## Data model - fields

### Before
<img width="1120" height="1808" alt="CleanShot 2026-01-22 at 15 46
48@2x"
src="https://github.com/user-attachments/assets/893115f4-219d-4a75-b628-56315298d791"
/>

### After
<img width="1120" height="1808" alt="CleanShot 2026-01-22 at 15 47
00@2x"
src="https://github.com/user-attachments/assets/093ae231-ccae-4df5-8227-a662a3fae97e"
/>

## Data model - relations

### Before
<img width="1172" height="754" alt="CleanShot 2026-01-22 at 16 00 33@2x"
src="https://github.com/user-attachments/assets/3367b849-a4f4-471b-b73a-a80a95368640"
/>

### After
<img width="1146" height="770" alt="CleanShot 2026-01-22 at 16 00 44@2x"
src="https://github.com/user-attachments/assets/ff08556e-fc16-4984-bd4b-bb15f4dabf36"
/>

## Object level object field permission

### Before
<img width="1150" height="1902" alt="CleanShot 2026-01-22 at 15 50
16@2x"
src="https://github.com/user-attachments/assets/adce1ba1-042b-4892-9a0e-af254109d03c"
/>

### After
<img width="1150" height="1902" alt="CleanShot 2026-01-22 at 15 50
01@2x"
src="https://github.com/user-attachments/assets/55476711-2573-43ae-b1d6-b8695f4305ab"
/>
2026-01-22 19:42:09 +00:00
Paul RastoinandGitHub 4c93ab5259 Introduce UniversalFlatEntityFrom (#17367)
# Introduction

Creating a `UniversalFlatEntityFrom` that strips out all the relation
and foreignKey properties in order to replace them with
`UniversalIdentifier` suffix

This data type will be major for the workspace migration workspace
agnostic refactor

## Chore
- renamed `flat-entity.type` to `flat-entity-from.type.ts` ( more
accurate to exported module )
- create static test type over the field metadata entity on quite
complex utils as both coverage and documentation

## Example
Here's an example of a `UniversalFlatEntityFrom<FieldMetadataEntity>`

```ts
const universalFlatFieldMetadata: UniversalFlatFieldMetadata<FieldMetadataType.RELATION> = {
  // Base properties (from FieldMetadataEntity, excluding relations and applicationId)
  universalIdentifier: '550e8400-e29b-41d4-a716-446655440001',
  applicationUniversalIdentifier: '5800681c-088e-4e2b-9fc3-bcf6e8ec2051',
  type: FieldMetadataType.RELATION,
  name: 'firstName',
  label: 'First Name',
  defaultValue: null,
  description: 'The first name of the person',
  icon: 'IconUser',
  standardOverrides: null,
  options: null,
  settings: {
    relationType: RelationType.ONE_TO_MANY,
  },
  isCustom: false,
  isActive: true,
  isSystem: false,
  isUIReadOnly: false,
  isNullable: true,
  isUnique: false,
  isLabelSyncedWithName: true,
  morphId: null,

  // Date properties cast to string
  createdAt: '2024-01-15T10:30:00.000Z',
  updatedAt: '2024-01-15T10:30:00.000Z',

  // ManyToOne relation universal identifiers (from FieldMetadataEntity relations)
  relationTargetFieldMetadataUniversalIdentifier:
    '550e8400-e29b-41d4-a716-446655440012',
  relationTargetObjectMetadataUniversalIdentifier:
    '550e8400-e29b-41d4-a716-446655440013',

  // Join column universal identifiers (foreignKey -> universalIdentifier)
  objectMetadataUniversalIdentifier: '550e8400-e29b-41d4-a716-446655440010',

  // OneToMany relation universal identifiers (array of related entity identifiers)
  viewFieldUniversalIdentifiers: [
    '550e8400-e29b-41d4-a716-446655440020',
    '550e8400-e29b-41d4-a716-446655440021',
  ],
  viewFilterUniversalIdentifiers: ['550e8400-e29b-41d4-a716-446655440030'],
  kanbanAggregateOperationViewUniversalIdentifiers: [],
  calendarViewUniversalIdentifiers: [],
  mainGroupByFieldMetadataViewUniversalIdentifiers: [],
};
```

## Settings
Will hop on the settings typing next. Might not be dynamic but
declarative though
2026-01-22 18:30:19 +00:00
nitinandGitHub fd43eeda41 [Dashboards] Update default bar chart config (#17375)
https://github.com/user-attachments/assets/f7f11048-f27b-4c81-8626-71351a278f2b
2026-01-22 17:46:22 +00:00
Raphaël BosiandGitHub 23798ddc6b Remove debounced chart resize (#17376)
Remove debounced chart resize
2026-01-22 17:40:22 +00:00
ad44852880 i18n - translations (#17374)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 18:22:28 +01:00
martmullandGitHub e72f652723 Follow up dev and build (#17364)
fresh eye report

- Improve template entities
- Ignore .twenty properly
- Add base src alias to tsconfig
2026-01-22 18:19:06 +01:00
Charles BochetandGitHub 210c66b5dd Add checksum to manifest (#17368)
## Refactor app-dev state management and build utilities

- Store only `manifest` in `AppDevState` instead of full
`ManifestBuildResult`; add `sourcePath` to `FileStatus`
- Pass `sourcePaths` directly to watchers instead of
`ManifestBuildResult`
- Only reset `fileUploadStatus` for functions/components when their
source paths change
- Add pure `updateManifestChecksum` utility that returns a new manifest
without side effects
- Extract `processEsbuildResult` to deduplicate build result processing
between watchers
- Rename `serverlessFunctions` → `functions` in SDK code (API unchanged)
- Extract `writeManifestToOutput` to shared `manifest-writer.ts`
2026-01-22 18:16:55 +01:00
neo773andGitHub 843fda7564 CalDav throw error If a user tries to connect an unsupported server. (#17363)
Legacy CalDav servers don't support `syncCollection` which is required
to store a `syncCursor` this PR updates the code to let user know we
don't support their server.

Source:
https://github.com/natelindev/tsdav/blob/main/src/collection.ts#L193-L194
2026-01-22 18:12:00 +01:00
neo773andGitHub 3ce33304a3 Revert TS LSP to Strada (#17365)
Corsa as LSP is not stable yet memory usage increases exponentially
which creates memory pressure, this PR reverts it to Strada while still
keeping Corsa for typecheck commands and CI

<img width="1590" height="892" alt="image"
src="https://github.com/user-attachments/assets/4377ecd5-a0dd-43d7-aeb7-9f8a34ea6c81"
/>


<img width="1646" height="878" alt="image"
src="https://github.com/user-attachments/assets/f82f5075-7404-46be-97a2-3313649d028c"
/>
2026-01-22 18:10:55 +01:00
Baptiste DevessierandGitHub 3ef789dab4 Fix blank page layout after navigation (#17340)
Until now, it wasn’t possible to navigate between page layouts.
Dashboards don’t contain links to other dashboards. With record page
layouts, however, a page layout can now contain a link to another page
layout. If the user opens a record in the side panel and then clicks a
link to another record, the current page layout is replaced with the
page layout for the clicked record.

In this scenario, the `PageLayoutRenderer` component is not remounted.
As a result, the `isInitialized` state was not reset to `false`, and the
corresponding initialization `useEffect` was not triggered again.

All page layout states are component states bound to
`PageLayoutComponentInstanceContext`. For example, after navigating to
another record, `pageLayoutPersistedComponentState` was actually
`undefined` because the context's `instanceId` had changed.

Replacing the `isInitialized` state with a component state fixes the bug
and is consistent with the existing pattern of using component states to
store everything related to page layouts.

## Before


https://github.com/user-attachments/assets/24217145-51e5-49ef-8180-de62a6acfe10

## After


https://github.com/user-attachments/assets/e0ffe107-8fae-4f3a-9016-72c85bc735f4
2026-01-22 16:52:56 +00:00
Thomas TrompetteandGitHub f11442953c Remove if-else workflow node flag (#17370)
As title
2026-01-22 16:33:52 +00:00
2d7a73d82d i18n - translations (#17373)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 17:42:28 +01:00
MarieandGitHub 8868ef4ec3 Remove useUpdateOneRecordV2 (#17355)
Previously we introduced useUpdateOneRecordV2 to handle morph relations.
In this PR we are removing it to only use useUpdateOneRecord which has
been adapted not to requires objectMetadataNameSingular in its props.
2026-01-22 16:27:57 +00:00
nitinandGitHub 873749981b [Dashboards] fix dragging widget z index (#17371) 2026-01-22 16:23:20 +00:00
Raphaël BosiandGitHub f5045d830e [DASHBOARDS] Improve bar chart performances (#17358)
This PR implements various performances improvements for bar charts.

## Before


https://github.com/user-attachments/assets/26e1d10a-582c-46c6-abc4-a094e0bf7417


## After


https://github.com/user-attachments/assets/fb3ebb8d-929d-4a67-8391-e5d5b22c9e11
2026-01-22 16:22:38 +00:00
Baptiste DevessierandGitHub fb2c9e12bc Fix a bunch of bugs on Record Page Layouts (#17342)
## Display task target and note target relations


https://github.com/user-attachments/assets/b291cc38-33b9-45b8-b685-e5890824176e

## Drop summary card in right drawer

### Before

<img width="1362" height="2160" alt="CleanShot 2026-01-22 at 14 27
09@2x"
src="https://github.com/user-attachments/assets/79f45d8f-976b-4999-8f62-5cb4b87b3ef6"
/>

### After

<img width="1362" height="2160" alt="CleanShot 2026-01-22 at 14 26
43@2x"
src="https://github.com/user-attachments/assets/fb67ddbb-0585-48bd-8ef6-c0139bba4062"
/>

## Add a tooltip to the see all action


https://github.com/user-attachments/assets/ab05cfab-8a6c-4caf-bfe9-698c8b7904e6

## Rename the Fields tab to Home

### Before

<img width="544" height="354" alt="CleanShot 2026-01-22 at 14 28 26@2x"
src="https://github.com/user-attachments/assets/62f7faa2-3a18-40af-8cdf-3fb9f7c7708c"
/>

### After

<img width="544" height="354" alt="CleanShot 2026-01-22 at 14 28 06@2x"
src="https://github.com/user-attachments/assets/76c21ec9-3723-4933-b34f-88b6403cbad5"
/>
2026-01-22 16:17:10 +00:00
Baptiste DevessierandGitHub 267691a403 Render missing context provider for layouts (#17349)
## Before

<img width="1656" height="892" alt="image"
src="https://github.com/user-attachments/assets/05c6ced5-ff13-4987-a978-bae5111da9d4"
/>


## After



https://github.com/user-attachments/assets/a3b34590-8006-4363-8fa4-b52fb099bbff
2026-01-22 16:17:04 +00:00
fe9c8f1429 i18n - translations (#17369)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 17:22:50 +01:00
Thomas TrompetteandGitHub c008e874e5 Allow variables with dots and keys (#17361)
Fixes
https://github.com/twentyhq/private-issues/issues/410#issuecomment-3781085655

Currently, JSON keys with spaces like { "toto toto": 123 } are rejected
with "JSON keys cannot contain spaces" error. This is problematic for
HTTP requests and webhook triggers where users cannot control the
response structure.

We use Handlebars to eval variables, segment-literal bracket notation to
escape keys with special characters:
Normal: {{step.normalKey}}
With spaces: `{{step.[key with space]}}`

So we simply need to wrap segments with spaces with brackets.

This PR: 
- Create shared path utilities to wrap the variable segments when needed
- Use it in all variable generation places
- Remove the restrictions

This body is now supported:
<img width="609" height="457" alt="Capture d’écran 2026-01-22 à 16 10
13"
src="https://github.com/user-attachments/assets/e7653c0a-df1e-49af-9c9a-4b7d59a99726"
/>
2026-01-22 16:06:53 +00:00
29c12c0ffd i18n - translations (#17366)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 17:03:44 +01:00
WeikoandGitHub 296a3099db Refactor rls backend simplify add tests (#17357)
## Context
Removing full CRUD to RLS since we are actually using an upsert only
over the role resolver, this removes a lot of unused code (could be
re-added later but unlikely). Simplified the folder arch at the same
time
Add simple integration tests to RLS
2026-01-22 15:44:18 +00:00
669da1a87d fix: display current object name in morph relation picker after rename (#17209)
**Summary**

Issue #16963: When an object is renamed, the morph relation picker still
shows the old name.

**Root cause**

The picker used searchRecord.objectNameSingular from the GraphQL search
response, which can be stale after a rename.
The search record stores the object name at query time, not the current
metadata.

**Solution**

- Updated SingleRecordPickerMenuItem:
- Added useObjectMetadataItems to access current object metadata.
- Look up the current object metadata by morphItem.objectMetadataId.
- Use labelSingular (or nameSingular as fallback) instead of
searchRecord.objectNameSingular for display.
- Updated MultipleRecordPickerMenuItemContent:
- Use objectMetadataItem.labelSingular (already available as a prop)
instead of searchRecord.objectNameSingular.

---------

Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
2026-01-22 15:40:57 +00:00
nitinandGitHub 1e9cd2f5c6 [Dashboards] Dynamic paddings (#17298)
closes https://github.com/twentyhq/core-team-issues/issues/2055


https://github.com/user-attachments/assets/7a19066f-e891-4dfa-92ee-b85690765e60
2026-01-22 15:38:02 +00:00
f8d1454c03 i18n - translations (#17362)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 16:40:58 +01:00
Paul RastoinandGitHub 2a28c34a37 Refactor workspace migration runner exception handling (#17310)
# Introduction
In this PR we catch all the runner errors coming from a single workspace
migration action execution.

**1. `WorkspaceMigrationActionExecutionException`** (low-level,
action-specific)
- Thrown from action handlers, utils, and helper functions
- Contains specific error codes: `FIELD_METADATA_NOT_FOUND`,
`OBJECT_METADATA_NOT_FOUND`, `ENUM_OPERATION_FAILED`, `NOT_SUPPORTED`,
etc.
- Simple structure: `message`, `code`, `userFriendlyMessage`
- No action context - just describes what went wrong

**2. `WorkspaceMigrationRunnerException`** (high-level, runner-scoped)
- Only two codes: `INTERNAL_SERVER_ERROR` and `EXECUTION_FAILED`
- `EXECUTION_FAILED` **requires** `action` + `errors` (contains the
action context)
- `INTERNAL_SERVER_ERROR` **requires** `message` (no action context)

## Refactor
- Removed the `relatedFlatEntityMapsKeys` from the `WorkspaceMigration`
type as they're directly inferred from passed actions
- Swallowing actions rollbacks errors in order to iterate over all of
them

## Testing
Created a very straigthforward install application from workspace
migration endpoint in order to start testing the introduced
`WorkspaceMigrationActionExecutionException`
Introduced a feature flag that stop the access to the endpoint if not
enabled
Whole taken direction are totally subjective and highly prone to
mutations ( endpoint location, naming and input schema see
`ts-expect-error` comment )
cc @martmull 

## Response error
```ts
{
  "eventId": "evt_a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "extensions": {
    "action": {
      "metadataName": "fieldMetadata",
      "type": "delete",
      "universalIdentifier": "20202020-6110-4547-9fd0-2525257a2c3f"
    },
    "code": "APPLICATION_INSTALLATION_FAILED",
    "errors": {
      "metadata": {
        "code": "ENTITY_NOT_FOUND",
        "message": "Could not find flat entity with universal identifier 20202020-6110-4547-9fd0-2525257a2c3f"
      },
      "workspaceSchema": {
        "code": "ENTITY_NOT_FOUND",
        "message": "Could not find flat entity in maps"
      }
    },
    "exceptionEventId": "exc_f9e8d7c6-5432-10ba-fedc-ba0987654321",
    "userFriendlyMessage": "Migration execution failed."
  },
  "message": "Migration action 'delete' for 'fieldMetadata' failed",
  "name": "GraphQLError"
}
```
2026-01-22 15:05:21 +00:00
martmullandGitHub a68e05682b Serverless built updates (#17351)
follow up of https://github.com/twentyhq/twenty/pull/17317
2026-01-22 15:04:29 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah.
7fcc43f96b Bump @sentry/nestjs from 10.27.0 to 10.36.0 (#17352)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/nestjs&package-manager=npm_and_yarn&previous-version=10.27.0&new-version=10.36.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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah. <125115953+mabdullahabaid@users.noreply.github.com>
2026-01-22 15:03:57 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Abdullah.
4153462db9 Bump ts-key-enum from 2.0.12 to 2.0.13 (#17353)
Bumps [ts-key-enum](https://gitlab.com/nfriend/ts-key-enum) from 2.0.12
to 2.0.13.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://gitlab.com/nfriend/ts-key-enum/tags">ts-key-enum's
releases</a>.</em></p>
<blockquote>
<h2>v2.0.13</h2>
<p>Remove link to Wikipedia page on teletext.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://gitlab.com/nfriend/ts-key-enum/commit/e9259b2365bbcd82abfb3cfa38a1f1a78041e95a"><code>e9259b2</code></a>
2.0.13</li>
<li><a
href="https://gitlab.com/nfriend/ts-key-enum/commit/5f8a0e079164cad933f1481034d063e088c2a5a3"><code>5f8a0e0</code></a>
MDN update</li>
<li>See full diff in <a
href="https://gitlab.com/nfriend/ts-key-enum/compare/v2.0.12...v2.0.13">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ts-key-enum&package-manager=npm_and_yarn&previous-version=2.0.12&new-version=2.0.13)](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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah. <125115953+mabdullahabaid@users.noreply.github.com>
2026-01-22 14:57:17 +00:00
d2bc38e25e i18n - translations (#17359)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 16:04:49 +01:00
nitinandGitHub 92ea329128 [Dashboards] Add delete widget option in widget settings footer (#17344) 2026-01-22 14:30:30 +00:00
Abdul RahmanandGitHub 4ccdbf1150 fix: remove empty else-if branches and add numbering (#17315) 2026-01-22 14:30:11 +00:00
Charles BochetandGitHub 235fc44228 Fix tests (#17356)
A small fix
2026-01-22 15:35:39 +01:00
Thomas TrompetteGitHubcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
8bf266626c [SSE] Event stream TTL refresh (#17337)
This PR update the redis subscription iterator wrapper with an heartbeat
system. Heartbeat interval are based on event stream TTL. Those refresh
the event stream TTL and active streams in redis.

I also cleaned a few functions that were not used anymore.

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-01-22 13:32:59 +00:00
Félix MalfaitandGitHub 6b63a28cd2 Exclude community apps from Dependabot scanning (#17345)
## Summary
- Adds `exclude-paths` configuration to Dependabot to skip
`packages/twenty-apps/community/**`
- Community-maintained apps have their own dependency management and
don't need the same security requirements as core packages

## Test plan
- Verify Dependabot no longer creates alerts/PRs for dependencies in
community apps
2026-01-22 14:47:21 +01:00
Charles BochetandGitHub d537d941a7 Add sdk build (#17335)
## Summary

Adds `app:build` command as a one-shot version of `app:dev` - builds
manifest, functions, and front components once then exits (no watching).

**Changes:**
- Added `watch` option to `FunctionsWatcher` and
`FrontComponentsWatcher` to support both watch and one-shot modes
- Created `AppBuildCommand` reusing the same build logic as `app:dev`
with `watch: false`
- Added integration tests for `app:build` on both `rich-app` and
`root-app`
- Updated manifest types: `handlerPath` → `sourceHandlerPath` +
`builtHandlerPath`, `componentPath` → `sourceComponentPath` +
`builtComponentPath`
- Updated test app `package.json` scripts to match `create-twenty-app`
template
2026-01-22 14:36:40 +01:00
Raphaël BosiandGitHub d6f088f720 [DASHBOARDS] Hide pinned workflow actions when dashboard is in edit mode (#17341)
## Before


https://github.com/user-attachments/assets/85fe3929-eebf-4412-bdb6-e069e623838d


## After



https://github.com/user-attachments/assets/5fd6d3ea-d667-4c70-86c1-ace57d3aee2f
2026-01-22 13:22:44 +00:00
Raphaël BosiandGitHub b98c60a5e2 Fix menu item toggle (#17338)
## Before


https://github.com/user-attachments/assets/c26ad780-3a6a-4ccf-91a9-1c7e1bd1413d



## After



https://github.com/user-attachments/assets/40f951c7-6bcf-45a3-98bc-c631f5ec883b
2026-01-22 13:21:49 +00:00
EtienneandGitHub 0459f25dec Files v2 - Add new workspace field file upload resolver (#17325) 2026-01-22 13:15:52 +00:00
96bdae4582 i18n - translations (#17339)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 14:09:39 +01:00
Abdul RahmanandGitHub aff577689f Add syncable NavigationMenuItem entity to core schema (#17232)
Implements a new syncable `navigationMenuItem` entity in the core schema
to replace the workspace `favorite` entity.

## Next Steps
- Frontend integration ([separate
PR](https://github.com/twentyhq/twenty/pull/17268))
- Data migration (separate PR)
2026-01-22 12:48:51 +00:00
Raphaël BosiandGitHub 2cda1f5f08 [DASHBOARD] Allow hovering small pie slices that are smaller than the gap (#17330)
Fixes https://github.com/twentyhq/core-team-issues/issues/2059

## Video QA

### Example with normal padding


https://github.com/user-attachments/assets/aa0c610a-cdac-472e-afe7-b995ef3f32cd

### Example with bigger padding to better see the behavior


https://github.com/user-attachments/assets/ff15b1f1-d18a-4fae-858a-2388f2cd9fa2
2026-01-22 12:18:31 +00:00
Félix MalfaitandGitHub 8dec37b826 feat: migrate typecheck to tsgo for faster type checking (#17331)
## Summary
- Switch `twenty-server` and `fireflies` typecheck from tsc to tsgo
(~75x faster)
- Enable tsgo in VSCode via `typescript.experimental.useTsgo` setting
- Add `@ts-nocheck` to `remove-step.spec.ts` to work around tsgo
performance issue with deep spread operations

## Performance
| Package | Before (tsc) | After (tsgo) |
|---------|-------------|--------------|
| `twenty-server` | ~150s | ~2s |
| `twenty-front` | ~40s | ~2s |

## Related
- Workaround for: https://github.com/microsoft/typescript-go/issues/2551

## Test plan
- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx typecheck twenty-front` passes  
- [x] `npx nx run-many --target=typecheck --exclude=fireflies` passes
(fireflies has pre-existing type errors)
2026-01-22 13:39:07 +01:00
martmullandGitHub d833914888 Disable query logs if I want to (#17329)
As tile
2026-01-22 13:38:45 +01:00
neo773andGitHub a1c112a9b9 Fix null user crash in admin panel user lookup (#17336) 2026-01-22 13:35:30 +01:00
WeikoandGitHub 4c565425c5 Fix workspace resolver when billing is not enabled (#17333) 2026-01-22 13:33:29 +01:00
Charles BochetandGitHub 5a94ef7dbb Move sdk watcher back to esbuild (#17316)
Demo of current state


https://github.com/user-attachments/assets/034aff50-9981-4c81-b5ce-410a07b8dbc9
2026-01-22 13:04:43 +01:00
martmullandGitHub f92c8a98a4 2114 extensibility manage serverless execution from built code (#17317)
Summary

- Serverless functions are now built when created/updated instead of at
execution time
  - Added builtHandlerPath field to track pre-built function artifacts
- Renamed base-typescript-project to seed-project with pre-built ESM
output included
- Renamed file folder enums for consistency (Functions → BuiltFunction,
SourceCode → Source)
  - supports backwards compatibility with existing serverless functions

Tested with all combinations
- storage : local driver and S3 driver
- serverless : local driver and lambda driver
2026-01-22 12:56:44 +01:00
WeikoandGitHub 9ee2cd0fe3 Enable RLS in lab (#17328) 2026-01-22 12:27:11 +01:00
neo773andGitHub 1ffd23764c Fix Gmail sync edge case with multi-label emails (#17318)
Gmail emails often have multiple labels - an email in "XYZ" label
typically also has "INBOX". The previous negative filter approach
(-label:inbox -label:sent...) excluded these emails entirely, even when
they had a selected label.

Switched to positive OR filtering: (label:cyz OR label:xyz-visible)
-label:spam... which correctly fetches emails with any of the selected
labels regardless of other labels they have.

This edge case wasn't caught earlier because manual testing with INBOX
selected worked fine - it only surfaced when selecting custom labels
without INBOX.
2026-01-22 12:00:45 +01:00
WeikoandGitHub 670ff1583e Fix RLS entitlement check + fix role page with RLS on object without object-permission not being displayed (#17326) 2026-01-22 11:48:24 +01:00
Paul RastoinandGitHub f10515fc2d Add vscode tasks for integration test run and inputs to debug mode (#17327)
# Introduction
Created a task that allow spawning a term that will run the opened file
and run its integration tests

## Motivation
Jest extension is quite buggy lately, this might just be a tmp
workaround but we can't get `run test` within the test file directly.
Also passing by task allows giving inputs

## Usage

`cmd+shift+P`-> `run task` -> `twenty server - run integration test
file` -> fill inputs prompts:
- watch: will rerun on every related files updates
- updateSnapshot

Note: you can add a custom shortcut to the task too, also it will appear
in task history

Watch mode won't re-create the server instance on each run, meaning that
nest won't hit the `createServer` making very fast to iterate with
Down side of this is that any module injection won't get hydrated
2026-01-22 10:32:30 +00:00
60d61555cc i18n - docs translations (#17320)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-22 10:51:43 +01:00
Charles BochetandGitHub 006be18f19 More improvements on SDK watch (#17314)
Refactor manifest build and remove src folder assumptions


- Unified entity builder interface: All entity builders now return
EntityBuildResult<T> with both manifests and filePaths
- Always return build result: runManifestBuild now always returns {
manifest, filePaths } instead of null on failure
- Return all entity paths: EntityFilePaths includes paths for all entity
types (application, objects, functions, etc.)
- Remove src folder assumptions: Applications can now have entities at
root level - removed hasSrcFolder checks
- Simplify watchers: Removed entries caching, compute lazily with map()
- Stabilize tests: Replaced flaky console output snapshots with key
message assertions; replaced inline snapshots with array comparisons
-
2026-01-21 21:49:50 +01:00
6bc64f78fc i18n - docs translations (#17261)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-21 21:48:49 +01:00
Charles BochetandGitHub cb4110e894 Enhance tests on SDK (#17312)
![20260121_180621](https://github.com/user-attachments/assets/9284f2b1-6b4e-40fb-abf1-9c981fcb7166)
2026-01-21 19:27:04 +01:00
Thomas TrompetteandGitHub 981956a636 Use new SSE setup for workflow runs (#17309)
- Add a specific subscriber for workflow run
- Workflow runs use apollo cache. Adding updateRecordFromCache on record
updates
2026-01-21 17:26:43 +00:00
MarieandGitHub 96aef62ae4 [Apps] Get rid of .yarn binaries in apps (#17306)
Fixes https://github.com/twentyhq/core-team-issues/issues/1956

**Problem**
Within an app, the `.yarn/releases/` folder contains executable Yarn
binaries that run when executing any yarn command (`.yarnrc` file
indicates yarn path to be `.yarn/releases/yarn-4.9.2.cjs `.)
This is a supply chain attack vector: a malicious actor could submit a
PR with a compromised `yarn-4.9.2.cjs binary`, which would execute
arbitrary code on developers' machines or CI systems.

**Fix**
Actually, thanks to Corepack, we don't need to store and execute this
binary.
Corepack can be seen as the manager of a package manager: in
`package.json` we indicate a packageManager version like
`"packageManager": "yarn@4.9.2"`, and when executing `yarn` Corepack
will securely fetch the verified version from npm, avoiding the risk of
executing a compromised binary committed to the repository. This was
already in our app's package.json template but we were not using it!

We can now
- remove the folder containing the binary from our app template
base-application (that is scaffolded when creating an app through cli),
`.yarn/releases/`, and remove `yarnPath: .yarn/releases/yarn-4.9.2.cjs`
from its .yarnrc
- remove them from the community apps that were already published in the
repo
- add .yarn to gitignore 

**Tested**
This has been tested and works for app created in the repo, outside the
repo, and existing apps in the repo
2026-01-21 17:23:16 +00:00
Paul RastoinandGitHub 6aa43b68f7 Identification cleanup (#17301)
# Introduction

following https://github.com/twentyhq/twenty/pull/17279
As we've finally identified all the syncable metadata entities, which
means they're expected to have non nullable applicationId and
universalIdentifier at pg_level we can remove previous retro comp
universalIdentifier fallbacking and update the dto too

~~This needs IdentifyRemainingEntitiesMetadataCommand and
MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
to be run~~
```ts
[Nest] 197  - 01/21/2026, 3:08:35 PM     LOG [MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand] Successfully run MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
```
2026-01-21 16:00:23 +00:00
Paul RastoinandGitHub 05f0d572e9 Refactor runner delete action to be workspace agnostic (#17276)
# Introduction

Straight to the point refactor passing only universalIdentifier in
workspace migration delete actions instead of id so it can be run on any
workspace

Related to
https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=132343284&issue=twentyhq%7Ccore-team-issues%7C1641
2026-01-21 15:57:52 +00:00
Lucas BordeauandGitHub d1a0aa3347 Fixed test file typing (#17307)
This PR fixes a typing bug introduced on main on a test file.
2026-01-21 15:53:03 +00:00
Thomas TrompetteandGitHub 2920dec6ba Add logs to debug workflow crons (#17302)
We have 3 crons that get a lot of timeout in sentry. 
Adding logs to help debugging.
2026-01-21 15:49:45 +00:00
bitloiandGitHub a4f5197e45 fix: implement polymorphic relation detach when selecting 'No {Field}' (#17264)
Fixes #17253

When clicking 'No {Field}' in a polymorphic relation picker, the
relation was not being cleared. This commit implements the missing
detach logic:

- RecordDetailMorphRelationSectionDropdownManyToOne: Call onSubmit with
newValue: null when no item is selected
- MorphRelationManyToOneFieldInput: Call persistMorphManyToOne with
valueToPersist: null for detach case
- useMorphPersistManyToOne: Implement actual detach logic that sets all
morph relation ID fields to null via updateOneRecord

Also adds unit tests for the useMorphPersistManyToOne hook.
2026-01-21 15:45:14 +00:00
nitinandGitHub 26a70c23e9 [Dashboards] fix line chart duplicate widget bleed by hashing series IDs (#17303)
closes last issue in the bug bucket -
https://github.com/twentyhq/core-team-issues/issues/2109

<img width="695" height="41" alt="CleanShot 2026-01-21 at 19 40 38"
src="https://github.com/user-attachments/assets/c6872e11-1e36-4b4e-ab06-9943c22cfab2"
/>


 ### Root cause - 
line chart series have stable IDs, so Apollo normalizes them across
duplicated widgets and X‑axis edits leak
 
 ### Fix - 
prefix series IDs with a deterministic hash of objectMetadataId +
configuration
 
 before - 
 


https://github.com/user-attachments/assets/538eded8-7036-415a-b707-a10007c3aa11


 


after - 


https://github.com/user-attachments/assets/cf9ca89a-e7c9-4106-b6cd-fbc072d9a233
2026-01-21 15:39:29 +00:00
Charles BochetandGitHub 5ee6853d7e Rework SDK watcher (#17305) 2026-01-21 16:18:54 +01:00
Paul RastoinandGitHub 54a9a1087b Fix identify remaining entities command to cover soft-deleted rows (#17304)
# Introduction
Some entities aren't passing the migration due to not covering the soft
deleted rows

## Entities that implements soft deletion
Inserted with universal programmatically
- rowLevelPermissionPredicate
- rowLevelPermissionPredicateGroup
- pageLayout
- pageLayoutTab
- pageLayoutWidget

Legacy might contains null
- viewFilterGroup
- serverlessFunction
- viewSort
2026-01-21 15:36:26 +01:00
Charles BochetandGitHub d74d74da4c Improvements on SDK watcher (#17291)
# Improve cross-entity duplicate detection in manifest validation

- Refactored findDuplicates to receive the full manifest, enabling
cross-entity duplicate checks
-ObjectExtensionEntityBuilder now validates that extension field IDs
don't conflict with object field IDs
- Renamed DuplicateId type to EntityIdWithLocation for clarity
- Updated all entity builders to use the new signature
2026-01-21 15:31:38 +01:00
neo773andGitHub 2ecc1ba897 fix gmail message import policy (#17272)
Gmail sync was importing messages from folders users had explicitly
disabled because the folder filtering logic lived in the parent service
but the Gmail driver wasn't aware of the import policy when building its
API queries.
Moved messageFolderImportPolicy down to the driver level so Gmail can
- Build proper -label: exclusions during initial sync
- Added `buildGmailLabelSearchName` as our syntax for nested folders was
wrong
- Filter out messages from disabled folders during incremental sync via
history API
2026-01-21 12:49:58 +00:00
Paul RastoinandGitHub f377727ff5 Update create entity cursor rule (#17299)
Following https://github.com/twentyhq/twenty/pull/17279
2026-01-21 10:59:19 +00:00
Lucas BordeauandGitHub 2835935f11 Handled SSE create event (#17293)
This PR handles SSE create event.

It also fixes a regression on board, which was making it flash with
skeletons on any update / create.

This PR was a good test case to experience how each main components of
the app : table, board, calendar, reacts to a create event.

It is not straightforward for now, because each component handles
records with its own state management to turn those records into a
coherent display of rows or cards.

The requests for each component are also different : fetch more, group
by, multiple requests in parallel, so the cleanest way to handle
optimistic effect requires to create one small optimistic engine per
component tuned for its internal data logic.

For now we've decided to implement what's doable in a reasonable amount
of time and that includes not handling table with groups for now.

Creating a clean optimistic logic for each component will be done later.

# QA

## Importing 100 records via the API (could be a script, a workflow, an
AI calling tools, a CSV import, etc.)


https://github.com/user-attachments/assets/d38a3770-8b0a-4f83-8275-5e7d1be0a5c6

## Creating from different components and seeing the SSE event being
processed by other components


https://github.com/user-attachments/assets/106b2f49-19cb-4190-92b7-0653c8373366
2026-01-21 10:49:20 +00:00
Paul RastoinandGitHub 596b7cc62d Deprecate nullable syncableEntity (#17279)
# Introduction
As we've been identifying both standard and custom entities for all the
metadata that had standard
We now still need to identify all custom entities enforcing them to have
an `applicationId` and `universalIdentifier`

In this PR we've removed the `SyncableEntityRequired` in favor requiring
props directly in the `SyncableEntity`
Which means that all metadata in db will now expect non nullable
applicationId and universalIdentifier across the whole application

Will add some type cleanup later in
https://github.com/twentyhq/twenty/pull/17277
2026-01-21 10:23:55 +00:00
Baptiste DevessierandGitHub b56f45e871 Remove extra spacing for empty widgets (#17289)
See the _Account Owner_ field

## Before

<img width="1148" height="2120" alt="CleanShot 2026-01-20 at 16 10
11@2x"
src="https://github.com/user-attachments/assets/5b726009-e68c-492e-bb36-de42062db6bd"
/>

## After

<img width="1148" height="2120" alt="CleanShot 2026-01-20 at 16 09
44@2x"
src="https://github.com/user-attachments/assets/f421efa3-d0f7-4ec0-93c0-a6b1f5d0e36c"
/>

<img width="1148" height="2120" alt="CleanShot 2026-01-20 at 16 09
57@2x"
src="https://github.com/user-attachments/assets/636ed2f0-34d8-4bd5-a47c-aef2121d1cbc"
/>
2026-01-21 09:21:26 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>DevessierDevessier
9b7953e7a3 Fix widget injection order: place relation widgets after FIELDS, Notes after relations (#17274)
> [!NOTE]
> This code is temporary. It will be dropped once Record Page Layouts
can be fully configured.

## Before



https://github.com/user-attachments/assets/3f6aed00-2fd5-47d4-bd74-002a68030627



## After

<img width="3456" height="2160" alt="CleanShot 2026-01-20 at 15 17
15@2x"
src="https://github.com/user-attachments/assets/2f9baf0a-e003-4cb6-a023-6e104b097df3"
/>

The `usePageLayoutWithRelationWidgets` hook was appending relation
widgets to the end of the widget list. This resulted in incorrect
ordering where relation widgets appeared after Notes and other widgets
instead of immediately following the FIELDS widget.

## Changes

- **Widget injection logic**: Modified `injectRelationWidgetsIntoLayout`
to find the first FIELDS widget and insert relation widgets immediately
after it, rather than appending to end
- **Notes positioning**: Extract and reposition NOTES widget to appear
after all relation widgets, maintaining correct semantic order: `FIELDS
→ Relations → Notes → Other widgets`
- **Fallback behavior**: When no FIELDS widget exists, append relation
widgets to end as before
- **Test coverage**: Added 6 test cases covering injection order, Notes
positioning, and edge cases (missing widgets, empty relations,
non-record pages)

## Example

Before:
```
[FIELDS, NOTES, GRAPH] → [FIELDS, NOTES, GRAPH, Relation1, Relation2]
```

After:
```
[FIELDS, NOTES, GRAPH] → [FIELDS, Relation1, Relation2, NOTES, GRAPH]
```

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `googlechromelabs.github.io`
> - Triggering command: `/usr/local/bin/node /usr/local/bin/node
install.mjs` (dns block)
> -
`https://storage.googleapis.com/chrome-for-testing-public/127.0.6533.88/linux64/chrome-headless-shell-linux64.zip`
> - Triggering command: `/usr/local/bin/node /usr/local/bin/node
install.mjs` (http block)
> -
`https://storage.googleapis.com/chrome-for-testing-public/127.0.6533.88/linux64/chrome-linux64.zip`
> - Triggering command: `/usr/local/bin/node /usr/local/bin/node
install.mjs` (http block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/twentyhq/twenty/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

Start implementation

<analysis>
[Chronological Review: The conversation began with the user requesting
an update to the `usePageLayoutWithRelationWidgets` function to inject
relation widgets after the first FIELDS widget and to position a Note
widget after all relation widgets. The user then asked for a plan
without comments, followed by a request to start the implementation of
the plan.]

[Intent Mapping: 
1. "Update #sym:usePageLayoutWithRelationWidgets. We should inject the
relation widgets directly after the first FIELDS widget."
2. "We want the Note widget to be after all the relation widgets we
inject."
3. "Prepare a plan. Don't write any comment."
4. "Start implementation."]

[Technical Inventory: 
- Function: `usePageLayoutWithRelationWidgets`
- Context: This function is part of a page layout management system,
likely dealing with dynamic widget placement based on certain
conditions.]

[Code Archaeology: 
- File:
`/Users/devessier/Web/twenty/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutWithRelationWidgets.ts`
- Function Purpose: To manage the layout of widgets on a page,
specifically handling relation widgets and their placement relative to
other widgets.]

[Progress Assessment: 
- Completed: User has defined the requirements for widget placement.
- Pending: Implementation of the specified changes to the
`usePageLayoutWithRelationWidgets` function.]

[Context Validation: All necessary context for continuing the
implementation of the widget placement logic is captured, including the
specific order of widget injection.]

[Recent Commands Analysis: 
- Last Agent Commands: The user requested to "Start implementation."
- Tool Results Summary: No specific tool results were generated as the
conversation focused on planning and implementation requests.
- Pre-Summary State: The agent was actively preparing to implement the
changes to the `usePageLayoutWithRelationWidgets` function when the
token budget was exceeded.
- Operation Context: The commands executed were directly related to the
user's goal of modifying the widget layout in the application.]

</analysis>

<summary>
1. Conversation Overview:
- Primary Objectives: 
- "Update #sym:usePageLayoutWithRelationWidgets. We should inject the
relation widgets directly after the first FIELDS widget."
- "We want the Note widget to be after all the relation widgets we
inject."
  - "Prepare a plan. Don't write any comment."
  - "Start implementation."
- Session Context: The user is focused on modifying the widget layout in
a page layout management system, specifically the order of relation and
Note widgets.
- User Intent Evolution: The user transitioned from defining
requirements to requesting a plan and then moving directly to
implementation.

2. Technical Foundation:
- Function: `usePageLayoutWithRelationWidgets`
- Purpose: To manage the layout of widgets on a page, particularly the
placement of relation widgets.

3. Codebase Status:
- File Name: `usePageLayoutWithRelationWidgets.ts`
- Purpose: To handle the layout of widgets dynamically based on
specified rules.
- Current State: Awaiting implementation of the user-defined widget
placement logic.
- Key Code Segments: The function signature is defined, but the
implementation details are yet to be added.

4. Problem Resolution:
- Issues Encountered: No specific technical problems were reported; the
focus was on planning and implementation.
- Solutions Implemented: None yet, as the implementation phase has just
begun.
- Debugging Context: No ongoing troubleshooting efforts were mentioned.
- Lessons Learned: The importance of clear widget placement requirements
was emphasized.

5. Progress Tracking:
- Completed Tasks: User has articulated the requirements for widget
placement.
- Partially Complete Work: Implementation of the specified changes is
pending.
- Validated Outcomes: No features have been confirmed working yet as
implementation has not started.

6. Active Work State:
- Current Focus: The user is preparing to implement the changes to the
`usePageLayoutWithRelationWidgets` function.
- Recent Context: The user has defined the order of widget placement and
is ready to start coding.
- Working Code: The function is currently defined but lacks the
implementation logic.
- Immediate Context: The user is focused on implementing the logic for
injecting relation widgets and positioning the Note widget.

7. Recent Operations:
- Last Agent Commands: "Start implementation."
- Tool Results Summary: No specific results were generated; the focus
was on user requests.
- Pre-Summary State: The agent was preparing to implement the changes to
the `usePageLayoutWithRelationWidgets` function.
- Operation Context: The commands executed were directly related to the
user's goal of modifying the widget layout.

8. Continuation Plan:
- Pending Task 1: Implement the logic to inject relation widgets after
the first FIELDS widget.
- Pending Task 2: Ensure the Note widget is positioned after all
relation widgets...

</details>



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

Created from [VS
Code](https://code.visualstudio.com/docs/copilot/copilot-coding-agent).

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

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
2026-01-21 08:38:08 +00:00
6b97e3b428 i18n - translations (#17296)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-20 22:21:02 +01:00
3ed67b825e feat: implement generic many-to-many junction relation support (#16820)
## Overview

This PR implements **generic many-to-many relation support** through
junction tables (also known as associative entities or join tables).
This replaces the need for hardcoded taskTarget/noteTarget logic and
provides a flexible foundation for modeling complex entity
relationships.

## Architecture

### Data Model

Many-to-many relationships are implemented using a **junction object
pattern**:

```
┌─────────┐       ┌──────────────────┐       ┌─────────┐
│  Pet    │──────>│   PetRocket      │<──────│ Rocket  │
│         │ 1:N   │  (junction)      │  N:1  │         │
│ rockets ├───────┤ pet    : Pet     ├───────┤         │
└─────────┘       │ rocket : Rocket  │       └─────────┘
                  └──────────────────┘
```

The junction object (PetRocket) has:
- A `MANY_TO_ONE` relation to **Pet** (the source)
- A `MANY_TO_ONE` relation to **Rocket** (the target)

The source object (Pet) has a `ONE_TO_MANY` relation pointing to the
junction, with **field settings** that specify which target field to
follow.

### Field Settings Schema

Junction configuration is stored in `FieldMetadataRelationSettings`:

```typescript
{
  relationType: "ONE_TO_MANY",
  // Points to the target field on the junction object
  junctionTargetFieldId?: string;      // For regular relations
  junctionTargetMorphId?: string;      // For polymorphic relations
}
```

**Two configuration modes:**
1. **`junctionTargetFieldId`** - References a specific `RELATION` field
on the junction
2. **`junctionTargetMorphId`** - References a `morphId` group for
polymorphic targets (e.g., link to Person OR Company)

### GraphQL Query Generation

When a junction relation is detected, the GraphQL fields are generated
to fetch the nested target:

```graphql
query GetPetWithRockets {
  pet(id: "...") {
    rockets {           # ONE_TO_MANY to junction
      id
      rocket {          # Target field on junction
        id
        name
        __typename
      }
    }
  }
}
```

For polymorphic junction targets:
```graphql
caretakerPerson { id, name }
caretakerCompany { id, name }
```

## Frontend Architecture

### Display Flow

1. **Detection**: `hasJunctionConfig()` checks if field has junction
settings
2. **Config Resolution**: `getJunctionConfig()` resolves junction object
metadata and target fields
3. **Record Extraction**: `extractTargetRecordsFromJunction()` extracts
target records from junction records
4. **Rendering**: Target records displayed as chips (not junction
records)

### Edit Flow

1. **Picker Opening**: Initializes the multi-record picker with:
   - Searchable object types (derived from junction target fields)
   - Pre-selected items (extracted from existing junction records)
   
2. **Selection Handling**: Manages create/delete of junction records:
   - **Select**: Creates new junction record with source + target IDs
   - **Deselect**: Finds and deletes the junction record
- **Optimistic Updates**: Manually updates Recoil store before API call

### Key Trade-offs

| Decision | Trade-off |
|----------|-----------|
| Junction records managed manually | More control over optimistic
updates, but requires manual cache management |
| Settings stored per-field | Flexible (same junction can power
different views), but requires UI to configure |
| Polymorphic via morphId groups | Supports N target types, but adds
query complexity |
| Feature flag gated | Safe rollout, but requires flag management |

## Backend Changes

- **Validation**: Junction target field must exist and be a valid
`MANY_TO_ONE` relation
- **Settings**: Extended `FieldMetadataRelationSettings` type with
junction fields
- **Dev Seeder**: Added sample junction objects (PetRocket,
EmploymentHistory, PetCareAgreement) for testing

## How to Test

1. Enable the `IS_JUNCTION_RELATIONS_ENABLED` feature flag
2. Create objects with junction pattern (Pet → PetRocket → Rocket)
3. Configure the junction target in field settings (advanced mode)
4. Verify:
- Display shows target objects (Rockets), not junction records
(PetRockets)
   - Picker allows selecting/deselecting targets
   - Changes persist correctly



https://github.com/user-attachments/assets/d04f057a-228c-4de8-af48-76bb2d72cac1

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-20 21:58:13 +01:00
0e7e471312 [Dashboards] fix tooltip item ordering to match visual stacking in bar and line chart (#17288)
related to the second bug from this bucket -
https://github.com/twentyhq/core-team-issues/issues/2109

<img width="929" height="509" alt="CleanShot 2026-01-20 at 20 40 20"
src="https://github.com/user-attachments/assets/c29d86af-faec-48da-92d6-e4dd695ce91f"
/>

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2026-01-20 19:35:42 +00:00
b1bd749abd i18n - translations (#17295)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-20 20:07:22 +01:00
Paul RastoinandGitHub e35b4b86cb Migrate serverless function service to v2 (#17285)
# Introduction

In this PR we're migrating the serverless function service that was
using the SF repo directly to the v2 build and runner.
The whole serverless engine now deals with flat entities only

## Resolvers
Refactored the resolvers ( serverlessFunction, route, database and cron
trigger) :
- return types to `dto`
- Standardized the flat to dto transpilation within the resolvers
- Find and findMany passing by the cached data

## Services
Refactored the services ( serverlessFunction, route, database and cron
trigger) :
- return type to be `flat`
- always calling v2 and computing cache

## New additional caches
- application variables ( cf
https://github.com/twentyhq/core-team-issues/issues/2116 )
- serverless function layer

## What to test:
- CRUD ( database trigger  , route trigger, cron trigger, serverless
function through workflows  )
- Duplicating a workflow with a serverless function code node  

## Concerns
We need to implement the cron that will hard delete soft deleted s3
serverless functions, not in this PR though ( cf
https://github.com/twentyhq/twenty/pull/17285#discussion_r2709168570 and
https://github.com/twentyhq/core-team-issues/issues/2118 )
2026-01-20 18:32:22 +00:00
ccf2eae684 i18n - translations (#17294)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-20 18:40:35 +01:00
EtienneandGitHub 30c13fc947 Files v2 - Add new FILES field type (#17236)
In this PR : 
- New field type FieldMetadataType.FILES added (as jsonb in DB)
- Backend: GraphQL types, validation, REST API schema, data processor
handling
- Frontend: field configuration in settings
- Feature flag IS_FILES_FIELD_ENABLED to gate the feature
- Integration tests for create/filter validation
- Dev seed: Feature flag enabled + FILES field on survey result object


To do in next PRs : 
- Backend : 
-- new workspaceFile controller (for download) & new workspaceFile
upload resolver (for upload)
-- pre-hook/post-hook to ensure fileId existence + listener to ensure
cleaning
- Frontend : FILES field display/edit in table view, record, ...
2026-01-20 17:03:49 +00:00
41329a0ea9 i18n - translations (#17292)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-20 18:09:44 +01:00
Thomas TrompetteandGitHub 799b08e1f2 Stop workflow in any status (#17271)
Workflows can now be cancelled when not started or enqueued.
Also displaying the stop option when selecting all.

We got the case of a client that did an infinite loop. So he had a lot
of workflows to stop. Supporting select all would have avoid him to wait
for 4 hours so we process the runs throttled. This is not something that
is supposed to happen often so I did not see the point of refactoring
the endpoint. But I wanted the users to have this option just in case
2026-01-20 16:38:39 +00:00
e40130f575 i18n - translations (#17290)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-20 17:42:30 +01:00
9cf88df67b Improve streamToBuffer error handling and memory safety (#17255)
## Description

This PR improves error handling and memory safety for the streamToBuffer
utility function.

### Changes
- Add proper cleanup of event listeners to prevent memory leaks
- Add checks for already-ended streams to handle edge cases gracefully
- Add protection against multiple resolve/reject calls with isResolved
flag
- Add handling for close events with appropriate error messages
- Improve robustness when streams are destroyed or closed externally

### Impact
This fixes potential memory leaks and race conditions when streams are
cancelled, destroyed, or closed before completion.

### Code Statistics
- 1 file changed
- ~53 lines added, 9 lines modified
- ~95% code changes (functional improvements)

---------

Co-authored-by: GitTensor Miner <miner@gittensor.io>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-01-20 16:19:47 +00:00
Charles BochetandGitHub 351e2030ff Rework watcher (#17284)
Heavy Refactoring of the watcher, sorry about this one, I'll keep
iterating on it.

In a nutshell:
- app-dev.ts is maintaining 3 watchers in parallel: manifest, function
and frontComponent
2026-01-20 17:25:44 +01:00
WeikoandGitHub b6635ba272 Add RLS Entitlement check (#17179)
## Context
- Add RLS entitlement to billing
- Check value in the backend (for RLS predicate entity
queries/mutations)
- Expose billingEntitlements to the API inside currentWorkspace to check
available features to the workspace and display the role components
accordingly
- Cleanup RLS when plan changes back to one without RLS.

This should cover almost everything, imho we don't need to check in the
ORM because => We can't create RLS without the correct PLAN and
switching back to a PLAN without RLS deletes existing RLS through
stripes webhooks
2026-01-20 16:06:37 +00:00
martmullandGitHub 579c59bd11 2093 extensibility add twenty auth switch command (#17286)
add auth:switch and auth:list commands
2026-01-20 15:12:59 +00:00
Thomas TrompetteandGitHub e599d6c4d0 Handle dynamic predicates (#17287)
Fetch and send full workspace member to handle dynamic predicate. I only
tested that it did not break the current logic since the frontend does
not yet send queries we dynamic predicates.
2026-01-20 14:57:36 +00:00
martmullandGitHub 02a181a49f Add endpoint to upload application file (#17270)
As title
2026-01-20 14:10:34 +00:00
martmullandGitHub 801878cb2b 2091 extensibility twenty sdk add command twenty app function logs and twenty app function test (#17278)
add `function:logs` and `function:execute` cli commands
2026-01-20 14:10:01 +00:00
Raphaël BosiandGitHub f9c400cd78 Fix hotkey bugs (#17273)
- Fix a race condition due to a page change effect re-trigger which
reset the focus stack (A user had a bug which happened when he opened
the command menu quickly after the page load. The focus stack was reset
and the hotkeys listening were the one from the table instead of the one
from the command menu)
- Fix the focus id in the side panel input which prevented using the
hotkeys
2026-01-20 12:19:58 +00:00
nitinandGitHub a004662221 [Dashboards] align cumulative range filtering with raw-value filters (#17265)
closes https://github.com/twentyhq/core-team-issues/issues/2097

- Align cumulative range filtering with non‑cumulative behavior by
applying rangeMin/rangeMax to raw values before cumulative totals are
computed.
- Remove cumulative‑value filtering to prevent hidden points from
influencing visible totals.
2026-01-20 12:15:24 +00:00
nitinandGitHub 196de28a0c [Dashboards] add missing hideEmptyCategory field to PieChartConfiguration GraphQL fragment (#17266)
related to the first bug from this bucket -
https://github.com/twentyhq/core-team-issues/issues/2109

<img width="761" height="75" alt="CleanShot 2026-01-20 at 13 20 23"
src="https://github.com/user-attachments/assets/c30712ce-3959-4647-9178-68a2b0230c03"
/>
2026-01-20 10:54:15 +00:00
Charles BochetandGitHub c388b29f7c Simplify config file script in sdk (#17267)
Some simplifications about loading the manifest as there is no
difference between all entities
2026-01-20 12:03:36 +01:00
EtienneandGitHub 472fe4eec3 Fix dev env (#17269) 2026-01-20 09:57:20 +00:00
Charles BochetandGitHub 29c0c952e7 Add Front components to SDK (#17259)
## Add `frontComponent` entity type to SDK

This PR adds a new `frontComponent` entity type at parity with
`serverlessFunction`. Front components are React components that can be
defined and bundled as part of Twenty applications.

### Changes

#### New Entity Type
- Added `FRONT_COMPONENT` to `SyncableEntity` enum
- Front components use `*.front-component.tsx` file naming convention
- CLI command `twenty app:add` now includes `front-component` as an
option

#### SDK Application Layer (`twenty-sdk`)
- Added `defineFrontComponent()` function for defining front component
configurations with validation
- Added `FrontComponentConfig` type for component configuration
- Added `getFrontComponentBaseFile()` template generator for scaffolding
new front components
- Added `loadFrontComponentModule()` to load front component modules and
extract component metadata

#### Shared Types (`twenty-shared`)
- Added `FrontComponentManifest` type with `universalIdentifier`,
`name`, `description`, `componentPath`, and `componentName` fields
- Updated `ApplicationManifest` to include optional `frontComponents`
array

#### Manifest Build System
- Updated `manifest-build.ts` to discover and load
`*.front-component.tsx` files
- Updated `manifest-validate.ts` to validate front components and check
for duplicate IDs
- Updated `manifest-display.ts` to display front component count in
build summary
- Updated `manifest-plugin.ts` to display front component entry points
and watch `.tsx` files

#### Template (`create-twenty-app`)
- New applications now include a sample
`hello-world.front-component.tsx` file

#### Tests
- Added unit tests for `defineFrontComponent()`
- Added unit tests for `getFrontComponentBaseFile()`
2026-01-20 05:54:14 +00:00
WeikoandGitHub b64951634e fix rls create new record with composite (#17243)
## Context
buildValueFromFilter was not handling composite filters which was needed
for RLS. This PR implements that and fix some issues with RLS

Tested with a few composite + relation fields
<img width="559" height="206" alt="Screenshot 2026-01-19 at 15 38 42"
src="https://github.com/user-attachments/assets/d64afa6e-3e12-4843-a215-a693665112c5"
/>
2026-01-20 05:36:38 +00:00
39470b74a4 i18n - docs translations (#17260)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 19:40:33 +01:00
neo773andGitHub 527c00d326 refresh oAuth tokens when sending email (#17250)
calls `connectedAccountRefreshTokensService` in `SendEmailTool`
resolving error `Lifetime validation failed, the token is expired.`
2026-01-19 17:54:07 +00:00
038f9cc44f i18n - docs translations (#17251)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 19:02:22 +01:00
dc982abb78 i18n - translations (#17258)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 19:01:11 +01:00
Charles BochetandGitHub 70582d063e Twenty SDK watch build of functions (#17252)
## Summary

This PR adds function build support to the `twenty dev` command,
enabling tree-shaken production builds of serverless functions during
development with Vite's incremental build capabilities.

## Changes

### New Features
- **Function building in dev mode**: Serverless functions are now
compiled to tree-shaken bundles in `.twenty/functions/` during
development
- **Automatic restart on entry point changes**: When functions are added
or removed, the watcher automatically restarts with the new
configuration
- **Function entry point logging**: Dev mode now displays detected
function entry points with their names and paths
2026-01-19 17:43:40 +00:00
042b15a7d1 i18n - translations (#17257)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 18:49:13 +01:00
MarieandGitHub 1731c3427e [App] Small fixes (#17254)
- update doc
- update generated Twenty client following [comment on previous
pr](https://github.com/twentyhq/twenty/pull/17240#pullrequestreview-3678830392)
2026-01-19 17:32:43 +00:00
Raphaël BosiandGitHub d0bc8b9ffe [DASHBOARDS] Move all the graph computing logic to the backend (#17189)
- Create resolvers for each type of charts which needs data
transformation after the group by operation: Bar Chart, Line Chart and
Pie Chart
- Move all the utils to the backend and refactored some into services

This allows all the computation to be done in the backend, improving
performances in the frontend.
2026-01-19 17:25:45 +00:00
WeikoandGitHub 1f1ccb281e Cache WorkspaceMember (#17247)
## Context
Due to RLS feature, workspaceMember entity and its properties is
becoming necessary in different places of the backend. To avoid querying
many times, this PR adds a map of workspace members per workspaceId,
leveraging WorkspaceCache pattern for WorkspaceEntity (in opposition
with CoreEntity)

Due to its content (mostly PII), I prefer to cache it locally only (node
process VS Redis) using localDataOnly.

TODO: invalidation logic when the workspaceMember object is updated
(more precisely, when its field map is updated). There is no easy way
today to update that list so it can be done in another PR imho
2026-01-19 17:13:10 +00:00
Thomas TrompetteandGitHub b15c042e77 SSE - Protect query api (#17241)
- Ensure user can only add/remove queries of its own stream
- Prevent stream from being highjacked
2026-01-19 17:12:41 +00:00
83d9c580a0 i18n - translations (#17256)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 18:21:37 +01:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>DevessierBaptiste Devessier
4ce6119be7 Add progressive loading to FIELD widget CARD display mode (#17202)
https://github.com/user-attachments/assets/3a444937-0290-4505-85e1-64b7b713bc3b



- [x] Analyze the existing `FieldWidgetRelationCard` and
`FieldWidgetMorphRelationCard` components
- [x] Review existing patterns for "More" buttons (found
`IconChevronDown` pattern)
- [x] Create a reusable `FieldWidgetShowMoreButton` component for the
"More (X)" button
- [x] Update `FieldWidgetRelationCard` to show max 5 items initially
with progressive loading
- [x] Update `FieldWidgetMorphRelationCard` to show max 5 items
initially with progressive loading
- [x] Add Storybook story with play function to test progressive loading
for regular relations
- [x] Verify changes visually in Storybook (all tests pass)
- [x] Run code review (no issues found)
- [x] Run CodeQL security check (no code changes for CodeQL to analyze)
- [x] Address PR review feedback:
- [x] Move constants to separate files
(`FieldWidgetRelationCardInitialVisibleItems.ts` and
`FieldWidgetRelationCardLoadMoreIncrement.ts`)
- [x] Add translation for "More (X)" string using `t` macro from
`@lingui/core/macro`

## Summary

This PR adds progressive loading functionality to the FIELD widget with
CARD display mode:

1. **New Component**: Created `FieldWidgetShowMoreButton` - A styled
button component that displays "More (X)" with a chevron icon, showing
the remaining count of hidden items.

2. **Updated Components**:
- `FieldWidgetRelationCard`: Now displays max 5 relation cards
initially, with a "More (X)" button that loads 5 more items on each
click
- `FieldWidgetMorphRelationCard`: Same progressive loading behavior for
morph relations

3. **Constants**: Moved to separate files following codebase
conventions:
   - `FIELD_WIDGET_RELATION_CARD_INITIAL_VISIBLE_ITEMS` (5)
   - `FIELD_WIDGET_RELATION_CARD_LOAD_MORE_INCREMENT` (5)

4. **Storybook Story**: Added
`OneToManyRelationCardWidgetWithProgressiveLoading` story with play
function that tests:
   - Initial display of 5 items
   - "More (7)" button visibility
   - Loading additional items on click
   - Button disappearing when all items are shown

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[RPL] Allow users to progressively load relations in
FIELD widget with CARD display mode</issue_title>
> <issue_description>## Current state
> 
> The FIELD widget should display max 5 relations in CARD display mode.
> 
> ## Goal
> 
> Display a "More (12)" button. Clicking on this button loads 5 more
items. After a click, "More (12)" becomes "More (7)".
> 
> <img width="1334" height="712" alt="Image"
src="https://github.com/user-attachments/assets/e801cca2-af99-4d87-8ce2-0c639775b0b1"
/>
> 
> ## Technical input
> 
> `FieldWidgetRelationCard` already loads all the relations and maps
over each one of them. Create a state that's updated when clicking on
the More button, slicing the relations to display.
> 
> ## Acceptance criteria
> 
> - Must work for relations and morph relations in _card_ display mode
> - Create stories to assert it works properly (with play function to
actually test the components)</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



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

- Fixes twentyhq/core-team-issues#2063

<!-- 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] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>
2026-01-19 16:58:23 +00:00
Charles BochetandGitHub 5a5e9eeb9e Watch command skeleton (#17246)
Summary

Rewrites the app:dev command to use Vite's dev server instead of manual
file watching with chokidar. This provides better file watching
capabilities and aligns with the existing build tooling.

<img width="1588" height="822" alt="image"
src="https://github.com/user-attachments/assets/7c54bd39-4905-456f-8e3d-64e97b031de0"
/>
2026-01-19 16:05:49 +00:00
374303455a i18n - translations (#17249)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 17:02:08 +01:00
Abdul RahmanandGitHub 3c26ebace2 Fix workflow if-else node issues (#17244) 2026-01-19 15:42:06 +00:00
Paul RastoinandGitHub 28e98086b0 Identify index (#17239)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids

## Test
tested prod extract
2026-01-19 15:36:41 +00:00
e0d1edb940 i18n - translations (#17248)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-19 16:50:31 +01:00
MarieandGitHub 1a38152451 Support referencing updatedFields array in databaseEvent triggers (#17240)
Closes https://github.com/twentyhq/core-team-issues/issues/1838

DatabaseEventTriggers can now define a field-level granularity on which
updates they should react to.
After a discussion with the team, we have decided to rely on field names
and not UID, just as we do for objects. The rationale is that we want to
keep a pleasant devX and consider it's not on twenty to ensure
continuity of api usage around an object if their name is updated: for
instance if a user has based their webhook on the name of an object, if
they decide to update it, they have to take care of updating their
webhook.
2026-01-19 15:33:49 +00:00
EtienneandGitHub f381516d30 BILLING - Send dis-sync error to sentry (#17242)
Investigate Stripe activity when workspace not found, creating
[alert](https://twenty-v7.sentry.io/issues/alerts/rules/twenty-server/16611050/details/)
in Sentry
2026-01-19 15:31:37 +00:00
Paul RastoinandGitHub 59c7295033 Identify standard role (#17234)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids

## Test
tested prod extract

## Note
Added build to typeorm nx generate migration command so we never forgot
to build server before
2026-01-19 15:04:33 +00:00
Charles BochetandGitHub 905898d109 Twenty SDK command renaming and dev mode (#17245)
## Description

This PR improves the developer experience for the `twenty-sdk` and
`create-twenty-app` packages by reorganizing commands, adding
development tooling, and improving documentation.

## Changes

### twenty-sdk

#### Command Refactoring
- **Renamed `app-watch` → `app-dev`**: Renamed `AppWatchCommand` to
`AppDevCommand` and moved to `app-dev.ts` for consistent naming with the
CLI command `app:dev`
- **Moved `app-add` → `entity-add`**: Relocated entity creation logic
from `app/app-add.ts` to `entity/entity-add.ts` and renamed
`AppAddCommand` to `EntityAddCommand` for better separation of concerns

#### Development Tooling
- **Added `dev` target**: New Nx target `npx nx run twenty-sdk:dev` that
runs the build in watch mode for faster development iteration

### create-twenty-app

#### Improved Scaffolded Project
- **Enhanced base-application README** with:
  - Updated Getting Started to recommend `yarn dev` for development
  - Added "Available Commands" section listing all available scripts
  
#### Better Onboarding
- **Improved success message** after app creation with formatted next
steps:
2026-01-19 16:02:12 +01:00
martmullandGitHub 3958664e87 Move assets folder at root level (#17238)
fix assets management
2026-01-19 16:01:54 +01:00
Abdullah.andGitHub 3c0314d14a fix: jsdiff has a denial of service vulnerability in parsePatch and applyPatch (#17235)
Resolves [Dependabot Alert
366](https://github.com/twentyhq/twenty/security/dependabot/366).
2026-01-19 14:19:18 +00:00
Charles BochetandGitHub e61b92132c Rework folder structure (#17230)
<img width="700" height="771" alt="image"
src="https://github.com/user-attachments/assets/b784fbe6-f976-4c9a-84c1-3d90a9010665"
/>

Second one :)
2026-01-19 14:08:05 +00:00
Thomas TrompetteandGitHub 7cf0dc522a Add permissions to SSE (#17201)
Flow:
- fetch user role from context stored in cache - do not support api
context yet
- check object permissions
- filter restricted fields on event
- fetch role rls predicate and combine it with the query filter

Do not support dynamic predicates yet.
2026-01-19 13:39:12 +00:00
Baptiste DevessierandGitHub 2505c631de fix: ensure we display fields in the same order as in production (#17231)
- Do not render fields that we don't want to render for now
- Preverse alphabetical order

## Without feature flag

<img width="3456" height="2160" alt="CleanShot 2026-01-19 at 11 02
57@2x"
src="https://github.com/user-attachments/assets/886116b5-6e17-4b68-96b6-ab1ed491a1e5"
/>

## With feature flag

<img width="3456" height="2234" alt="CleanShot 2026-01-19 at 11 03
31@2x"
src="https://github.com/user-attachments/assets/9bf47ca3-bc34-47de-b8e5-9615eb684a05"
/>

## With feature flag–before

<img width="3456" height="2160" alt="CleanShot 2026-01-19 at 11 03
53@2x"
src="https://github.com/user-attachments/assets/71c7dc16-b3da-4dfa-86b2-2c2ec7ff0bd8"
/>
2026-01-19 13:18:36 +00:00
Paul RastoinandGitHub 859e718cf2 Identify view group (#17220)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids

## Test
tested prod extract
2026-01-19 12:56:52 +00:00
Félix MalfaitandGitHub 0b3f243f24 fix: E2E login test flakiness (#17233)
## Summary
- Fix E2E login test flakiness by using `click()` auto-waiting instead
of `isVisible()` check
- The login form shows a loader while GraphQL data loads. The previous
`isVisible()` check returned immediately (no waiting) and would fail
while the loader was showing
- Using `click()` which has built-in auto-waiting for elements to be
visible and actionable fixes this

## Test plan
- E2E tests should pass more reliably in CI
- Login setup test should no longer timeout waiting for the email field
2026-01-19 12:39:14 +00:00
4ab95235ce [Breaking: DEPLOY SERVER BEFORE FRONT] fix: allow custom domain without Cloudflare API key (#17160)
## Summary

Fixes #17101

When self-hosting TwentyCRM, users can now set workspace custom domains
without requiring CLOUDFLARE_API_KEY to be configured. This enables
manual DNS configuration for those not using Cloudflare.

## Changes

- Added isCloudflareConfigured method to DnsManagerService
- Modified all Cloudflare-dependent methods to gracefully handle missing
configuration
- Added getManualDnsRecords helper that provides DNS configuration
instructions when Cloudflare is not available
- isHostnameWorking returns true in manual mode, allowing the domain to
be saved and enabled
- Added comprehensive tests for non-Cloudflare scenarios

## Behavior

When CLOUDFLARE_API_KEY is set: Works exactly as before with automatic
Cloudflare provisioning

When CLOUDFLARE_API_KEY is NOT set:
- Custom domain can be saved to the database
- User receives manual DNS configuration instructions
- Domain is marked as working, user is responsible for external DNS and
TLS configuration

## Testing

Added tests covering non-Cloudflare scenarios. Full test suite requires
Docker which was not run locally.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces a Cloudflare integration feature flag and wires it through
server and front to gate the custom domain UI.
> 
> - Server: adds `isCloudflareIntegrationEnabled` to `ClientConfig`,
computed from `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ZONE_ID` in
`client-config.service`; updates GraphQL schema and unit tests.
> - Frontend: adds `isCloudflareIntegrationEnabledState`, extends
`ClientConfig` type, sets the flag in `useClientConfig`, and
conditionally renders `SettingsCustomDomain` in `SettingsDomain` when
enabled.
> - Updates mocked client config to include the new flag.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0c76dde03b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-01-19 12:47:09 +01:00
Félix MalfaitandGitHub dc93cf4c59 feat: add TypeScript Go (tsgo) for faster type checking (#17211)
## Summary

- Add `@typescript/native-preview` (tsgo) for dramatically faster type
checking on frontend projects
- Configure tsgo as default for frontend projects (twenty-front,
twenty-ui, twenty-shared, etc.)
- Keep tsc for twenty-server (faster for NestJS decorator-heavy code)
- Fix type imports for tsgo compatibility (DOMPurify, AxiosInstance)
- Remove deprecated `baseUrl` from tsconfigs where safe

## Performance Results

| Project | tsgo | tsc -b | Speedup |
|---------|------|--------|---------|
| **twenty-front** | 1.4s | 60.7s | **43x faster** |
| **twenty-server** | 2m42s | 1m10s | tsc is faster (decorators) |

tsgo excels at modern React/JSX codebases but struggles with
decorator-heavy NestJS backends, so we use the optimal checker for each.

## Usage

```bash
# Default (tsgo for frontend, tsc for backend)
nx typecheck twenty-front
nx typecheck twenty-server

# Force tsc fallback if needed
nx typecheck twenty-front --configuration=tsc

# Force tsgo on backend (slower, not recommended)
nx typecheck twenty-server --configuration=tsgo
```

## Test plan

- [x] `nx typecheck twenty-front` passes with tsgo
- [x] `nx typecheck twenty-server` passes with tsc
- [x] `nx run-many -t typecheck --exclude=fireflies` passes
- [ ] CI tests pass
2026-01-19 12:46:34 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
42c6a04be6 Bump eslint-plugin-prettier from 5.2.1 to 5.5.5 (#17228)
Bumps
[eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier)
from 5.2.1 to 5.5.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/eslint-plugin-prettier/releases">eslint-plugin-prettier's
releases</a>.</em></p>
<blockquote>
<h2>v5.5.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/772">#772</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/7264ed0a6cf47fc36befed32f459e7d875f5992c"><code>7264ed0</code></a>
Thanks <a href="https://github.com/BPScott"><code>@​BPScott</code></a>!
- Bump prettier-linter-helpers dependency to v1.0.1</p>
</li>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/776">#776</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/77651a33cd16fd4c50b7346971990b900a42408b"><code>77651a3</code></a>
Thanks <a href="https://github.com/aswils"><code>@​aswils</code></a>! -
fix: bump synckit for yarn PnP ESM issue</p>
</li>
</ul>
<h2>v5.5.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/755">#755</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/723f7a803f014746f2146e5be021c9071fa52d7e"><code>723f7a8</code></a>
Thanks <a href="https://github.com/kbrilla"><code>@​kbrilla</code></a>!
- fix: add 'oxc', 'oxc-ts' and 'hermes' parsers to
<code>parserBlocklist</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/751">#751</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/cf52b306a533b971bf40bbbf0d2033a1ed4f3c5d"><code>cf52b30</code></a>
Thanks <a
href="https://github.com/andreww2012"><code>@​andreww2012</code></a>! -
fix: disallow extra properties in rule options</p>
</li>
</ul>
<h2>v5.5.3</h2>
<p>republish the latest version</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.2...v5.5.3">https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.2...v5.5.3</a></p>
<h2>v5.5.2</h2>
<p>republish the latest version</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.1...v5.5.2">https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.1...v5.5.2</a></p>
<h2>v5.5.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/748">#748</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/bfd1e9547de9afaaf30318735f2f441c0250b77e"><code>bfd1e95</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- fix: use <code>prettierRcOptions</code> directly for prettier
3.6+</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.0...v5.5.1">https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.0...v5.5.1</a></p>
<h2>v5.5.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/743">#743</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/92f2c9c8f0b083a0208b4236cf5c8e4af5612a8b"><code>92f2c9c</code></a>
Thanks <a
href="https://github.com/dotcarmen"><code>@​dotcarmen</code></a>! -
feat: support non-js languages like <code>css</code> for
<code>@eslint/css</code> and <code>json</code> for
<code>@eslint/json</code></li>
</ul>
<h3>New Contributors</h3>
<ul>
<li><a href="https://github.com/dotcarmen"><code>@​dotcarmen</code></a>
made their first contribution in <a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/743">prettier/eslint-plugin-prettier#743</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.4.1...v5.5.0">https://github.com/prettier/eslint-plugin-prettier/compare/v5.4.1...v5.5.0</a></p>
<h2>v5.4.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/740">#740</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/c21521ffbe7bfb60bdca8cbf6349fba4de774d21"><code>c21521f</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- fix(deps): bump <code>synckit</code> to v0.11.7 to fix potential
<code>TypeError: Cannot read properties of undefined (reading
'message')</code> error</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.4.0...v5.4.1">https://github.com/prettier/eslint-plugin-prettier/compare/v5.4.0...v5.4.1</a></p>
<h2>v5.4.0</h2>
<h3>Minor Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/eslint-plugin-prettier/blob/main/CHANGELOG.md">eslint-plugin-prettier's
changelog</a>.</em></p>
<blockquote>
<h2>5.5.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/772">#772</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/7264ed0a6cf47fc36befed32f459e7d875f5992c"><code>7264ed0</code></a>
Thanks <a href="https://github.com/BPScott"><code>@​BPScott</code></a>!
- Bump prettier-linter-helpers dependency to v1.0.1</p>
</li>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/776">#776</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/77651a33cd16fd4c50b7346971990b900a42408b"><code>77651a3</code></a>
Thanks <a href="https://github.com/aswils"><code>@​aswils</code></a>! -
fix: bump synckit for yarn PnP ESM issue</p>
</li>
</ul>
<h2>5.5.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/755">#755</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/723f7a803f014746f2146e5be021c9071fa52d7e"><code>723f7a8</code></a>
Thanks <a href="https://github.com/kbrilla"><code>@​kbrilla</code></a>!
- fix: add 'oxc', 'oxc-ts' and 'hermes' parsers to
<code>parserBlocklist</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/751">#751</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/cf52b306a533b971bf40bbbf0d2033a1ed4f3c5d"><code>cf52b30</code></a>
Thanks <a
href="https://github.com/andreww2012"><code>@​andreww2012</code></a>! -
fix: disallow extra properties in rule options</p>
</li>
</ul>
<h2>5.5.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/748">#748</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/bfd1e9547de9afaaf30318735f2f441c0250b77e"><code>bfd1e95</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- fix: use <code>prettierRcOptions</code> directly for prettier
3.6+</li>
</ul>
<h2>5.5.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/743">#743</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/92f2c9c8f0b083a0208b4236cf5c8e4af5612a8b"><code>92f2c9c</code></a>
Thanks <a
href="https://github.com/dotcarmen"><code>@​dotcarmen</code></a>! -
feat: support non-js languages like <code>css</code> for
<code>@eslint/css</code> and <code>json</code> for
<code>@eslint/json</code></li>
</ul>
<h2>5.4.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/740">#740</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/c21521ffbe7bfb60bdca8cbf6349fba4de774d21"><code>c21521f</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- fix(deps): bump <code>synckit</code> to v0.11.7 to fix potential
<code>TypeError: Cannot read properties of undefined (reading
'message')</code> error</li>
</ul>
<h2>5.4.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/736">#736</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/59a0cae5f27801d7e00f257c6be059a848b32fbe"><code>59a0cae</code></a>
Thanks <a
href="https://github.com/yashtech00"><code>@​yashtech00</code></a>! -
refactor: migrate <code>worker.js</code> to <code>worker.mjs</code></li>
</ul>
<h2>5.3.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/pull/734">#734</a>
<a
href="https://github.com/prettier/eslint-plugin-prettier/commit/dcf2c8083e0f7146b7b7d641224ee2db8b318189"><code>dcf2c80</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- ci: enable <code>NPM_CONFIG_PROVENANCE</code> env</li>
</ul>
<h2>5.3.0</h2>
<h3>Minor Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/e2c154a7214d4548dad225a56ee1e333d6389b66"><code>e2c154a</code></a>
chore: release eslint-plugin-prettier (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/773">#773</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/6795c1abf6dc9949da8681b05ec31d323794d00c"><code>6795c1a</code></a>
build(deps): Bump the actions group across 1 directory with 2 updates
(<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/774">#774</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/77651a33cd16fd4c50b7346971990b900a42408b"><code>77651a3</code></a>
fix: bump synckit for yarn PnP ESM issue (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/776">#776</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/7264ed0a6cf47fc36befed32f459e7d875f5992c"><code>7264ed0</code></a>
chore: bump prettier-linter-helpers to v1.0.1 (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/772">#772</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/e11a5b7e71f41b3238da944ba1ee84f7f518a4f4"><code>e11a5b7</code></a>
build(deps): Bump the actions group across 1 directory with 3 updates
(<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/769">#769</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/befda88381335cd5491d2aaa16b67350ba3cc602"><code>befda88</code></a>
ci: enable trusted publishing (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/757">#757</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/e2c31d20f326133157a12d0989097ebd52860c5b"><code>e2c31d2</code></a>
chore: release eslint-plugin-prettier (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/756">#756</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/98a8bfd269f0f2ead6750ec88eb81f6d59b6c005"><code>98a8bfd</code></a>
chore(deps): update all dependencies (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/750">#750</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/cf52b306a533b971bf40bbbf0d2033a1ed4f3c5d"><code>cf52b30</code></a>
fix: disallow extra properties in rule options (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/751">#751</a>)</li>
<li><a
href="https://github.com/prettier/eslint-plugin-prettier/commit/723f7a803f014746f2146e5be021c9071fa52d7e"><code>723f7a8</code></a>
fix: add 'oxc', 'oxc-ts' and 'hermes' parsers to
<code>parserBlocklist</code> (<a
href="https://redirect.github.com/prettier/eslint-plugin-prettier/issues/755">#755</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/prettier/eslint-plugin-prettier/compare/v5.2.1...v5.5.5">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 eslint-plugin-prettier since your current
version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=eslint-plugin-prettier&package-manager=npm_and_yarn&previous-version=5.2.1&new-version=5.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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 10:26:33 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
58619e3b02 Bump json-2-csv from 5.5.5 to 5.5.10 (#17226)
Bumps [json-2-csv](https://github.com/mrodrig/json-2-csv) from 5.5.5 to
5.5.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mrodrig/json-2-csv/releases">json-2-csv's
releases</a>.</em></p>
<blockquote>
<h2>NPM Release v5.5.10</h2>
<ul>
<li>Ability to rename individual field headers - thanks to <a
href="https://github.com/petterw03"><code>@​petterw03</code></a> - <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/280">#280</a></li>
<li>NPM Audit Fix patches</li>
</ul>
<h2>NPM Release v5.5.9</h2>
<ul>
<li>Thanks to <a
href="https://github.com/jose-cabral"><code>@​jose-cabral</code></a> for
reporting <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/273">#273</a>
and implementing a fix for it in <a
href="https://redirect.github.com/mrodrig/json-2-csv/pull/275">mrodrig/json-2-csv#275</a></li>
</ul>
<h2>NPM Release v5.5.8</h2>
<ul>
<li>Incorporates <a
href="https://github.com/sevrai"><code>@​sevrai</code></a>'s fix from <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/271">#271</a></li>
<li>Dev Dependency vulnerability patch</li>
</ul>
<h2>NPM Release v5.5.7</h2>
<ul>
<li>Fixes the bug identified in <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/265">#265</a></li>
<li>Patches a high severity vulnerability identified in a dependency by
<code>npm audit</code></li>
</ul>
<h2>NPM Release v5.5.6</h2>
<ul>
<li>Thanks <a href="https://github.com/Altioc"><code>@​Altioc</code></a>
for finding and fixing <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/264">#264</a></li>
<li>Patches a moderate severity vulnerability in the dependency chain
via <code>npm audit fix</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/dd154d63966b8edd9396d3c323d16aa1597e3e3a"><code>dd154d6</code></a>
Merge pull request <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/283">#283</a>
from mrodrig/mr-patch-oct25</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/2c5da6c5c3f8a876d3d6b781c1f0e0ca19200e05"><code>2c5da6c</code></a>
chore(rel): 5.5.10</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/6db4ca1ce1cc903cfa0fe7411a7e4b037b231998"><code>6db4ca1</code></a>
chore(deps): npm audit fix</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/e3a9bcfbf2991a8fece6e85d33a56054635a1c39"><code>e3a9bcf</code></a>
Merge pull request <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/280">#280</a>
from petterw03/rename-header-fields</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/a617948ae26649196a3e9e3c06e7df701a4dba89"><code>a617948</code></a>
add documentation for fieldTitleMap</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/d057d2423da089f72f5cb627bc26b5a64d96ee27"><code>d057d24</code></a>
remove redundant type definition</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/4b6a177ab0aa649b97e6aaa32c71ee085bae7dd2"><code>4b6a177</code></a>
allow user to rename individual field headers</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/6f43ada8a2310c3e16fb98a43b7429fbc07af792"><code>6f43ada</code></a>
chore(rel): 5.5.9</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/98af2bdc8d6f7a2bb4d73fccfabe2de15996777b"><code>98af2bd</code></a>
Merge pull request <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/275">#275</a>
from jose-cabral/nested-arrays-273</li>
<li><a
href="https://github.com/mrodrig/json-2-csv/commit/32ca8c4cc578a13fcd231f27f17e6bb8445bfe59"><code>32ca8c4</code></a>
fixes <a
href="https://redirect.github.com/mrodrig/json-2-csv/issues/273">#273</a>:
nested arrays are not always unwound</li>
<li>Additional commits viewable in <a
href="https://github.com/mrodrig/json-2-csv/compare/5.5.5...5.5.10">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=json-2-csv&package-manager=npm_and_yarn&previous-version=5.5.5&new-version=5.5.10)](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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 10:26:06 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
66ec2f124f Bump eslint-config-prettier from 9.1.0 to 9.1.2 (#17227)
Bumps
[eslint-config-prettier](https://github.com/prettier/eslint-config-prettier)
from 9.1.0 to 9.1.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md">eslint-config-prettier's
changelog</a>.</em></p>
<blockquote>
<h1>eslint-config-prettier</h1>
<h2>10.1.5</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/332">#332</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/60fef02574467d31d10ff47ecb567d378483c9d4"><code>60fef02</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- chore: add <code>funding</code> field into
<code>package.json</code></li>
</ul>
<h2>10.1.4</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/328">#328</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/94b47999e7eb13b703835729331376cef598b850"><code>94b4799</code></a>
Thanks <a
href="https://github.com/silvenon"><code>@​silvenon</code></a>! -
fix(cli): do not crash on no rules configured</li>
</ul>
<h2>10.1.3</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/325">#325</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/4e95a1d50073f1a24f004239ad6e1a4ffa8476df"><code>4e95a1d</code></a>
Thanks <a href="https://github.com/pilikan"><code>@​pilikan</code></a>!
- fix: this package is <code>commonjs</code>, align its types
correctly</li>
</ul>
<h2>10.1.2</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/321">#321</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/a8768bfe54a91d08f0cef8705f91de2883436bb0"><code>a8768bf</code></a>
Thanks <a href="https://github.com/Fdawgs"><code>@​Fdawgs</code></a>! -
chore(package): add homepage for some 3rd-party registry - see <a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/321">#321</a>
for more details</li>
</ul>
<h2>10.1.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/309">#309</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/eb56a5e09964e49045bccde3c616275eb0a0902d"><code>eb56a5e</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- fix: separate the <code>/flat</code> entry for compatibility</p>
<p>For flat config users, the previous
<code>&quot;eslint-config-prettier&quot;</code> entry still works, but
<code>&quot;eslint-config-prettier/flat&quot;</code> adds a new
<code>name</code> property for <a
href="https://eslint.org/blog/2024/04/eslint-config-inspector/">config-inspector</a>,
we just can't add it for the default entry for compatibility.</p>
<p>See also <a
href="https://redirect.github.com/prettier/eslint-config-prettier/issues/308">prettier/eslint-config-prettier#308</a></p>
<pre lang="ts"><code>// before
import eslintConfigPrettier from &quot;eslint-config-prettier&quot;;
<p>// after<br />
import eslintConfigPrettier from
&quot;eslint-config-prettier/flat&quot;;<br />
</code></pre></p>
</li>
</ul>
<h2>10.1.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/prettier/eslint-config-prettier/pull/306">#306</a>
<a
href="https://github.com/prettier/eslint-config-prettier/commit/56e2e3466391d0fdfc200e42130309c687aaab53"><code>56e2e34</code></a>
Thanks <a href="https://github.com/JounQin"><code>@​JounQin</code></a>!
- feat: migrate to exports field</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/prettier/eslint-config-prettier/commits">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~jounqin">jounqin</a>, a new releaser for
eslint-config-prettier since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=eslint-config-prettier&package-manager=npm_and_yarn&previous-version=9.1.0&new-version=9.1.2)](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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-19 10:25:44 +00:00
Charles BochetandGitHub 7c713577f1 Rework folder structure (#17229)
<img width="700" height="771" alt="image"
src="https://github.com/user-attachments/assets/509b95b5-9c4f-474d-a47f-f950dee189ea"
/>
2026-01-19 09:36:40 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>DevessierDevessier
72e89d5c6b Add "See all" widget action for ONE_TO_MANY relation fields (#17192)
FIELD widgets displaying ONE_TO_MANY relations now show a "See all"
action button (arrow-up-right icon) that navigates to the record index
with filtered relations.

### Demo



https://github.com/user-attachments/assets/fd632553-70e3-4757-8f26-80aa8d2ce0d2



### Changes

- **`WidgetAction` type**: Added `'see-all'` to `WidgetActionId`
- **`useWidgetActions` hook**: Returns `'see-all'` action for
ONE_TO_MANY relation fields (position 0, before edit)
- **`WidgetActionFieldSeeAll` component**: Renders the navigation button
with:
  - `IconArrowUpRight` icon
  - Link computed using same logic as `RecordDetailRelationSection`
  - Hover visibility behavior matching existing edit button
- **`WidgetActionRenderer`**: Added case for `'see-all'` action
- **Storybook**: Added `OneToManyRelationFieldWidgetWithSeeAllButton`
story verifying button visibility and well-formed link

### Link computation

Uses the same filter URL pattern as `RecordDetailRelationSection`:

```typescript
const filterQueryParams = {
  filter: {
    [relationFieldMetadataItem.name]: {
      [ViewFilterOperand.IS]: {
        selectedRecordIds: [targetRecord.id],
      },
    },
  },
  viewId: indexViewId,
};

const filterLinkHref = getAppPath(
  AppPath.RecordIndexPage,
  { objectNamePlural: relationObjectMetadataItem.namePlural },
  filterQueryParams,
);
```

The "see-all" action is shown regardless of field read-only status since
it's a navigation action, not an edit action.

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>[RPL] Display "See all" widget action for FIELD widget
displaying relations</issue_title>
> <issue_description>The "See all" widget action is the button you can
see in the top right corner in the following screen. We should redirect
the user to the record index showing the filtered relations.
> 
> <img width="1334" height="712" alt="Image"
src="https://github.com/user-attachments/assets/c8aca25e-2136-43b3-8896-abd161a5693e"
/>
> 
> Example of interaction today in production:
> 
>
https://github.com/user-attachments/assets/3730301c-d04b-4195-b2fb-a51f8efee08a
> 
> ## Technical details
> 
> See `useWidgetActions` and `WidgetActionRenderer`. Use the
`arrow-up-right` icon.
> 
> See how link is computed here:
https://github.com/twentyhq/twenty/blob/3cf7803ee1ae545add02f0c628a933618ce736d5/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx#L193-L204.
Reuse the same link.
> 
> ## Acceptance criteria
> 
> - The action should be displayed in addition to other actions that
might be rendered today
> - The action must only be displayed for ONE_TO_MANY relations
> - Ensure you write at least one story to ensure the button is
correctly displayed; the button should a well formed
link</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



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

- Fixes twentyhq/core-team-issues#2064

<!-- 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] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
2026-01-19 09:09:02 +00:00
92a080b704 Improve image upload error handling and validation (#17188)
- Add URL validation in getImageBufferFromUrl utility
- Add response status validation and content-type checking
- Add timeout and connection error handling with specific error messages
- Validate buffer is not empty before processing
- Validate file type detection results before proceeding
- Ensure detected file type is actually an image format
- Add proper type safety for Axios error handling

This improves robustness when uploading images from URLs by:
- Preventing invalid URLs from being processed
- Providing clear error messages for different failure scenarios
- Ensuring only valid image files are processed
- Handling network errors gracefully

---------

Co-authored-by: GitTensor Miner <miner@gittensor.io>
2026-01-19 08:40:02 +00:00
5cef07af45 Twenty SDK iteration (#17223)
Opening a PR to merge the wip work by @martmull 
We will re-organize twenty-sdk in an upcoming PR

---------

Co-authored-by: martmull <martmull@hotmail.fr>
2026-01-19 09:41:54 +01:00
b001991047 i18n - translations (#17224)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-18 20:00:54 +01:00
MarieandGitHub 2c596e7b1e [Apps] App misc - fixes + settings permissions for apps + uploadFile (#17167)
In this PR

- handle settings permission check for applications. Until then this was
unhandled and applications could not perform actions requiring settings
permissions, even if they were granted them

- fix ties to attachment, noteTarget etc: 
When an object had their fields synchronized in the app, the system
fields created as a side-effect of the object creation (relations to
noteTarget, attachment, taskTarget, favorites, timelineActivities -
created with `isCustom: true`, not sure that is correct btw) were then
deleted because they are not declared in the app, and identified as
deletable because of `isCustom: true`.
Updating the logic to exclude system fields from the logic that detects
fields to delete.
I think this outline the confusion we have around isCustom, isSystem
etc.

- introduce uploadFile util in generated twenty client as it cannot be
handled by the client's query / mutation. I had to use this for my
invoicing app
2026-01-18 18:41:48 +00:00
Paul RastoinandGitHub 6070dbf16a Identify agent (#17221)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids

## Test
tested prod extract
2026-01-18 16:31:25 +00:00
Paul RastoinandGitHub fae6d0e262 Improve cleaning job (#17208)
# Introduction

Refactored the workspace deletion to dynamically iterate over all known
v2 syncable entities repos and delete all of them from child to parent
Exception for field metadata that we chunk delete in order to avoid
locking the core schema too long, it does not have an impact on perfs at
all ( neither plus or less ) Chunking by constraint within a transaction
is not necessary both does not cost more

## From 
30s for a workspace complete deletion
```ts
[Nest] 93244  - 01/16/2026, 10:24:52 PM     LOG [WorkspaceService] workspace WS_ID cache flushed
[Runner] Total execution: 26.290s // ( deleteAllObjectMetadatas v2 )
[Nest] 93244  - 01/16/2026, 10:25:22 PM     LOG [WorkspaceService] workspace WS_ID hard deleted
```

## To
3s !

```ts
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [DatabaseConfigDriver] [INIT] Config variables loaded: 0 values found in DB, 69 falling to env vars/defaults
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [CleanSuspendedWorkspacesCommand] IGNORING GRACE PERIOD - Cleaning 1 suspended workspaces
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [CleanerWorkspaceService] batchWarnOrCleanSuspendedWorkspaces running...
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [CleanerWorkspaceService] Processing workspace - 1/1
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [CleanerWorkspaceService] Destroying workspace Twenty Eng
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace user workspaces deleted
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace cache flushed
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 80 viewFilter record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 21 pageLayoutWidget record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 1515 viewField record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 91 index record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 66 roleTarget record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 174 viewGroup record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 1 agent record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 7 pageLayout record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 111 view record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 1/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 2/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 3/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 4/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 5/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 6/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 7/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 8/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 9/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 10/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 11/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 12/15 - deleted 51 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 13/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 14/15 - deleted 50 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: fieldMetadata chunk 15/15 - deleted 36 record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 737 fieldMetadata record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 6 role record(s)
[Nest] 65112  - 01/18/2026, 4:37:38 PM     LOG [WorkspaceService] workspace: deleted 78 serverlessFunction record(s)
[Nest] 65112  - 01/18/2026, 4:37:39 PM     LOG [WorkspaceService] workspace: deleted 43 objectMetadata record(s)
[Nest] 65112  - 01/18/2026, 4:37:41 PM     LOG [WorkspaceService] workspace hard deleted
[Nest] 65112  - 01/18/2026, 4:37:41 PM     LOG [CleanerWorkspaceService] Destroyed 1 workspaces on 5 limit durings this execution
[Nest] 65112  - 01/18/2026, 4:37:41 PM     LOG [CleanerWorkspaceService] batchWarnOrCleanSuspendedWorkspaces done!
[Nest] 65112  - 01/18/2026, 4:37:41 PM     LOG [CleanSuspendedWorkspacesCommand] Command completed!
```


## Update
Discussed with @charlesBochet ended debugging and analyzing sql query
operations
He discovered that we were not indexing foreignKey effectively
We've ended up fixing all the FK indeces coverage leading to 

## Cleaning
Removed the
```sh
npx nx run twenty-server:command workspace:clean-soft-deleted-suspended-workspaces --ignore-grace-period
```
In favor of
```sh
npx nx run twenty-server:command workspace:clean --only-operation destroy --ignore-destroy-grace-period
```

## Conclusion
Not that crazy but still worth it and could demultiply in production
2026-01-18 16:22:26 +00:00
Lucas BordeauandGitHub d51c988a9e Implemented SSE update across main front components (#17205)
This PR implements SSE update events across the main components of the
application : tables, boards, calendars, show pages.

There is still work to do on other event type and on making sure
everything works fine, but this first implementation should be robust
enough to start with.

Some problems encountered along the way : 
- Events are returning raw Postgres output, because they are not
normalized by the GraphQL layer, so we ended up with amountMicros as
string values, which the frontend does not like, so I implemented a
small util in our `formatResult` generic pipeline to turn amountMicros
to a number if it's a string value. We could implement other formatters
for composite fields if we see problems with events.
-`action` property was missing in SSE events, which is required by the
frontend to know what kind of event it is.

# QA


https://github.com/user-attachments/assets/393b45ac-59d2-48b0-855b-2ce9c4b8ae57


https://github.com/user-attachments/assets/cb214e7a-1595-4b85-bdb6-87630993d2e2
2026-01-18 16:14:36 +00:00
Paul RastoinandGitHub a91bac9dd9 Identify view filter (#17197)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids

## Test
tested prod extract
2026-01-18 11:08:21 +00:00
Abdullah.andGitHub 92eff62523 Replace test-runner with vitest for storybook (#17187) 2026-01-18 03:25:45 +00:00
Félix MalfaitandGitHub c737028dd6 Move tools/eslint-rules to packages/twenty-eslint-rules (#17203)
## Summary

Moves the custom ESLint rules from `tools/eslint-rules` to
`packages/twenty-eslint-rules` for better organization within the
monorepo packages structure.

## Changes

- Move `eslint-rules` from `tools/` to `packages/twenty-eslint-rules`
- Use `loadWorkspaceRules` from `@nx/eslint-plugin` to load custom rules
- Update all ESLint configs to use the `twenty/` rule prefix instead of
`@nx/workspace-`
- Update `project.json`, `jest.config.mjs` with new paths
- Update `package.json` workspaces and `nx.json` cache inputs
- Update Dockerfile reference

## Technical Details

The custom ESLint rules are now loaded using Nx's `loadWorkspaceRules`
utility which:
- Handles TypeScript transpilation automatically
- Allows loading workspace rules from any directory
- Provides a cleaner approach than the previous `@nx/workspace-`
convention

## Testing

- Verified all 17 custom ESLint rules load correctly from the new
location
- Verified linting works on dependent packages (twenty-front,
twenty-server, etc.)
2026-01-17 07:37:17 +01:00
a429bc922d i18n - docs translations (#17204)
Created by Github action

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Improves translation accuracy and terminology across localized docs.
> 
> - AR `chart-settings.mdx`: corrected "Group by" image `alt` text
> - RO `chart-settings.mdx`: fixed diacritic in `"Sursă"` image `alt`
and table header `"Opțiune"`
> - TR `widgets.mdx`: corrected iFrame image `alt` text
> - TR `send-emails-from-workflows.mdx`: updated section title and table
header under attachments usage
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2a19997384. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-01-16 20:31:07 +00:00
1b9dc414c7 i18n - translations (#17206)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-16 20:20:38 +01:00
Abdul RahmanandGitHub 5a617e4718 Add command menu item entity (#17181) 2026-01-16 19:02:07 +00:00
b7fbe1c49e i18n - docs translations (#17199)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-16 18:36:39 +01:00
Baptiste DevessierandGitHub 5bcbe43596 Various layout fixes so that RPL v1 look the same as production version (#17195)
> [!WARNING]
> Most of the quick fixes will be dropped once we release the feature
and deprecate the old show pages code.

This PR fixes padding and alignment issues so that the new Record Page
Layout feature looks like the old show pages.

## This PR

<img width="3456" height="2160" alt="CleanShot 2026-01-16 at 17 24
40@2x"
src="https://github.com/user-attachments/assets/f3640553-2316-498c-8dd0-14b09ed913f1"
/>

<img width="3456" height="2160" alt="CleanShot 2026-01-16 at 17 27
14@2x"
src="https://github.com/user-attachments/assets/c4ccb28f-5629-4e68-83ad-863f21066962"
/>


## The production

<img width="3456" height="2160" alt="CleanShot 2026-01-16 at 17 25
11@2x"
src="https://github.com/user-attachments/assets/a91f7216-0b57-404b-9f9d-f6f45d4858d6"
/>

<img width="3456" height="2234" alt="CleanShot 2026-01-16 at 17 27
19@2x"
src="https://github.com/user-attachments/assets/aaf1ee6b-b323-4844-9f1a-a59807cdbe40"
/>
2026-01-16 17:06:34 +00:00
8a27c4987d i18n - translations (#17200)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-16 17:40:58 +01:00
Charles BochetandGitHub 5fc4e810f7 Front Extensibility: Introduce Front Component Entity (#17175)
As part of the extensibility effort, we are introducing a new engine
entity called "Front Component". This represents a dynamic react
component that will be rendered in CommandMenu actions or in PageLayout
widgets

This PR introduce the entity and all the necessary boilerplate to make
it syncable and cachable in the engine
2026-01-16 17:23:46 +01:00
fe0d84c97e i18n - translations (#17198)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-16 17:22:02 +01:00
EtienneandGitHub 76cbe59929 File v2 - update core.file table (#17172)
For the following, I'll create the issues first
2026-01-16 15:59:39 +00:00
Félix MalfaitandGitHub dcdf9e4d9c fix: add base_path to Crowdin configs for correct path resolution (#17196)
## Problem
The Crowdin GitHub Actions were failing with:
```
 No sources found for 'packages/twenty-docs/user-guide/**/*.mdx' pattern
 No sources found for 'packages/twenty-docs/developers/**/*.mdx' pattern
 No sources found for 'packages/twenty-docs/twenty-ui/**/*.mdx' pattern
 No sources found for 'packages/twenty-docs/navigation/navigation.template.json' pattern
```

## Root Cause
The Crowdin config files are located at `.github/crowdin-docs.yml` and
`.github/crowdin-app.yml`. By default, the Crowdin CLI resolves source
paths relative to the config file's directory (`.github/`), not the
repository root.

So paths like `packages/twenty-docs/...` were being resolved as
`.github/packages/twenty-docs/...`, which doesn't exist.

## Fix
Added `base_path: ".."` to both Crowdin config files to make paths
resolve relative to the repository root.
2026-01-16 16:54:40 +01:00
Félix MalfaitandGitHub 6a709d9c50 feat(serverless): add basic sandbox isolation and flexible driver options (#17176)
## Overview
- Add a DISABLED serverless driver to explicitly turn off execution
- Clarify self-hosting docs with driver options and recommended usage
- Keep integration coverage for serverless function execution (default +
external package example)

## Notes
- Local driver remains the default for development usage; Lambda or
Disabled recommended for production deployments
- No functional changes to Lambda execution

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces flexible serverless execution modes and safer local
execution.
> 
> - **New driver:** `DISABLED` serverless driver with wiring in
`serverless.interface`, factory, module provider, and GraphQL exception
mapping; new exception code `SERVERLESS_FUNCTION_DISABLED`.
> - **Local driver hardening:** Strip `NODE_OPTIONS` when spawning child
processes; cleanup promise signature; better log capture.
> - **Dependency build reliability:** Use `execFile` with bundled Yarn
(`.yarn/releases/yarn-4.9.2.cjs`), strip `NODE_OPTIONS`, improved error
messages, and parallel cleanup excluding `node_modules`.
> - **Docs:** Add serverless section detailing `SERVERLESS_TYPE` options
(LOCAL, LAMBDA, DISABLED), security notice, and recommended configs.
> - **Config/env:** Default
`IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS` set to `true`
(examples/tests default `false`); sample envs updated.
> - **Tests:** Add integration tests and GraphQL helpers for creating,
updating, publishing, executing, and deleting serverless functions,
including external package usage and error paths.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1a2958cc19. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2026-01-16 15:54:44 +01:00
nitinandGitHub 9620961f16 [Dashboards] [ai] expand widget schemas with subfield grouping, color, and styling options (#17185)
Got rid of filters for now -- filters would need extra tailoring since
they depend on field names/types and the RecordGqlOperationFilter DSL
(nested AND/OR and composite subfields), which the AI can’t reliably
construct without additional metadata and skills

created a followup to tackle the same
https://github.com/twentyhq/core-team-issues/issues/2108
2026-01-16 13:22:34 +00:00
Paul RastoinandGitHub 964f1c6227 Remove delete-field and delete-object aggregators (#17183)
# Introduction
~~Testing first this might break some constraints can't remember the
initial motivation~~ -> it does not

The goal here is to avoid relying on pg cascading which will lock the
schema longer than if done in n operations
2026-01-16 12:44:23 +00:00
Paul RastoinandGitHub 9750bcba81 Improve view field identification command perfs and reliability (#17168)
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1989
Same continuity than what we've done in
https://github.com/twentyhq/twenty/pull/17161 comparing expect to
existing instead of existing versus expected

Plus various fixes
Tried both view and view field identification on prod extract, ( clean
soft deleted workspace and applied the migration WIP )
2026-01-16 09:35:41 +00:00
martmullandGitHub 1917c1c9b9 Add forwardedRequestHeaders in routeTriggers (#17151)
- add `forwardedRequestHeaders` `string[]` column in `core.routeTrigger`
- filter request headers and forward filtered headers to function
payload (avoid spreading unexpectedly token or cookie)
- add `forwardedRequestHeaders` option in twenty-sdk `defineFunction`
util

BREAKING for actual routeTrigger payload but only 16 to migrate in
production
2026-01-16 09:19:28 +00:00
nitinandGitHub 8908e0785f [Dashboards] [ai] update dashboard tools schema and skill with correct config field names (#17180)
<img width="2560" height="1353" alt="CleanShot 2026-01-16 at 00 55 56"
src="https://github.com/user-attachments/assets/c242071c-13d3-494d-bcce-9d45f36fcd77"
/>
2026-01-15 20:55:47 +00:00
Thomas TrompetteandGitHub 0fa8098c50 Add get current workflow version tool (#17177)
This is required so an agent can easily understand what version should
be updated from the workflow show page.
2026-01-15 16:59:11 +00:00
WeikoandGitHub 8413c6f3dd Fix insert new record with RLS (#17164)
## Context
Now that RLS predicates are applied, creating a record through the FE
(which is empty by default) is failing if your role has predicates and
your input does not respect them (which will always be true since, as
said above, input will be pretty much empty)

## Implementation
- Moved isMatching* filters to twenty-shared
- Implemented isMatchingRlsPredicates utils in the backend (ORM) to
check before insertion/update if the record is matching the current user
role Rls predicates, reusing the isMatching* filters utils moved to
twenty-shared
- Frontend now applies RLS predicates before creating a new record
(similarly to what we do with view filters)

Note:
It seems composite were not properly handled with view-filter insertion
logic, since I'm reusing the util for now, the issue remains for RLS and
will need to be addressed
2026-01-15 16:40:47 +00:00
Lucas BordeauandGitHub 2cff75d772 Refactored object operation dispatch for SSE (#17174)
This PR refactors object record operation dispatch through a browser
event instead of a state that was registering all operations.

This is a cleaner pattern as it is a synchronous event code path instead
of relying on a useEffect to watch state change, which is not ideal.

We introduce this change first to then rely on this new pattern to
dispatch SSE events in a following PR.
2026-01-15 16:38:21 +00:00
nitinandGitHub 3f28fde03a [Dashboards] fix widget type switching when navigating back in command menu (#17166)
before - 



https://github.com/user-attachments/assets/ab1e1719-f636-4d49-8c3f-cbc6b5e1f61f





after - 


https://github.com/user-attachments/assets/4bebe9c5-d8eb-49ca-9265-70e571408465
2026-01-15 13:47:02 +00:00
161e8670d0 fix: show save button when creating a new role (#17163)
## Summary
Fixes a regression where the save button was not visible when creating a
new role.

## Root Cause
PR #17062 (RLS FE implementation) introduced a change to the `isDirty`
logic that added `isDefined(settingsPersistedRole)` as a condition:

```typescript
const isDirty =
  isDefined(settingsPersistedRole) &&
  !isDeeplyEqual(settingsDraftRole, settingsPersistedRole);
```

However, in create mode, `settingsPersistedRole` is intentionally set to
`undefined` (in `SettingsRoleCreateEffect.tsx`), causing `isDirty` to
always evaluate to `false` and hiding the save button.

## Fix
Added `isCreateMode` to the `isDirty` condition so the save button shows
when creating a new role:

```typescript
const isDirty =
  isCreateMode ||
  (isDefined(settingsPersistedRole) &&
    !isDeeplyEqual(settingsDraftRole, settingsPersistedRole));
```

cc @Weiko

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Restores save button visibility when creating a role.
> 
> - Simplifies `isDirty` to `!isDeeplyEqual(settingsDraftRole,
settingsPersistedRole)`, removing the `isDefined(settingsPersistedRole)`
check so create-mode is considered dirty.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
21593d54c3. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-01-15 13:42:23 +00:00
nitinandGitHub abdccf2940 [Dashboards] restrict dashboard actions based on object permissions (#17169) 2026-01-15 13:37:08 +00:00
f218d652df [Dashboard] Add chart settings/config documentation (#17053)
closes https://github.com/twentyhq/core-team-issues/issues/1973

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-15 13:05:39 +00:00
Paul RastoinandGitHub 466d272f9c Create syncable entity cursor rule (#17165)
# Introduction

As per title
We could improve the integration tests section ( maybe a dedicated tool
)
2026-01-15 13:05:30 +00:00
nitinandGitHub 975401e18f fix widget skeleton loader not being centered (#17157) 2026-01-15 13:04:12 +00:00
nitinandGitHub 55b4ac4314 [Dashboards] [Performance] Bar chart re renders fix (#17162)
before - 


https://github.com/user-attachments/assets/c9b4a60a-d4bd-43d5-8621-54777a326171


after - 




https://github.com/user-attachments/assets/74358810-067e-44f7-82f1-f4dcd7956956
2026-01-15 12:40:28 +00:00
Paul RastoinandGitHub d8f0115fef Improve view identification command perfs and reliability (#17161)
# Introduction
Instead of comparing existing to expected comparing expected to existing
Also improved logs and fallback use cases

# Test
Tested on a prod extract
2026-01-15 12:27:07 +00:00
martmullandGitHub f894f6b1c4 Fix universalIdentifier ignored when creating object (#17158)
As title
2026-01-15 08:41:44 +00:00
Thomas TrompetteandGitHub 6e53f5ab92 Throw 400 for webhook triggering deleted workspaces + enqueue workflows by batches (#17154)
Fixes https://github.com/twentyhq/twenty/issues/17027

Fixes https://github.com/twentyhq/twenty/issues/16824
2026-01-14 17:35:05 +00:00
MarieandGitHub 8379e19587 [Apps] Small improvements (#17129)
- Fix undefined in breadcrumbs
<img width="783" height="220" alt="breadcrumbs_bad"
src="https://github.com/user-attachments/assets/557648da-f825-4bf6-b66f-2ac3992eeab1"
/>
- Make role required in app definition
2026-01-14 17:28:51 +00:00
5156 changed files with 220507 additions and 137879 deletions
+3 -1
View File
@@ -13,6 +13,7 @@ This directory contains Twenty's development guidelines and best practices in th
- **architecture.mdc** - Project overview, technology stack, and infrastructure setup (Always Applied)
- **nx-rules.mdc** - Nx workspace guidelines and best practices (Auto-attached to Nx files)
- **server-migrations.mdc** - Backend migration and TypeORM guidelines for `twenty-server` (Auto-attached to server entities and migration files)
- **creating-syncable-entity.mdc** - Comprehensive guide for creating new syncable entities (with universalIdentifier and applicationId) in the workspace migration system (Agent-requested for metadata-modules and workspace-migration files)
### Code Quality
- **typescript-guidelines.mdc** - TypeScript best practices and conventions (Auto-attached to .ts/.tsx files)
@@ -39,6 +40,7 @@ You can manually reference any rule using the `@ruleName` syntax:
- `@nx-rules` - Include Nx-specific guidance
- `@react-general-guidelines` - Load React best practices
- `@testing-guidelines` - Get testing recommendations
- `@creating-syncable-entity` - Guide for creating new syncable entities
### Rule Types Used
- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc)
@@ -53,7 +55,7 @@ You can manually reference any rule using the `@ruleName` syntax:
# Testing
npx nx test twenty-front # Run unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:serve-and-test:static # Run Storybook tests
npx nx storybook:test # Run Storybook tests
# Development
npx nx lint:diff-with-main twenty-front # Lint files changed vs main (fastest)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -5,6 +5,7 @@
#
"preserve_hierarchy": true
"base_path": ".."
files: [
{
+1
View File
@@ -6,6 +6,7 @@
"project_id": 2
"preserve_hierarchy": true
"base_url": "https://twenty.api.crowdin.com"
"base_path": ".."
files: [
{
+2
View File
@@ -2,6 +2,8 @@ version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
exclude-paths:
- "packages/twenty-apps/community/**"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
+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
+24 -15
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:
@@ -56,8 +57,6 @@ jobs:
run: df -h
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Front / Clean storybook-static
run: rm -rf packages/twenty-front/storybook-static
- name: Front / Build storybook
run: npx nx storybook:build twenty-front
- name: Save storybook build cache
@@ -66,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
@@ -83,18 +82,28 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-sdk
- name: Install Playwright
run: cd packages/twenty-front && npx playwright install
- name: Restore storybook build cache
uses: ./.github/actions/restore-cache
with:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
run: |
cd packages/twenty-front
npx playwright install
- name: Front / Write .env
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:serve-and-test:static twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }} --checkCoverage=false
run: npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
- name: Rename coverage file
run: mv packages/twenty-front/coverage/storybook/coverage-storybook.json packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
run: |
if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
mv packages/twenty-front/coverage/storybook/coverage-final.json packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
else
echo "Error: coverage-final.json not found"
ls -la packages/twenty-front/coverage/storybook/ || echo "Coverage directory does not exist"
exit 1
fi
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
@@ -131,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 }}
@@ -197,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:
+58 -49
View File
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test]
task: [lint, typecheck, test:unit]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
@@ -44,58 +44,67 @@ jobs:
with:
tag: scope:sdk
tasks: ${{ matrix.task }}
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
# TODO uncomment when syncApplication resolver is fixed
# sdk-e2e-integration-test:
# timeout-minutes: 30
# runs-on: ubuntu-latest-8-cores
# needs: [changed-files-check, sdk-test]
# strategy:
# matrix:
# task: [test:integration, test:e2e]
# 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 ${{ matrix.task }} Tests
# uses: ./.github/actions/nx-affected
# with:
# tag: scope:sdk
# tasks: ${{ matrix.task }}
ci-sdk-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, sdk-test, sdk-e2e-test]
needs: [changed-files-check, sdk-test]
# TODO uncomment when syncApplication resolver is fixed
# needs: [changed-files-check, sdk-test, sdk-e2e-integration-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})`
});
+4 -1
View File
@@ -107,10 +107,13 @@ jobs:
- name: Regenerate docs.json
run: yarn docs:generate
- name: Regenerate documentation paths constants
run: yarn docs:generate-paths
- name: Commit artifacts to pull request branch
if: github.event_name == 'pull_request'
run: |
git add packages/twenty-docs/docs.json packages/twenty-docs/navigation/navigation.template.json
git add packages/twenty-docs/docs.json packages/twenty-docs/navigation/navigation.template.json packages/twenty-shared/src/constants/DocumentationPaths.ts
if git diff --staged --quiet --exit-code; then
echo "No navigation/doc changes to commit."
exit 0
+3 -5
View File
@@ -2,11 +2,9 @@
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "uv",
"args": ["run", "postgres-mcp", "--access-mode=unrestricted"],
"env": {
"DATABASE_URI": "${PG_DATABASE_URL}"
}
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "${PG_DATABASE_URL}"],
"env": {}
},
"playwright": {
"type": "stdio",
+12 -1
View File
@@ -68,12 +68,14 @@
"./jest-integration.config.ts",
"${relativeFile}",
"--silent=false",
"${input:updateSnapshot}"
],
"cwd": "${workspaceFolder}/packages/twenty-server",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"env": {
"NODE_ENV": "test"
"NODE_ENV": "test",
"NODE_OPTIONS": "--max-old-space-size=12288 --import tsx/esm"
}
},
{
@@ -97,5 +99,14 @@
"NODE_ENV": "test"
}
}
],
"inputs": [
{
"id": "updateSnapshot",
"type": "pickString",
"description": "Update snapshots?",
"options": ["", "--updateSnapshot"],
"default": ""
}
]
}
+2 -1
View File
@@ -53,5 +53,6 @@
".cursorrules": "markdown"
},
"jestrunner.codeLensSelector": "**/*.{test,spec,integration-spec}.{js,jsx,ts,tsx}",
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.experimental.useTsgo": false
}
+59 -7
View File
@@ -2,12 +2,64 @@
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"path": "server",
"problemMatcher": [],
"label": "yarn: start - server",
"detail": "yarn start"
"label": "twenty-server - run integration test file",
"type": "shell",
"command": "npx nx run twenty-server:jest -- --config ./jest-integration.config.ts ${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": []
},
{
"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": [
{
"id": "watchMode",
"type": "pickString",
"description": "Enable watch mode?",
"options": ["", "--watch"],
"default": ""
},
{
"id": "updateSnapshot",
"type": "pickString",
"description": "Update snapshots?",
"options": ["", "--updateSnapshot"],
"default": ""
}
]
}
}
+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:serve-and-test:static 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
+5 -2
View File
@@ -11,6 +11,8 @@ 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');
export default [
// Base JavaScript configuration
js.configs.recommended,
@@ -195,6 +197,7 @@ export default [
plugins: {
...mdxPlugin.flat.plugins,
'@nx': nxPlugin,
'twenty': { rules: twentyRules },
},
},
mdxPlugin.flatCodeBlocks,
@@ -205,9 +208,9 @@ export default [
'unused-imports/no-unused-imports': 'off',
'unused-imports/no-unused-vars': 'off',
// Enforce JSX tags on separate lines to prevent Crowdin translation issues
'@nx/workspace-mdx-component-newlines': 'error',
'twenty/mdx-component-newlines': 'error',
// Disallow angle bracket placeholders to prevent Crowdin translation errors
'@nx/workspace-no-angle-bracket-placeholders': 'error',
'twenty/no-angle-bracket-placeholders': 'error',
},
},
];
+6 -1
View File
@@ -1,4 +1,9 @@
const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset };
module.exports = {
...nxPreset,
// Override the new testEnvironmentOptions added in @nx/jest 22.3.3
// which breaks Lingui's module resolution
testEnvironmentOptions: {},
};
+36 -113
View File
@@ -4,9 +4,7 @@
"libsDir": "packages"
},
"namedInputs": {
"default": [
"{projectRoot}/**/*"
],
"default": ["{projectRoot}/**/*"],
"excludeStories": [
"default",
"!{projectRoot}/.storybook/*",
@@ -34,26 +32,17 @@
"targetDefaults": {
"build": {
"cache": true,
"inputs": [
"^production",
"production"
],
"dependsOn": [
"^build"
]
"inputs": ["^production", "production"],
"dependsOn": ["^build"]
},
"start": {
"cache": false,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"cache": true,
"outputs": [
"{options.outputFile}"
],
"outputs": ["{options.outputFile}"],
"options": {
"eslintConfig": "{projectRoot}/eslint.config.mjs",
"cache": true,
@@ -67,9 +56,7 @@
"fix": true
}
},
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"lint:diff-with-main": {
"executor": "nx:run-commands",
@@ -103,46 +90,36 @@
"write": true
}
},
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"typecheck": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "tsc -b tsconfig.json --incremental"
"command": "tsgo -p tsconfig.json"
},
"configurations": {
"watch": {
"watch": true
"command": "tsgo -p tsconfig.json --watch"
}
},
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"test": {
"executor": "@nx/jest:jest",
"cache": true,
"dependsOn": [
"^build"
],
"dependsOn": ["^build"],
"inputs": [
"^default",
"excludeStories",
"{workspaceRoot}/jest.preset.js"
],
"outputs": [
"{projectRoot}/coverage"
],
"outputs": ["{projectRoot}/coverage"],
"options": {
"jestConfig": "{projectRoot}/jest.config.mjs",
"coverage": true,
"coverageReporters": [
"text-summary"
],
"coverageReporters": ["text-summary"],
"cacheDirectory": "../../.cache/jest/{projectRoot}"
},
"configurations": {
@@ -151,10 +128,7 @@
"maxWorkers": 3
},
"coverage": {
"coverageReporters": [
"lcov",
"text"
]
"coverageReporters": ["lcov", "text"]
},
"watch": {
"watch": true
@@ -163,36 +137,25 @@
},
"test:e2e": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"storybook:build": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"^default",
"excludeTests"
],
"outputs": [
"{projectRoot}/{options.output-dir}"
],
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/{options.output-dir}"],
"options": {
"cwd": "{projectRoot}",
"command": "NODE_OPTIONS='--max-old-space-size=10240' VITE_DISABLE_TYPESCRIPT_CHECKER=true storybook build --test",
"output-dir": "storybook-static",
"config-dir": ".storybook"
},
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"storybook:serve:dev": {
"executor": "nx:run-commands",
"cache": true,
"dependsOn": [
"^build"
],
"dependsOn": ["^build"],
"options": {
"cwd": "{projectRoot}",
"command": "storybook dev",
@@ -201,9 +164,7 @@
},
"storybook:serve:static": {
"executor": "nx:run-commands",
"dependsOn": [
"storybook:build"
],
"dependsOn": ["storybook:build"],
"options": {
"cwd": "{projectRoot}",
"command": "npx http-server {args.staticDir} -a={args.host} --port={args.port} --silent={args.silent}",
@@ -216,38 +177,21 @@
"storybook:test": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"^default",
"excludeTests"
],
"outputs": [
"{projectRoot}/coverage/storybook"
],
"inputs": ["^default", "excludeTests"],
"outputs": ["{projectRoot}/coverage/storybook"],
"options": {
"cwd": "{projectRoot}",
"commands": [
"test-storybook --url http://localhost:{args.port} --maxWorkers=3 --coverage --coverageDirectory={args.coverageDir} --shard={args.shard}",
"nx storybook:coverage {projectName} --coverageDir={args.coverageDir} --checkCoverage={args.checkCoverage}"
],
"shard": "1/1",
"parallel": false,
"coverageDir": "coverage/storybook",
"port": 6006,
"checkCoverage": true
"command": "vitest run --coverage --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:test:no-coverage": {
"executor": "nx:run-commands",
"inputs": [
"^default",
"excludeTests"
],
"inputs": ["^default", "excludeTests"],
"options": {
"cwd": "{projectRoot}",
"commands": [
"test-storybook --url http://localhost:{args.port} --maxWorkers=2"
],
"port": 6006
"command": "vitest run --shard={args.shard}",
"shard": "1/1"
}
},
"storybook:coverage": {
@@ -274,17 +218,6 @@
}
}
},
"storybook:serve-and-test:static": {
"executor": "nx:run-commands",
"options": {
"commands": [
"npx concurrently --kill-others --success=first -n SB,TEST 'nx storybook:serve:static {projectName} --port={args.port}' 'npx wait-on tcp:{args.port} && nx storybook:test {projectName} --shard={args.shard} --checkCoverage={args.checkCoverage} --port={args.port} --configuration={args.scope}'"
],
"shard": "1/1",
"checkCoverage": true,
"port": 6006
}
},
"chromatic": {
"executor": "nx:run-commands",
"options": {
@@ -326,29 +259,21 @@
"inputs": [
"default",
"{workspaceRoot}/eslint.config.mjs",
"{workspaceRoot}/tools/eslint-rules/**/*"
]
},
"@nx/vite:test": {
"cache": true,
"inputs": [
"default",
"^default"
"{workspaceRoot}/packages/twenty-eslint-rules/**/*"
]
},
"@nx/vite:build": {
"cache": true,
"dependsOn": [
"^build"
],
"inputs": [
"default",
"^default"
]
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
},
"@nx/vitest:test": {
"cache": true,
"inputs": ["default", "^default"]
}
},
"installation": {
"version": "22.0.3"
"version": "22.3.3"
},
"generators": {
"@nx/react": {
@@ -376,9 +301,7 @@
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": [
"storybook:build"
]
"cacheableOperations": ["storybook:build"]
}
}
},
+23 -14
View File
@@ -72,19 +72,20 @@
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@nx/eslint": "22.0.3",
"@nx/eslint-plugin": "22.0.3",
"@nx/jest": "22.0.3",
"@nx/js": "22.0.3",
"@nx/react": "22.0.3",
"@nx/storybook": "22.0.3",
"@nx/vite": "22.0.3",
"@nx/web": "22.0.3",
"@nx/eslint": "22.3.3",
"@nx/eslint-plugin": "22.3.3",
"@nx/jest": "22.3.3",
"@nx/js": "22.3.3",
"@nx/react": "22.3.3",
"@nx/storybook": "22.3.3",
"@nx/vite": "22.3.3",
"@nx/web": "22.3.3",
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
"@storybook/addon-docs": "^10.1.11",
"@storybook/addon-links": "^10.1.11",
"@storybook/addon-vitest": "^10.1.11",
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.1.11",
"@storybook/test-runner": "^0.24.2",
@@ -134,7 +135,11 @@
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@typescript-eslint/utils": "^8.39.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
"@vitejs/plugin-react-swc": "3.11.0",
"@vitest/browser-playwright": "^4.0.17",
"@vitest/coverage-istanbul": "^4.0.17",
"@vitest/coverage-v8": "^4.0.17",
"@yarnpkg/types": "^4.0.0",
"chromatic": "^6.18.0",
"concurrently": "^8.2.2",
@@ -163,9 +168,9 @@
"jest-environment-node": "^29.4.1",
"jest-fetch-mock": "^3.0.3",
"jsdom": "~22.1.0",
"msw": "^2.0.11",
"msw-storybook-addon": "^2.0.5",
"nx": "22.0.3",
"msw": "^2.12.7",
"msw-storybook-addon": "^2.0.6",
"nx": "22.3.3",
"prettier": "^3.1.1",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
@@ -179,7 +184,8 @@
"ts-node": "10.9.1",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"vite": "^7.0.0"
"vite": "^7.0.0",
"vitest": "^4.0.17"
},
"engines": {
"node": "^24.5.0",
@@ -195,13 +201,16 @@
"typescript": "5.9.2",
"graphql-redis-subscriptions/ioredis": "^5.6.0",
"prosemirror-view": "1.40.0",
"prosemirror-transform": "1.10.4"
"prosemirror-transform": "1.10.4",
"@lingui/core": "5.1.2",
"@types/qs": "6.9.16"
},
"version": "0.2.1",
"nx": {},
"scripts": {
"docs:generate": "tsx packages/twenty-docs/scripts/generate-docs-json.ts",
"docs:generate-navigation-template": "tsx packages/twenty-docs/scripts/generate-navigation-template.ts",
"docs:generate-paths": "tsx packages/twenty-docs/scripts/generate-documentation-paths.ts",
"start": "npx concurrently --kill-others 'npx nx run-many -t start -p twenty-server twenty-front' 'npx wait-on tcp:3000 && npx nx run twenty-server:worker'"
},
"workspaces": {
@@ -220,7 +229,7 @@
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
"tools/eslint-rules"
"packages/twenty-eslint-rules"
]
},
"prettier": {
+26 -19
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)
@@ -28,29 +31,33 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# If you don't use yarn@4
corepack enable
yarn install
# Get help
yarn run help
# Authenticate using your API key (you'll be prompted)
yarn auth
yarn auth:login
# Add a new entity to your application (guided)
yarn create-entity
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn generate
yarn app:generate
# Start dev mode: automatically syncs local changes to your workspace
yarn dev
# Start dev mode: watches, builds, and syncs local changes to your workspace
yarn app:dev
# Or run a onetime sync
yarn sync
# Watch your application's function logs
yarn function:logs
# Watch your application's functions logs
yarn 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 uninstall
# Display commands' help
yarn help
yarn app:uninstall
```
## What gets scaffolded
@@ -60,9 +67,9 @@ yarn help
- Example placeholders to help you add entities, actions, and sync logic
## Next steps
- Explore the generated project and add your first entity with `yarn create-entity`.
- Keep your types uptodate using `yarn generate`.
- Use `yarn dev` while you iterate to see changes instantly in your workspace.
- Explore the generated project and add your first entity with `yarn entity:add` (functions, front components, objects, roles).
- Keep your types uptodate using `yarn app:generate`.
- Use `yarn app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
## Publish your application
@@ -90,8 +97,8 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn auth` again and verify the API key permissions.
- Types not generated: ensure `yarn generate` runs without errors, then restart `yarn dev`.
- Auth prompts not appearing: run `yarn auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn app:generate` runs without errors, then restart `yarn app:dev`.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.3.1",
"version": "0.4.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
@@ -5,17 +5,36 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn auth
yarn auth:login
```
Then, install this app to your workspace:
Then, start development mode to sync your app and watch for changes:
```bash
yarn sync
yarn app:dev
```
Open your Twenty instance and go to `/settings/applications` section to see the result.
## Available Commands
```bash
# Authentication
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 (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
yarn app:uninstall # Uninstall app from workspace
```
## Learn More
To learn more about Twenty applications, take a look at the following resources:
@@ -5,6 +5,7 @@
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"jsx": "react-jsx",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
@@ -21,6 +22,10 @@
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
"paths": {
"src/*": ["./src/*"],
"~/*": ["./*"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
@@ -1,12 +1,12 @@
import { copyBaseApplicationProject } from '@/utils/app-template';
import { convertToLabel } from '@/utils/convert-to-label';
import { install } from '@/utils/install';
import { tryGitInit } from '@/utils/try-git-init';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import * as path from 'path';
import { copyBaseApplicationProject } from '@/utils/app-template';
import kebabCase from 'lodash.kebabcase';
import { convertToLabel } from '@/utils/convert-to-label';
import { tryGitInit } from '@/utils/try-git-init';
import { install } from '@/utils/install';
import * as path from 'path';
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
@@ -119,10 +119,15 @@ export class CreateAppCommand {
}
private logSuccess(appDirectory: string): void {
const dirName = appDirectory.split('/').reverse()[0] ?? '';
console.log(chalk.green('✅ Application created!'));
console.log('');
console.log(chalk.blue('Next steps:'));
console.log(`cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
console.log('yarn auth');
console.log(chalk.gray(` cd ${dirName}`));
console.log(chalk.gray(` corepack enable # if you don't use yarn@4`));
console.log(chalk.gray(` yarn install # if you don't use yarn@4`));
console.log(chalk.gray(' yarn auth:login # Authenticate with Twenty'));
console.log(chalk.gray(' yarn app:dev # Start dev mode'));
}
}
@@ -32,7 +32,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',
@@ -41,15 +41,15 @@ describe('copyBaseApplicationProject', () => {
});
// Verify src/app/ folder exists
const srcAppPath = join(testAppDirectory, 'src', 'app');
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');
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/app/
const roleConfigPath = join(srcAppPath, 'default.role.ts');
expect(await fs.pathExists(roleConfigPath)).toBe(true);
});
@@ -67,9 +67,8 @@ 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.sync).toBe('twenty app sync');
expect(packageJson.scripts.dev).toBe('twenty app dev');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.4.0');
expect(packageJson.scripts['app:dev']).toBe('twenty app:dev');
});
it('should create .gitignore file', async () => {
@@ -103,7 +102,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',
@@ -114,20 +113,19 @@ describe('copyBaseApplicationProject', () => {
const appConfigPath = join(
testAppDirectory,
'src',
'app',
'application.config.ts',
);
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 './default-function.role'",
"import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/default.role'",
);
// Verify display name and description
@@ -141,11 +139,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',
@@ -153,12 +151,7 @@ describe('copyBaseApplicationProject', () => {
appDirectory: testAppDirectory,
});
const roleConfigPath = join(
testAppDirectory,
'src',
'app',
'default-function.role.ts',
);
const roleConfigPath = join(testAppDirectory, 'src', 'default.role.ts');
const roleConfigContent = await fs.readFile(roleConfigPath, 'utf8');
// Verify it uses defineRole
@@ -169,7 +162,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
@@ -185,7 +178,7 @@ describe('copyBaseApplicationProject', () => {
// Verify it has a universalIdentifier (UUID format)
expect(roleConfigContent).toMatch(
/universalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER/,
/universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER/,
);
});
@@ -216,7 +209,6 @@ describe('copyBaseApplicationProject', () => {
const appConfigPath = join(
testAppDirectory,
'src',
'app',
'application.config.ts',
);
const appConfigContent = await fs.readFile(appConfigPath, 'utf8');
@@ -247,11 +239,11 @@ describe('copyBaseApplicationProject', () => {
// Read both app configs
const firstAppConfig = await fs.readFile(
join(firstAppDir, 'src', 'app', 'application.config.ts'),
join(firstAppDir, 'src', 'application.config.ts'),
'utf8',
);
const secondAppConfig = await fs.readFile(
join(secondAppDir, 'src', 'app', 'application.config.ts'),
join(secondAppDir, 'src', 'application.config.ts'),
'utf8',
);
@@ -287,19 +279,19 @@ describe('copyBaseApplicationProject', () => {
appDirectory: secondAppDir,
});
// Read both role configs
const firstRoleConfig = await fs.readFile(
join(firstAppDir, 'src', 'app', 'default-function.role.ts'),
join(firstAppDir, 'src', 'default.role.ts'),
'utf8',
);
const secondRoleConfig = await fs.readFile(
join(secondAppDir, 'src', 'app', 'default-function.role.ts'),
join(secondAppDir, 'src', 'default.role.ts'),
'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/app';
const SRC_FOLDER = 'src';
export const copyBaseApplicationProject = async ({
appName,
@@ -21,24 +22,38 @@ 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,
});
await createDefaultFrontComponent({
appDirectory: sourceFolderPath,
});
await createDefaultFunction({
appDirectory: sourceFolderPath,
});
await createApplicationConfig({
displayName: appDisplayName,
description: appDescription,
appDirectory: appFolderPath,
appDirectory: sourceFolderPath,
});
};
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
@@ -64,6 +79,9 @@ generated
# dev
/dist/
.twenty/*
!.twenty/output/
# production
/build
@@ -87,7 +105,7 @@ yarn-error.log*
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
};
const createDefaultServerlessFunctionRoleConfig = async ({
const createDefaultRoleConfig = async ({
displayName,
appDirectory,
}: {
@@ -98,11 +116,11 @@ const createDefaultServerlessFunctionRoleConfig = async ({
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,
@@ -112,7 +130,78 @@ export default defineRole({
});
`;
await fs.writeFile(join(appDirectory, 'default-function.role.ts'), content);
await fs.writeFile(join(appDirectory, 'default.role.ts'), content);
};
const createDefaultFrontComponent = async ({
appDirectory,
}: {
appDirectory: string;
}) => {
const universalIdentifier = v4();
const content = `import { defineFrontComponent } from 'twenty-sdk';
export const HelloWorld = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Hello, World!</h1>
<p>This is your first front component.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: '${universalIdentifier}',
name: 'hello-world-front-component',
description: 'A sample front component',
component: HelloWorld,
});
`;
await fs.writeFile(
join(appDirectory, 'hello-world.front-component.tsx'),
content,
);
};
const createDefaultFunction = async ({
appDirectory,
}: {
appDirectory: string;
}) => {
const universalIdentifier = v4();
const triggerUniversalIdentifier = v4();
const content = `import { defineLogicFunction } from 'twenty-sdk';
const handler = async (): Promise<{ message: string }> => {
return { message: 'Hello, World!' };
};
// Logic function handler - rename and implement your logic
export default defineLogicFunction({
universalIdentifier: '${universalIdentifier}',
name: 'hello-world-logic-function',
description: 'A simple logic function',
timeoutSeconds: 5,
handler,
triggers: [
{
universalIdentifier: '${triggerUniversalIdentifier}',
type: 'route',
path: '/hello-world-logic-function',
httpMethod: 'GET',
isAuthRequired: false,
},
],
});
`;
await fs.writeFile(
join(appDirectory, 'hello-world.logic-function.ts'),
content,
);
};
const createApplicationConfig = async ({
@@ -124,14 +213,14 @@ const createApplicationConfig = async ({
description?: string;
appDirectory: string;
}) => {
const content = `import { defineApp } from 'twenty-sdk';
import { DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
const content = `import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/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,
});
`;
@@ -156,23 +245,29 @@ const createPackageJson = async ({
},
packageManager: 'yarn@4.9.2',
scripts: {
'create-entity': 'twenty app add',
dev: 'twenty app dev',
generate: 'twenty app generate',
sync: 'twenty app sync',
logs: 'twenty app logs',
uninstall: 'twenty app uninstall',
'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',
'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',
auth: 'twenty auth login',
lint: 'eslint',
'lint-fix': 'eslint --fix',
'lint:fix': 'eslint --fix',
},
dependencies: {
'twenty-sdk': '0.3.1',
'twenty-sdk': '0.4.0',
},
devDependencies: {
typescript: '^5.9.3',
'@types/node': '^24.7.2',
'@types/react': '^19.0.2',
react: '^19.0.2',
eslint: '^9.32.0',
'typescript-eslint': '^8.50.0',
},
+10 -11
View File
@@ -1,4 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"allowJs": false,
"esModuleInterop": false,
@@ -8,19 +9,17 @@
"noImplicitAny": true,
"strictBindCallApply": false,
"noEmit": true,
"types": ["jest", "node"],
"paths": {
"@/*": ["./src/*"]
}
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.d.ts",
"**/__mocks__/**/*",
"vite.config.ts",
"jest.config.mjs"
]
}
@@ -1,16 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "node"]
},
"include": [
"**/__mocks__/**/*",
"vite.config.ts",
"jest.config.mjs",
"src/**/*.d.ts",
"src/**/*.spec.ts",
"src/**/*.spec.tsx",
"src/**/*.test.ts",
"src/**/*.test.tsx"
]
}
@@ -75,6 +75,7 @@ export default defineConfig(() => {
'path',
'fs',
'child_process',
'util',
],
output: [
{
+1
View File
@@ -1 +1,2 @@
generated
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
@@ -230,7 +230,7 @@ npm run test -- --watch
npx twenty-cli app dev
# Type checking
npx tsc --noEmit
npx tsgo --noEmit
```
## Testing
@@ -24,10 +24,7 @@
}
},
"typecheck": {
"executor": "nx:run-commands",
"options": {
"command": "tsc --noEmit --project {projectRoot}/tsconfig.json"
}
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/eslint:lint",
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
@@ -1,7 +1,7 @@
import typescriptParser from '@typescript-eslint/parser';
import path from 'path';
import { fileURLToPath } from 'url';
import reactConfig from '../../../../../tools/eslint-rules/eslint.config.react.mjs';
import reactConfig from '../../../../twenty-eslint-rules/eslint.config.react.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -3,9 +3,8 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
"baseUrl": "src",
"paths": {
"@/*": ["*"]
"@/*": ["./src/*"]
}
}
}
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
File diff suppressed because one or more lines are too long
@@ -1,3 +1 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
@@ -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": "yarn@4.9.2",
"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:
@@ -8,7 +8,7 @@ COPY ./yarn.lock .
COPY ./.yarnrc.yml .
COPY ./.yarn/releases /app/.yarn/releases
COPY ./.yarn/patches /app/.yarn/patches
COPY ./tools/eslint-rules /app/tools/eslint-rules
COPY ./packages/twenty-eslint-rules /app/packages/twenty-eslint-rules
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-website/package.json /app/packages/twenty-website/package.json
+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
@@ -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,11 +9,11 @@ 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:**
@@ -33,30 +33,34 @@ Create a new app using the official scaffolder, then authenticate and start deve
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# If you don't use yarn@4
corepack enable
yarn install
# Authenticate using your API key (you'll be prompted)
yarn auth
yarn auth:login
# Start dev mode: automatically syncs local changes to your workspace
yarn dev
yarn app:dev
```
From here you can:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn create-entity
yarn entity:add
# Generate a typed Twenty client and workspace entity types
yarn generate
yarn app:generate
# Run a onetime sync (instead of watch mode)
yarn sync
# Watch your application's function logs
yarn function:logs
# Watch your application's functions logs
yarn logs
# Execute a function by name
yarn function:execute -n my-function -p '{"name": "test"}'
# Uninstall the application from the current workspace
yarn uninstall
yarn app:uninstall
# Display commands' help
yarn help
@@ -83,18 +87,17 @@ my-twenty-app/
.nvmrc
.yarnrc.yml
.yarn/
releases/
yarn-4.9.2.cjs
install-state.gz
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
application.config.ts # Required - main application configuration
default-function.role.ts # Default role for serverless functions
hello-world.function.ts # Example serverless function
hello-world.front-component.tsx # Example front component
// your entities (*.object.ts, *.function.ts, *.front-component.tsx, *.role.ts)
```
### Convention-over-configuration
@@ -105,6 +108,7 @@ Applications use a **convention-over-configuration** approach where entities are
|-------------|-------------|
| `*.object.ts` | Custom object definitions |
| `*.function.ts` | Serverless function definitions |
| `*.front-component.tsx` | Front component definitions |
| `*.role.ts` | Role definitions |
### Supported folder organizations
@@ -113,75 +117,93 @@ You can organize your entities in any of these patterns:
**Traditional (by type):**
```text
src/app/
src/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
├── components/
│ └── card.front-component.tsx
└── roles/
└── admin.role.ts
```
**Feature-based:**
```text
src/app/
src/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
├── card.front-component.tsx
└── postCardAdmin.role.ts
```
**Flat:**
```text
src/app/
src/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
├── card.front-component.tsx
└── admin.role.ts
```
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 `auth:login` 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:
- **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:
- `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.
- `*.role.ts`: Role definitions used by your logic 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.
- `*.function.ts`: Logic function definitions.
- `*.front-component.tsx`: Front component definitions.
Later commands will add more files and folders:
- `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
- `yarn create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
- `yarn app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
- `yarn entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
## Authentication
The first time you run `yarn auth`, you'll be prompted for:
The first time you run `yarn auth:login`, you'll be prompted for:
- API URL (defaults to http://localhost:3000 or your current workspace profile)
- API key
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
Examples:
### Managing workspaces
```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
```
Once you've switched workspaces with `auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
## Use the SDK resources (types & config)
The twenty-sdk provides typed building blocks and helper functions you use inside your app. Below are the key pieces you'll touch most often.
@@ -192,9 +214,9 @@ The SDK provides four helper functions with built-in validation for defining you
| Function | Purpose |
|----------|---------|
| `defineApp()` | Configure application metadata |
| `defineApplication()` | Configure application metadata |
| `defineObject()` | Define custom objects with fields |
| `defineFunction()` | Define serverless functions with handlers |
| `defineFunction()` | Define logic functions with handlers |
| `defineRole()` | Configure role permissions and object access |
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
@@ -278,63 +300,12 @@ 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 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';
@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)
@@ -344,14 +315,14 @@ Every app has a single `application.config.ts` file that describes:
- **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';
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './default-function.role';
export default defineApp({
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
@@ -364,18 +335,18 @@ export default defineApp({
isSecret: false,
},
},
functionRoleUniversalIdentifier: DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER,
roleUniversalIdentifier: 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).
- `roleUniversalIdentifier` must match the role you define in your `*.role.ts` 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 `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.
@@ -389,11 +360,11 @@ When you scaffold a new app, the CLI also creates a default role file. Use `defi
// src/app/default-function.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,
@@ -406,7 +377,7 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectNameSingular: 'postCard',
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
@@ -415,8 +386,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,
},
@@ -425,7 +396,7 @@ 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 `roleUniversalIdentifier`. 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.
@@ -436,25 +407,20 @@ 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.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';
const handler = 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({
@@ -482,17 +448,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.created',
},
// {
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
// type: 'databaseEvent',
// eventName: 'person.updated',
// updatedFields: ['name'],
// },
],
});
```
@@ -501,32 +468,122 @@ Common trigger types:
- **route**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
- **cron**: Runs your function on a schedule using a CRON expression.
- **databaseEvent**: Runs on workspace object lifecycle events
> e.g. `person.created`
- **databaseEvent**: Runs on workspace object lifecycle events. When the event operation is `updated`, specific fields to listen to can be specified in the `updatedFields` array. If left undefined or empty, any update will trigger the function.
> e.g. `person.updated`
Notes:
- The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions.
- You can mix multiple trigger types in a single function.
### Route trigger payload
<Warning>
**Breaking change (v1.16, January 2026):** The route trigger payload format has changed. Prior to v1.16, query parameters, path parameters, and body were sent directly as the payload. Starting with v1.16, they are nested inside a structured `RoutePayload` object.
**Before v1.16:**
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
**After v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**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 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';
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' };
};
```
The `RoutePayload` type has the following structure:
| Property | Type | Description |
|----------|------|-------------|
| `headers` | `Record<string, string \| undefined>` | HTTP headers (only those listed in `forwardedRequestHeaders`) |
| `queryStringParameters` | `Record<string, string \| undefined>` | Query string parameters (multiple values joined with commas) |
| `pathParameters` | `Record<string, string \| undefined>` | Path parameters extracted from the route pattern (e.g., `/users/:id` → `{ id: '123' }`) |
| `body` | `object \| null` | Parsed request body (JSON) |
| `isBase64Encoded` | `boolean` | Whether the body is base64 encoded |
| `requestContext.http.method` | `string` | HTTP method (GET, POST, PUT, PATCH, DELETE) |
| `requestContext.http.path` | `string` | Raw request path |
### Forwarding HTTP headers
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({
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'],
},
],
});
```
In your handler, you can then access these headers:
```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>
Header names are normalized to lowercase. Access them using lowercase keys (for example, `event.headers['content-type']`).
</Note>
You can create new functions in two ways:
- **Scaffolded**: Run `yarn create-entity` and choose the option to add a new function. This generates a starter file with a handler and config.
- **Scaffolded**: Run `yarn entity:add` 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.
### Generated typed client
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
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 generate`. Re-run after changing your objects and `yarn 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:
@@ -535,8 +592,8 @@ 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 `roleUniversalIdentifier`. 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 `roleUniversalIdentifier` to that role's universal identifier.
### Hello World example
@@ -556,25 +613,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 dev`, `yarn 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` and ensure your API key has the required permissions.
- 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 generate` and then `yarn dev`.
- Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
- 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
@@ -288,3 +288,44 @@ yarn command:prod cron:workflow:automated-cron-trigger
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
</Warning>
## Logic Functions
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 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 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) |
### Recommended Configuration
**For development:**
```bash
SERVERLESS_TYPE=LOCAL # default
```
**For production (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
```
**To disable logic functions:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
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
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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