docs(apps): add Targeting System Fields page (#22856)
## What
Adds a docs page teaching app developers how to reference auto-created
**system fields** (`createdAt`, `updatedAt`, `id`, …) from views and
other entities, and makes the API it documents real by exporting
`generateDefaultFieldUniversalIdentifier` from the SDK.
## Why
System fields are provisioned by the server, so they're never declared
with `defineField()` and have no importable `universalIdentifier`
constant. Since 2.19 their universal identifier is derived
deterministically from the application id, the object id and the field
name. Hardcoding an invented id fails sync with `INVALID_VIEW_DATA:
Field metadata not found` (this is exactly what broke the
twenty-partners `createdAt` view column).
The twenty-partners app already imports
`generateDefaultFieldUniversalIdentifier` from `twenty-sdk/define`, but
the function was never exported from the SDK. This PR adds the export
and documents the pattern.
## Changes
- **New page** `data/system-fields.mdx` — "Targeting System Fields":
- Lists the 8 system fields (`id`, `createdAt`, `updatedAt`,
`deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`).
- Explains the deterministic derivation and the sync error from
hardcoding ids.
- Documents `generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier, objectUniversalIdentifier, fieldName })`
with a full `defineView` example.
- Contrasts with standard objects (use
`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<object>.fields.<field>.universalIdentifier`)
and notes that `name` is a default, not system, field.
- **SDK export** — new `generate-default-field-universal-identifier.ts`
wrapping the existing `getFieldUniversalIdentifier` from
`twenty-shared/application` (`name` → `fieldName`), exported from
`define/index.ts`.
- Registered the page in `docs.json` (Data group) and cross-linked it
from the Views doc.
## Notes
`node_modules` isn't installed in this environment, so `nx typecheck`
wasn't run. The wrapper is a signature-matched pass-through and the
`twenty-shared/application` subpath + `getFieldUniversalIdentifier`
barrel export were both verified to exist.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017B7VivHcqZYjn3ukestY9U)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22856?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
@@ -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
|
||||
|
||||
<Note>
|
||||
`getFieldUniversalIdentifier` is available from `twenty-sdk` 2.21 onward.
|
||||
</Note>
|
||||
|
||||
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.
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
## 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.
|
||||
|
||||
<Note>
|
||||
`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()`.
|
||||
</Note>
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
},
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1
|
||||
"everyEquals",
|
||||
"favoriteRecordIds",
|
||||
"featureFlags",
|
||||
"getFieldUniversalIdentifier",
|
||||
"hasAnySoftDeleteFilterOnView",
|
||||
"includes",
|
||||
"includesEvery",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user