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.
# 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
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
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
# 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
> [!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>
## 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>
# 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 )
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, ...
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
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
## 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
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.
- 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
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.
## 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()`
## 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"
/>
## 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
- 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.
## 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
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 -->
- Fixestwentyhq/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>