--- title: Objects description: Declare new record types — custom tables with their own fields — using defineObject. icon: "table" --- Custom **objects** are new record types your app adds to a workspace — Post Card, Invoice, Subscription, anything specific to your domain. Each object declares its schema (fields, relations, default values) and a stable universal identifier that survives across syncs and deploys. ```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, }, ], }); ``` ## Key points - The `universalIdentifier` must be unique and stable across deployments. - Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`. - The `fields` array is optional — you can define objects without custom fields. - Inline fields defined here do **not** need an `objectUniversalIdentifier` — it's inherited from the parent object. Use [`defineField()`](/developers/extend/apps/data/extending-objects) to add fields to objects you don't own. - You can scaffold new objects with `yarn twenty dev:add object`, which guides you through naming, fields, and relationships. See [Architecture → Scaffolding entities](/developers/extend/apps/getting-started/scaffolding). **Base fields are added automatically.** When you define a custom object, Twenty creates standard fields like `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`, and `deletedAt` for you. You don't need to declare them in your `fields` array — only your custom fields. You can override a default field by declaring one with the same name, but this is rarely a good idea. ## Field types The full set of `FieldType` values, exported from `twenty-sdk/define`: | Category | Types | |----------|-------| | Text | `TEXT`, `RICH_TEXT`, `ARRAY` (of strings), `RAW_JSON` | | Numeric | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (arbitrary precision), `RATING`, `POSITION` | | Dates | `DATE`, `DATE_TIME` | | Choice | `BOOLEAN`, `SELECT`, `MULTI_SELECT` | | Composite | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` | | Identifiers & relations | `UUID`, `RELATION`, `MORPH_RELATION` (see [Relations](/developers/extend/apps/data/relations)) | | System | `TS_VECTOR` (full-text search vector, managed by the server) | Composite types store multiple sub-fields (e.g. `FULL_NAME` = first + last name; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` and `MULTI_SELECT` require an `options` array as in the example above. ## Default values Literal string defaults must be wrapped in single quotes **inside** the string — `defaultValue: "'Draft'"`, not `defaultValue: "Draft"`. That's why the `status` field above uses `` `'${PostCardStatus.DRAFT}'` ``. Unquoted strings are reserved for computed defaults, evaluated when a record is created: - `'uuid'` — generates a UUID (for `UUID` fields) - `'now'` — the current timestamp (for `DATE_TIME` fields) The same convention applies to string sub-fields of composite defaults (e.g. `{ source: "'MANUAL'" }` on an `ACTOR` field) and to `SELECT`/`MULTI_SELECT` values. A literal string default left unquoted raises a warning when your app is built. ## Nullability `isNullable` controls whether a field accepts `NULL`. It defaults to `true` — omit it for optional fields. Set `isNullable: false` to make a field required at the database level. Changes to `isNullable` are applied on every sync, including syncs that update an existing field — so you can flip a field's nullability by editing the manifest and re-syncing. **Making an existing field non-nullable requires a default value.** When you change a field to `isNullable: false`, you must also provide a non-null `defaultValue`. The default backfills any existing `NULL` rows before the `NOT NULL` constraint is applied; without it the sync fails with `Default value cannot be null for non-nullable fields`. Relation fields and `TS_VECTOR` fields are always nullable, so `isNullable` has no effect on them. ```ts { universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000', name: 'reference', type: FieldType.TEXT, label: 'Reference', isNullable: false, defaultValue: "'N/A'", } ``` ## What's next - **Connect this object to others** — see [Relations](/developers/extend/apps/data/relations) for the bidirectional relation pattern. - **Add fields to objects from other apps** — see [Extending Objects](/developers/extend/apps/data/extending-objects) for `defineField()`. - **Display this object in the UI** — see [Views](/developers/extend/apps/layout/views) and [Navigation Menu Items](/developers/extend/apps/layout/navigation-menu-items) to put it in the sidebar.