Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

144 lines
8.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 对象
description: 使用 defineObject 声明新的记录类型——具有其自身字段的自定义表。
icon: table
---
自定义**对象**是你的应用在工作区中添加的新记录类型——Post Card、Invoice、Subscription,或任何特定于你业务领域的内容。 每个对象都会声明其模式(字段、关系、默认值)以及一个在多次同步和部署中保持稳定的通用标识符。
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## 关键点
* `universalIdentifier` 必须在各次部署间保持唯一且稳定。
* 每个字段都需要 `name`、`type`、`label` 以及其自身稳定的 `universalIdentifier`。
* `fields` 数组是可选的——你可以定义没有自定义字段的对象。
* `openRecordIn` 设置在点击此对象的记录时,它们会在何处打开:`ObjectOpenRecordIn.USER_CHOICE`(默认,遵循每位工作区成员在 Settings → Experience 中的个人偏好)、`ObjectOpenRecordIn.SIDE_PANEL`,或 `ObjectOpenRecordIn.RECORD_PAGE`。 将其固定为 `RECORD_PAGE`,用于那些需要完整页面才能正常使用的记录(就像工作流和仪表盘那样),或者固定为 `SIDE_PANEL`,用于那些只适合作为快速面板显示的记录(就像日历事件那样)。
* `writability` 控制在角色权限生效之前,哪些主体可以对该对象的记录进行任何写入操作:`MetadataWritability.OPEN`(默认值 — 由工作区角色决定)、`MetadataWritability.APPLICATION`(只有你的应用自身的逻辑函数可以创建、更新或删除记录;将其用于类似配置的对象,这类对象的记录定义行为,因此具有广泛记录访问权限的工作区成员无法通过 API 编辑这些记录)、或者 `MetadataWritability.SYSTEM`(保留用于平台管理的数据)。 读取不受影响 — 这是在服务器端强制执行的,不同于 `isUIEditable`,后者只会隐藏 UI 的可操作入口。 它同样存在于每个字段上,但其限制只能比对象级别更严格。
* 此处定义的内联字段**不**需要 `objectUniversalIdentifier`——它会从父对象继承。 使用[`defineField()`](/l/zh/developers/extend/apps/data/extending-objects)为你不拥有的对象添加字段。
* 你可以使用 `yarn twenty dev:add object` 脚手架创建新对象,它会引导你完成命名、字段和关系。 参见 [Architecture → Scaffolding entities](/l/zh/developers/extend/apps/getting-started/scaffolding)。
<Note>
**基础字段会自动添加。** 当你定义自定义对象时,Twenty 会为你创建标准字段,例如 `id`、`name`、`createdAt`、`updatedAt`、`createdBy`、`updatedBy` 和 `deletedAt`。 你无需在 `fields` 数组中声明这些字段——只需声明你的自定义字段。 你可以通过声明一个同名字段来覆盖默认字段,但这么做通常并不是一个好主意。
</Note>
## 字段类型
从 `twenty-sdk/define` 导出的完整 `FieldType` 值集合:
| 类别 | 类型 |
| ------ | ----------------------------------------------------------------------------------------------------------- |
| 文本 | `TEXT`、`RICH_TEXT`、`ARRAY`(字符串数组)、`RAW_JSON` |
| 数值 | `NUMBER`(`universalSettings.dataType`:`'float'` / `'int'` / `'bigint'`)、`NUMERIC`(任意精度)、`RATING`、`POSITION` |
| 日期 | `DATE`, `DATE_TIME` |
| 选项 | `BOOLEAN`、`SELECT`、`MULTI_SELECT` |
| 复合 | `FULL_NAME`、`ADDRESS`、`EMAILS`、`PHONES`、`LINKS`、`CURRENCY`、`ACTOR`、`FILES` |
| 标识符和关联 | `UUID`、`RELATION`、`MORPH_RELATION`(参见 [Relations](/l/zh/developers/extend/apps/data/relations)) |
| 系统 | `TS_VECTOR`(全文搜索向量,由服务器管理) |
复合类型会存储多个子字段(例如,`FULL_NAME` = 名 + 姓;`CURRENCY` = `amountMicros` + `currencyCode`)。 `SELECT` 和 `MULTI_SELECT` 需要一个如上示例所示的 `options` 数组。
## 默认值
字面量字符串默认值必须在字符串**内部**用单引号包裹——应写成 `defaultValue: "'Draft'"`,而不是 `defaultValue: "Draft"`。 这就是上面的 `status` 字段使用 `` `'${PostCardStatus.DRAFT}'` `` 的原因。
未加引号的字符串保留用于计算得到的默认值,这些默认值会在创建记录时进行求值:
* `'uuid'` — 生成一个 UUID(用于 `UUID` 字段)
* `'now'` — 当前时间戳(用于 `DATE_TIME` 字段)
同样的约定也适用于复合默认值的字符串子字段(例如 `ACTOR` 字段上的 `{ source: "'MANUAL'" }`)以及 `SELECT`/`MULTI_SELECT` 值。 在构建应用时,未加引号的字面字符串默认值会触发警告。
## 可空性
`isNullable` 控制字段是否接受 `NULL`。 它默认是 `true` —— 对可选字段可以省略。 将 `isNullable` 设为 `false`,可在数据库层面将字段设为必填。
对 `isNullable` 的更改会在每次同步时应用,包括更新现有字段的同步——因此你可以通过编辑 manifest 并重新同步来切换字段的可空性。
<Note>
**将现有字段改为不可为空时需要提供默认值。** 当你将字段更改为 `isNullable: false` 时,还必须提供一个非空的 `defaultValue`。 在应用 `NOT NULL` 约束之前,默认值会为所有现有的 `NULL` 行进行回填;如果没有默认值,同步将失败,并出现错误:`Default value cannot be null for non-nullable fields`。 关系字段和 `TS_VECTOR` 字段始终可为空,因此 `isNullable` 对它们不起作用。
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## 接下来
* **将此对象与其他对象关联**——关于双向关系模式,参见 [Relations](/l/zh/developers/extend/apps/data/relations)。
* **为其他应用的对象添加字段**——关于 `defineField()`,参见 [Extending Objects](/l/zh/developers/extend/apps/data/extending-objects)。
* **在界面中显示此对象** — 请参阅 [导航菜单项](/l/zh/developers/extend/apps/layout/navigation-menu-items) 以添加侧边栏条目;请参阅 [视图](/l/zh/developers/extend/apps/layout/views) 以添加自定义列表配置。