diff --git a/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx b/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx
new file mode 100644
index 0000000000..becda88783
--- /dev/null
+++ b/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx
@@ -0,0 +1,107 @@
+---
+title: Targeting System Fields
+description: Reference auto-created system fields like createdAt or updatedAt from views and other entities with getFieldUniversalIdentifier.
+icon: "gears"
+---
+
+Every object in Twenty ships with a set of **system fields** that you never declare yourself. They are created automatically by the server when the object is provisioned:
+
+`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
+
+Because you don't declare these fields with [`defineField()`](/developers/extend/apps/data/extending-objects), there's no `universalIdentifier` constant for you to import. So how do you reference `createdAt` as a column in a [view](/developers/extend/apps/layout/views)?
+
+## The problem
+
+Since Twenty 2.19, a system field's universal identifier is **derived deterministically** by the server from three inputs: the application universal identifier, the object universal identifier, and the field name. Inventing an id and hardcoding it won't work: it matches nothing on the server, and the sync rejects the dangling reference:
+
+```
+Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
+```
+
+## The solution
+
+
+ `getFieldUniversalIdentifier` is available from `twenty-sdk` 2.21 onward.
+
+
+Use `getFieldUniversalIdentifier` to resolve the exact same value the server uses. It takes the three inputs and returns the field's universal identifier:
+
+```ts
+import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
+
+const createdAtFieldId = getFieldUniversalIdentifier({
+ applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
+ objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
+ name: 'createdAt',
+});
+```
+
+- `applicationUniversalIdentifier` is your app's identifier, the one you pass to [`defineApplication()`](/developers/extend/apps/config/application).
+- `objectUniversalIdentifier` is the identifier of the object the field belongs to.
+- `name` is the system field name, one of the values listed above.
+
+## Example: a createdAt column in a view
+
+The typical case is adding a `createdAt` column to a view of one of your custom objects. Resolve the field id and reference it as any other `fieldMetadataUniversalIdentifier`:
+
+```ts src/views/example-view.ts
+import {
+ defineView,
+ getFieldUniversalIdentifier,
+} from 'twenty-sdk/define';
+
+const APPLICATION_UNIVERSAL_IDENTIFIER =
+ '0b04e15c-27b2-4741-9046-b32e07469072';
+const MY_OBJECT_UNIVERSAL_IDENTIFIER =
+ 'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
+
+export default defineView({
+ universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
+ name: 'All records',
+ objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
+ icon: 'IconList',
+ position: 0,
+ fields: [
+ {
+ universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
+ fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
+ applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
+ objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
+ name: 'createdAt',
+ }),
+ position: 0,
+ isVisible: true,
+ size: 200,
+ },
+ ],
+});
+```
+
+The same resolved id works anywhere a `fieldMetadataUniversalIdentifier` is expected: view fields, filters, sorts, groups, and page-layout widgets.
+
+
+ Resolve the id, don't hardcode it. Because the server derives the value from
+ the application id, the object id and the field name, calling
+ `getFieldUniversalIdentifier` keeps your reference correct even if those
+ inputs change, and avoids drift if the derivation ever evolves.
+
+
+## Standard Twenty objects
+
+For a **standard** Twenty object (Person, Company, Opportunity, …), you don't need to derive anything: the system field identifiers are pre-computed constants you can import directly.
+
+```ts
+import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
+
+// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
+// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
+```
+
+Reach for `getFieldUniversalIdentifier` when the object is one **your app** defines with [`defineObject()`](/developers/extend/apps/data/objects), where no such constant exists.
+
+
+ `name` is a **default** field, not a system field. It keeps its own hardcoded
+ universal identifier and is not resolved through
+ `getFieldUniversalIdentifier`. On objects you define, reference the
+ `name` field by the identifier you gave it in `defineObject()`.
+
diff --git a/packages/twenty-docs/developers/extend/apps/layout/views.mdx b/packages/twenty-docs/developers/extend/apps/layout/views.mdx
index 8d265e31e8..e64adf6184 100644
--- a/packages/twenty-docs/developers/extend/apps/layout/views.mdx
+++ b/packages/twenty-docs/developers/extend/apps/layout/views.mdx
@@ -34,7 +34,7 @@ export default defineView({
- `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object.
- `key: ViewKey.INDEX` marks the view as the object's main list view (the one an `OBJECT` navigation item opens).
-- `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`.
+- `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`. To reference an auto-created system field such as `createdAt`, see [Targeting System Fields](/developers/extend/apps/data/system-fields).
- You can also declare `filters`, `filterGroups`, `sorts`, `groups`, and `fieldGroups` for advanced configurations.
- `position` controls ordering when multiple views exist for the same object.
diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json
index 37c6846d39..9e1856c115 100644
--- a/packages/twenty-docs/docs.json
+++ b/packages/twenty-docs/docs.json
@@ -413,6 +413,7 @@
"developers/extend/apps/data/overview",
"developers/extend/apps/data/objects",
"developers/extend/apps/data/extending-objects",
+ "developers/extend/apps/data/system-fields",
"developers/extend/apps/data/relations"
]
},
diff --git a/packages/twenty-docs/navigation/base-structure.json b/packages/twenty-docs/navigation/base-structure.json
index 08261a0ce3..372f7c69d6 100644
--- a/packages/twenty-docs/navigation/base-structure.json
+++ b/packages/twenty-docs/navigation/base-structure.json
@@ -413,6 +413,7 @@
"developers/extend/apps/data/overview",
"developers/extend/apps/data/objects",
"developers/extend/apps/data/extending-objects",
+ "developers/extend/apps/data/system-fields",
"developers/extend/apps/data/relations"
]
},
diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap
index ac1d691261..954cca8a18 100644
--- a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap
+++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap
@@ -37,6 +37,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1
"everyEquals",
"favoriteRecordIds",
"featureFlags",
+ "getFieldUniversalIdentifier",
"hasAnySoftDeleteFilterOnView",
"includes",
"includesEvery",
diff --git a/packages/twenty-sdk/src/sdk/define/index.ts b/packages/twenty-sdk/src/sdk/define/index.ts
index a75a2a6061..44320b7839 100644
--- a/packages/twenty-sdk/src/sdk/define/index.ts
+++ b/packages/twenty-sdk/src/sdk/define/index.ts
@@ -23,6 +23,7 @@ export type {
} from '@/sdk/define/fields/composite-fields';
export { defineField } from '@/sdk/define/fields/define-field';
export { FieldType } from '@/sdk/define/fields/field-type';
+export { getFieldUniversalIdentifier } from 'twenty-shared/application';
export { OnDeleteAction } from '@/sdk/define/fields/on-delete-action';
export { RelationType } from '@/sdk/define/fields/relation-type';
export { validateFields } from '@/sdk/define/fields/validate-fields';
diff --git a/packages/twenty-shared/src/constants/DocumentationPaths.ts b/packages/twenty-shared/src/constants/DocumentationPaths.ts
index 96d4f6f4e6..ce0ad2214e 100644
--- a/packages/twenty-shared/src/constants/DocumentationPaths.ts
+++ b/packages/twenty-shared/src/constants/DocumentationPaths.ts
@@ -32,6 +32,8 @@ export const DOCUMENTATION_PATHS = {
DEVELOPERS_EXTEND_APPS_DATA_OVERVIEW: '/developers/extend/apps/data/overview',
DEVELOPERS_EXTEND_APPS_DATA_RELATIONS:
'/developers/extend/apps/data/relations',
+ DEVELOPERS_EXTEND_APPS_DATA_SYSTEM_FIELDS:
+ '/developers/extend/apps/data/system-fields',
DEVELOPERS_EXTEND_APPS_GETTING_STARTED_CONCEPTS:
'/developers/extend/apps/getting-started/concepts',
DEVELOPERS_EXTEND_APPS_GETTING_STARTED_LOCAL_SERVER: