Commit Graph
2545 Commits
Author SHA1 Message Date
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
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
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
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 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
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
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
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
Thomas TrompetteandGitHub 93aef30046 Add feature flag for sse (#17152)
As title
2026-01-14 14:20:45 +00:00
Paul RastoinandGitHub 9e4247c934 Clean soft deleted suspended workspace command and ignore-grace-period flag (#17144)
# Introduction

In the https://github.com/twentyhq/core-team-issues/issues/1989 's
context we will apply migration through an upgrade command post entity
backfill to fit the applied constraint. But suspended soft deleted
workspace are not included in the upgrade workspace batches

In order to be able to pass the constraint in production, discussed with
@FelixMalfait, we will manually clear all the currently suspended and
soft deleted workspace ignore their grace period

## Force mode
When running the command in force it will ignore the limit per execution
( which really serve the cron job ) and the grace period

## Test
Tested on a production extract
2026-01-14 14:15:15 +00:00
Paul RastoinandGitHub 2c24180b44 Identify standard view fields and views (#17118)
# Introduction
Related 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 `standardId` or `isCustom`

## Test
Both tested on prod extract
Some view field are set as non custom is prod whereas they should for
several manually handle-able workspace amount
2026-01-14 14:15:07 +00:00
Thomas TrompetteandGitHub ec87b29286 Move query matching before publication (#17121)
- new channel EVENT_STREAM_CHANNEL based on event stream id
- on event, perform the matching and publish only to the right streams
- store a list of active streams per workspace
- store the user id along with the queries for each stream

Bonus:
- remove onSubscriptionMatch
2026-01-14 12:29:12 +00:00
Paul RastoinandGitHub 70704264ac Fix dev seed page layout cache invalidation (#17141)
# Introduction

followup https://github.com/twentyhq/twenty/pull/16962

Root cause: as now the twenty standard app installation in seeded
workspace invalidates and set the page layout-xxx caches the inserted
through direct repo access data in the prefill legacy page layout
methods do not interact with the cache
The cache being set in prior when recompute does not result in db
introspection as before as when was unset
2026-01-14 12:08:51 +00:00
Raphaël BosiandGitHub f793faae2b 17042 followups (#17122)
Followups after @Weiko review on
https://github.com/twentyhq/twenty/pull/17042
2026-01-13 16:25:30 +00:00
Paul RastoinandGitHub 46cf551281 Invalidate legacy cache after flats (#17126)
# Introduction

Some legacy caches are based on the flat ones
So we need to seq invalidate before invalidating others as it could
result in inter dep cache invalidation race condition
2026-01-13 16:07:27 +00:00
4a3cc0aeb8 i18n - translations (#17125)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-13 16:38:18 +01:00
martmullandGitHub d55b83375d Allow app to extend object (#17116)
- exports `extendObject` utils from `twenty-sdk`
- define `*.object-extension.ts` filename pattern to extend and object
- handle object-extension configs in manifest
2026-01-13 15:07:14 +00:00
Paul RastoinandGitHub f1be7129cb Allow isUnique mutation on standard field (#17120)
# Introduction
As we've just
[identified](https://github.com/twentyhq/twenty/pull/16981) the standard
field metadata in production we can not enable the is unique comparison
even for standard entities
2026-01-13 13:28:50 +00:00
db87da162c i18n - translations (#17119)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-13 11:21:29 +01:00
b0d6571469 Improve Japanese translations (#16979)
The current Japanese translations are severely lacking. My company
(based in Tokyo) uses Japanese as its main language. I've gone through
and corrected/standardized everything. This is good enough to merge as a
starting point.

Includes `lingui compile` for the changes.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Major localization refresh focused on Japanese.
> 
> - Rewrites and standardizes `ja-JP` message strings (clearer phrasing,
consistent terminology, corrected placeholders/punctuation)
> - Regenerates compiled Lingui locale bundles; updates `ja-JP.ts` and
`ko-KR.ts` outputs
> - No runtime logic changes; scope limited to generated i18n resources
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3365dcdac6. 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-13 10:28:45 +01:00
Abdullah.andGitHub 2c8d3f02e1 feat: upgrade to Storybook version 10 (#17110)
Upgraded to Storybook 10. We still use `@storybook/test-runner` for
testing since it appears it'd require more work to move from Jest to
Vitest than I initially anticipated, but I completed this PR to fix
`storybook:serve:dev` - it takes time to load, but it works the way it
used to with Storybook 8.


https://github.com/user-attachments/assets/7afc32c6-4bcf-4b37-b83b-8d00d28dda15
2026-01-13 08:18:07 +00:00
Paul RastoinandGitHub df108ea040 Identify standard objects (#17091)
# Introduction
Followup https://github.com/twentyhq/twenty/pull/16981

1/ Migration, applicationId and universalIdentifier are required on
entity ( save point migration + upgrade command fallback pattern )
2/ Backfill using previous standard ids
2026-01-12 18:06:13 +00:00
Lucas BordeauandGitHub 04a370e043 Implemented SSE subscription mechanism on the frontend (#17017)
This PR is a follow-up of https://github.com/twentyhq/twenty/pull/16966
and implements a new mechanism to handle SSE events.

It creates only one event stream per browser tab, then use mutations to
tell the backend which query to listen to, without re-mounting the event
stream connexion.

Then each event that comes from this unique subscription is then
dispatched in a new JavaScript CustomEvent, per queryId, on which
specific hooks add an event listener.

This PR introduces the generic tooling as well as the handling of update
events on table.
2026-01-12 18:58:46 +01:00
1c2fda30bd i18n - translations (#17106)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-12 18:58:01 +01:00
c795b8c52a RLS FE implementation (#17062)
## Summary
This PR introduces row-level security (RLS) permissions for roles in the
frontend, allowing fine-grained access control at the record level.
Users can now define permission rules that determine which specific
records a role can access based on dynamic conditions and filters.
## What's Changed
Implemented UI for configuring record-level permissions on object
permissions screens
Added support for defining permission predicates using filter conditions
(similar to advanced filters)
Introduced variable picker for dynamic permission rules (e.g., "me"
context for user-specific access)
Built predicate conversion layer to sync UI state with backend
permission structure
Extended GraphQL schema with mutations for upserting row-level
permission predicates
Fixed handling of orphaned RLS groups to prevent data inconsistencies
Added enterprise key validation for RLS features
The implementation enables scenarios like "users can only see their own
records" or "users can access records associated with their team."

<img width="687" height="702" alt="Screenshot 2026-01-09 at 23 07 02"
src="https://github.com/user-attachments/assets/33fe736e-6cbf-40bd-b2eb-c8a90c8d21bc"
/>

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-12 17:39:17 +00:00
493f0575ea i18n - translations (#17097)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-12 14:29:26 +01:00
Raphaël BosiandGitHub 655f1eef5f [DASHBOARDS] Allow dashboards to be restored (#17042)
This PR introduces a few changes:
- Add three actions: see deleted dashboards, destroy dashboard and
restore dashboard
- Remove the soft delete and restore on all the page layout entities
- Cascade the destruction of a dashboard to a page layout

Video QA:


https://github.com/user-attachments/assets/ab993b11-dd9c-4e88-880c-92691a521cc2
2026-01-12 12:59:17 +00:00
MarieandGitHub 3ada8e5168 [Extensibility] Support relation fields (#17056)
Closes https://github.com/twentyhq/core-team-issues/issues/2043
2026-01-12 12:54:30 +00:00
Paul RastoinandGitHub 91b788ee09 [REQUIRES_FIELDS_CACHE_FLUSH]Fix field entity to flat field transpiler (#17096)
Following https://github.com/twentyhq/twenty/pull/16981

UniversalIdentifier is now required in the entity so no need to fallback
it
2026-01-12 12:49:07 +00:00
nitinandGitHub 342ae995d9 Add error boundary on widget renderer and minor widget seed fix (#17058)
closes https://github.com/twentyhq/twenty/issues/16896 and
https://github.com/twentyhq/core-team-issues/issues/2050

should error be Invalid Configuration or something else?
2026-01-12 12:02:58 +00:00
238e6d5cda Common api - chores (#17051)
Remove refacto-common TODO

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-12 11:37:29 +00:00
Paul RastoinandGitHub a6f371a42a Identify standard field do deploy until IS_WORKSPACE_CREATION_V2_ENABLED is enabled in prod (#16981)
# Introduction

fixes https://github.com/twentyhq/twenty/issues/16905

Do not merge until `IS_WORKSPACE_CREATION_V2_ENABLED` has been activated
by default, and so sync metadata has been deprecated by doing so. As the
sync metadata will attempt to insert `null` `applicationId` and
`universalIdentifier` values while creating a workspace

In this PR we're introducing a new `SyncableEntityRequired` which
enforces the non nullable `applicationId` and `universalIdentifier` on
extending entity

In this PR we also migrate the field metadata entity to extend the
required

## Identification upgrade command
This command will search for workspace field metadata entities that
aren't associated to an applicationId, dispatch them to either the
workspace-custom `applicationId` or the twenty-standard `applicationId`.
For the standard entities it will also set their universal identifier
based on the `STANDARD_OBJECTS` const hashmap

## Typeorm migration
As the non nullable `applicationId` and `universalIdentifier`migration
won't pass in the first we've been using the save point and upgrade
command migration fallback pattern

## Tests
Tested the command on a prod extract locally

Both `twenty-eng` and `twenty-for-twenty` have unexpected standard
objects
Please note that we will deprecate the `isCustom` and `standardId` col
later in the future

### Twenty-eng
```ts
[Nest] 98971  - 01/01/2026, 3:18:00 PM     LOG [IdentifyStandardEntitiesCommand] Successfully validated 600/600 field metadata update(s) for workspace 9870323e-22c3-4d14-9b7f-5bdc84f7d6ee (309 custom, 291 standard)
[Nest] 98971  - 01/01/2026, 3:18:00 PM    WARN [IdentifyStandardEntitiesCommand] Found 35 warning(s) while processing field metadata for workspace 9870323e-22c3-4d14-9b7f-5bdc84f7d6ee. These fields will become custom.
```

### Twenty for twenty

### Just created workspace
2026-01-12 09:59:01 +00:00
46e5420d20 dashboard workspace standard seeds (#16962)
# Introduction

In this pull request we're introducing new standard page layout, tabs
and widget ( 1 page layout, 1 tab and 8 widgets ) and also a new
opportunity field

Also now prefilling new records, 6 opportunities and a dashboard.

## Standard declaration
### New workspace creation
Relies on existing standard declaration builder

### Backfill command
We've been hacking through the standard builder in order to extract only
the standard page layout entities, updated their entity dependencies to
match the workspace ids so the validation passes


## Remark
- Refactored the `PageLayoutWidget` configuration type to be dynamically
typed through a generic discriminated union

---------

Co-authored-by: prastoin <paul@twenty.com>
2026-01-12 09:34:15 +00:00
Félix MalfaitandGitHub 57363c2127 Fix orderBy columns missing from SELECT in DISTINCT subquery (#17079)
## Description

Fixes a bug where queries with relation field + scalar field ordering
would fail with:
```
column distinctAlias.person_position does not exist
```

## Root Cause

When a GraphQL query orders by columns that are **not in the selected
fields**, TypeORM's DISTINCT subquery fails because it expects those
columns in the inner SELECT with alias format (e.g., `person_position`).

The issue only manifests when:
1. A filter is applied (triggers DISTINCT path in TypeORM)
2. OrderBy includes columns NOT in the GraphQL selection
3. Example: query selects only `id`, but orders by `position`

## The Fix

`addRelationOrderColumnsToBuilder` now accepts `columnsToSelect` and
adds orderBy columns via `addSelect()` only if they're **NOT** already
in the selected columns:

- **Relation orderBy columns**: Always added (never in columnsToSelect)
- **Main entity orderBy columns**: Added only when not already selected

This ensures all orderBy columns are present in TypeORM's inner SELECT
for the DISTINCT subquery.

## Test Plan

Added integration test for the exact failing scenario:
- Filter with `neq` (triggers DISTINCT path)
- Multiple orderBy: relation field + scalar field  
- Minimal field selection (only `id`, not `position`)

## Related

Regression introduced in #17021 which added nested sort support.
2026-01-11 14:19:05 +01:00
20f62e05f5 fix(search): add support for searching by additional emails, phones, and secondary links (#17034)
## Summary
- Add `additionalEmails` (EMAILS type) to tsvector search expression
- Add `additionalPhones` (PHONES type) to tsvector search expression  
- Add `secondaryLinks` (LINKS type) to tsvector search expression

This enables searching for people/companies by their secondary contact
information, not just primary values.

## Test plan
- [x] Unit tests for all three composite field types (16 tests passing)
- [x] Integration tests for searching by secondary email, work email,
partial domain
- [x] Integration tests for searching by additional phone numbers
- [x] Integration test for searching by secondary link URL
- [x] Lint passes

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-10 15:24:46 +00:00
Thomas TrompetteandGitHub 3acc87a620 [SSE] Add backend for SSE subscriptions (#17022)
- moved a few gql types to twenty shared to re-use in server
- added a new endpoint, onEventSubscription that expect a streamId to
create a connection
- two new endpoints to store queries in Redis
- updated the existing batch channel to directly use object record type
2026-01-10 14:44:02 +00:00
Félix MalfaitandGitHub a8331dc43e feat: add case-insensitive sorting for text fields (#17023)
## Context

Text fields were being sorted case-sensitively on the backend (e.g.,
'Apple', 'apple', 'Banana' would sort as 'Apple', 'Banana', 'apple').
This resulted in unexpected sorting behavior that differed between the
frontend Apollo cache sorting and backend database sorting.

## Changes

### Backend (packages/twenty-server)

- **`graphql-query-order.parser.ts`**:
- Added `shouldUseCaseInsensitiveOrder()` helper that returns `true` for
TEXT, SELECT, and MULTI_SELECT fields
- Added `buildOrderByColumnExpression()` method that wraps column
expressions with `LOWER()` for case-insensitive sorting
- Updated `parse()` and `parseObjectRecordOrderByForScalarField()` to
use the new helpers
- Updated `parseObjectRecordOrderByForRelationField()` to apply LOWER()
to nested text fields

- **`parse-composite-field-for-order.util.ts`**:
- Added `shouldUseCaseInsensitiveOrder()` helper for composite subfields
- Updated composite field parsing to apply `LOWER()` to subfields of
type TEXT (e.g., `name.firstName`, `name.lastName`)

### Frontend (packages/twenty-front)

- **`sort.ts`**:
- Updated `sortAsc()` to use case-insensitive comparison
(`toLowerCase()`) for string values
- `sortDesc()` automatically benefits from this since it delegates to
`sortAsc()`
  - Ensures Apollo cache sorting matches backend behavior

## Example

Before:
```sql
ORDER BY "person"."name" ASC
```
Result: ['Apple', 'Banana', 'apple', 'cherry']

After:
```sql
ORDER BY LOWER("person"."name") ASC
```
Result: ['apple', 'Apple', 'Banana', 'cherry']

## Testing

- Manual testing of sorting on People and Companies views
- Verified frontend cache sorting matches backend results
2026-01-10 14:05:56 +01:00
Félix MalfaitandGitHub 1a5675d63e feat: add sorting on relation fields (Many-to-One) (#17021)
## Summary

This PR enables sorting records by fields of related objects. For
example, sorting **People by their Company's name**.

### Before
Only scalar and composite fields could be sorted. Relation fields showed
in the sort dropdown but produced errors.

### After
Many-to-One relation fields can now be sorted using the related object's
**label identifier field** (e.g., Company's `name`).

---

## Changes

### Frontend
- Added `RELATION` to sortable field types (restricted to `MANY_TO_ONE`
relations)
- New `getOrderByForRelationField()` generates nested orderBy structures
using the related object's label identifier
- Updated `turnSortsIntoOrderBy()` to handle relation fields by looking
up related object metadata

### Backend
- Extended `GraphqlQueryOrderFieldParser.parse()` to detect nested
relation ordering like `{ company: { name: 'AscNullsLast' } }`
- Returns `ParseOrderByResult` containing both `orderBy` conditions and
`relationJoins` info
- Added LEFT JOINs for relation ordering in `applyOrderToBuilder()`
- Added `addRelationOrderColumnsToBuilder()` for TypeORM DISTINCT
compatibility

### Tests
- Added unit tests for `filterSortableFieldMetadataItems`,
`getOrderByForRelationField`, and `turnSortsIntoOrderBy`
- Added integration tests covering ascending/descending order and
composite label identifiers

---

## TypeORM Bug Workaround

We encountered a significant TypeORM limitation when implementing this
feature. When using `getMany()` with `ORDER BY` on joined relation
columns, TypeORM generates a DISTINCT subquery that has specific
requirements:

### Issue 1: Alias Parsing
TypeORM's `orderBy()` method fails with **"alias not found"** when using
quoted SQL identifiers like `"company"."name"`. TypeORM internally
expects unquoted property paths (e.g., `company.name`) for its alias
resolution mechanism.

### Issue 2: setFindOptions Clears addSelect
`setFindOptions({ select })` **clears any previously added `addSelect()`
columns**. This caused `"column distinctAlias.company_name does not
exist"` errors because the relation columns needed for ORDER BY were
being removed.

### Solution
We split the logic into two methods:
1. `applyOrderToBuilder()` - adds JOINs and ORDER BY (before
`setFindOptions`)
2. `addRelationOrderColumnsToBuilder()` - adds relation columns for
SELECT (AFTER `setFindOptions`)

This ensures the relation columns are present in the final SQL query's
SELECT clause with the proper underscore aliases (`company_name`) that
TypeORM's DISTINCT subquery expects.

**Related TypeORM issue**:
https://github.com/typeorm/typeorm/issues/9921

---

## Screenshots/Demo

_Add screenshots if applicable_
2026-01-10 11:02:16 +01:00