# 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
# 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
# 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
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
f208fa23b1135af114f484f6d65041a816d6e718. 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>
# 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
# 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
# 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
# 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
# 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
# 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
# Introduction
Instead of comparing existing to expected comparing expected to existing
Also improved logs and fallback use cases
# Test
Tested on a prod extract
# 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
# 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
# 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
# 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>
# Introduction
Followup of
https://github.com/twentyhq/twenty/pull/17001#pullrequestreview-3638508738
close https://github.com/twentyhq/core-team-issues/issues/1910
We've completely decom the `sync-metadata` in production. We're now then
removing its implementation in favor of the v2.
## TODO:
- [x] Remove sync-metadata implem and commands
- [x] Remove workspace decorators
- [x] Type each deprecated field to deprecated on their workspaceEntity
- [x] Remove the `workspace-sync-metadata` folder entirely
- [x] remove workspace migration
- [x] workspace migration removal migration
- [x] remove the `v2` references from workspace manager file names
- [x] remove the `v2` references from workspace manager modules
- [ ] Double check impact on translation file path updates
## Note
- Removed the gate logic
- Remains some service v2 naming, serverless needs to be migrated on v2
fully
- Removed workspaceMigration service app health consumption, making it
always returning up ( no more down ) cc @FelixMalfait ( quite obsolete
health check now, will require complete refactor once we introduce inter
app dependency etc )
## Introduction
On the side hanlded PR with a // agents on another repo
Made several iterations to fix behavior and direction
Find below auto-generated PR description
closes https://github.com/twentyhq/core-team-issues/issues/2037
Created generic tooling for entity circular dep checking, will be useful
for permissions validation too @Weiko
## Migrate `viewFilterGroup` entity to v2 flat architecture
### Summary
Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity
architecture, following the established patterns for other v2 entities
like `viewFilter`, `view`, and `viewField`.
### Changes
**Types & Constants**
- Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types
- Added editable properties constant for `viewFilterGroup`
- Registered `viewFilterGroup` in `ALL_METADATA_NAME`,
`ALL_METADATA_RELATION_PROPERTIES`,
`ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants
**Cache Service**
- Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper
relation loading for `viewFilters` and `childViewFilterGroups`
- Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups`
relation
**Builder & Validator**
- Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService`
- Created `FlatViewFilterGroupValidatorService` with creation, update,
and deletion validation
- Integrated validation into the orchestrator service (runs before
`viewFilter` validation)
**Action Handlers**
- Created create, update, and delete action handlers for
`viewFilterGroup`
**Service Migration**
- Rewrote `ViewFilterGroupService` to use v2 migration pattern with
`WorkspaceMigrationValidateBuildAndRunService`
- Created utility functions for transforming DTOs to flat entities
**Database Migration**
- Added migration to make `parentViewFilterGroupId` foreign key
deferrable (handles self-referential parent/child insertions)
**ViewFilter Integration**
- Added `viewFilterGroupId` validation in
`FlatViewFilterValidatorService`
- Updated `viewFilter` many-to-one relations to include
`viewFilterGroup`
**Tests**
- Added integration tests for successful creation, update, deletion, and
destruction
- Added failing test cases for non-existent entities and invalid
references
- Added failing test for `viewFilter` creation with non-existent
`viewFilterGroupId`
### Breaking Changes
None - existing API contracts are preserved.
# Introduction
The `AddWorkspaceForeignKeys1767002571103` migration would fail when
released in production right now, as `foreignKey` be applicable as
there's a lof of orphan entries in database
As a workaround in order not to block any patch release we're
fallbacking the migration using save point and an upgrade command that
will attempt to apply the `foreignKey` on every workspace upgrade until
it succeed
We should keep in mind that any new fresh self installation will have
the foreignKey double checked that it would not implies regression on
workspace deletion using the integration tests
## Cleaning upgrade command
We won't implement the cleaning command in this PR yet either will I as
discussed with @Weiko someone else might be taking the subject starting
next week
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Strengthens workspace data integrity and makes the FK migration
resilient.
>
> - Adds `upgrade:1-16:add-workspace-foreign-keys-migration` command to
apply `workspaceId` FKs once per run; wires into
`V1_16_UpgradeVersionCommandModule` and 1.16 upgrade sequence
> - Refactors migration `1767002571103` to use
`addWorkspaceForeignKeysQueries` util and wrap in a savepoint,
swallowing errors to avoid blocking releases
> - Extracts FK DDL into
`utils/1767002571103-addWorkspaceForeignKeys.util` for reuse by command
and migration
> - Removes duplicate `workspaceId` columns from entities (e.g.,
`cronTrigger`, `databaseEventTrigger`, `indexMetadata`,
`objectMetadata`, `roleTarget`, `role`, `serverlessFunction`) relying on
`SyncableEntity`; keeps indexes/relations
> - Marks legacy delete paths as deprecated; temporarily extends
`WorkspaceManagerService.delete` to also delete `serverlessFunction` by
`workspaceId`
> - Updates wiring to inject `ServerlessFunctionEntity` repository in
`workspace-manager` module/service and corresponding unit test
> - Extends integration tests and adds GraphQL helpers to create
serverless functions and triggers; verifies cascade deletion of related
metadata on workspace removal
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6805bf5d1b32828b4bb1e9f130bfe6e478f66aee. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
# Introduction
In this pull-request we're refactoring the page layout widget
configuration entity to be containing its discriminated key simplifying
underlying code and maintainability
- Made the configuration and title non nullable
- Introduced a generic predicate for the `widgetConfigurationType`
- Upgraded command to remove `graphType` and insert new
`configurationType` to existing entries
- Migrated frontend to new type system
---------
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Following [discord
thread](https://discord.com/channels/1130383047699738754/1453910755387899996/1453910755387899996),
reproductible on twenty-eng
https://github.com/user-attachments/assets/ae7f363d-87e1-44fa-8fe2-ee78412d62a7
Records positions are computed at record creation, depending on the
position arg from the request, being equal to `last`, `first`, or not
present.
When being equal to last, as it is done when creating a note from the
product, the position is calculated using `.maximum()` function which
uses postgres' MAX function. If there is a `NaN` value among the list,
the MAX will return `NaN` too. So if for some reason there is a NaN
somewhere in the position column, all subsequent records being created
with last position argument will be created with NaN value. Until [this
PR](https://github.com/twentyhq/twenty/pull/16630), where we introduced
a validation on position at record creation which throws when NaN is
trying to be introduced as a position, this was going silent. (fyi
@etiennejouan , not on you at all but for info)
Looking into twenty-eng workspace, I found note records with NaN
position dating back to august 2025, making it hard to understand and
debug why they were introduced with NaN position. So I did not find the
real root cause, but I suggest to
- update record-position.service to fix the issue for subsequent records
that go through this service (which is what is currently broken)
- run a command to fix the existing records with NaN position for Notes,
as it is where the issue happened for both the user reporting the issue
on discord, and us on twenty-eng. So hopefully the problem was limited
to Notes
# Introduction
Creating dedicated folders and module for both `page-layout-tab` and
`page-layout-widget`
The addition diff with deletion is due to the module being added
# Introduction
In this pullrequest have been migrated to the flat standard entities:
Role, Agent and RoleTargets.
## What happens
- Removed createStandardMorph tool util in favor of dynamic typing of
`createMorphOrRelationStandardField`
- Implemented a command to remove standard agents and their role that
has been removed in https://github.com/twentyhq/twenty/pull/16513 also
added a default role target to data manipulator role to the only
remaining agent
- Implemented an agent deleteMany service handler
- moves applicationRoleId to application entity
- add new `APPLICATION` FieldActorSource and `APPLICATION`
JwtTokenTypeEnum value
- create a new token with applicationId when executing a function
- when applicationId is in token, check for application.defaultRole
permissions
-use twenty-shared types in `twenty-sdk/application`
- create a new import from generate called "Twenty" that you can use
directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep
metadata or core parameter only)
- provide to serverless unique one time BEARER TOKEN to run it
Result
<img width="977" height="566" alt="image"
src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c"
/>
<img width="910" height="596" alt="image"
src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324"
/>
<img width="741" height="568" alt="image"
src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f"
/>
## Introduction
When migrating a v1 index name to v2 it might collide with an existing
v2 index
In this case we remove both the v1 metadata and pg index
In case of a metadata and pg_index desync this might occur too late in
the process that's why we have two fallback
One computing the v1 deletion from the metadata and another one in the
catch block of the v1 to migration transaction commit
# Introduction
Related https://github.com/twentyhq/core-team-issues/issues/1910
From now on the upgrade won't integrate any sync metadata as it's going
to be deprecated very soon
Any updates to about to removes workspace-entity or standard flat entity
will require a dedicated upgrade command, what we've already started
doing during the 1.13 sprint, until we have totally migrated the v2 to
be workspace agnostic