Files
twenty/packages/twenty-docs/developers/extend/apps/data/objects.mdx
T
Paul Rastoin ebe816abb5 Remove outdated index view pitfall from app agent docs (#23667)
## Context

Index views are now auto-generated by a metadata side effect when an
object is created, so the "Common Pitfalls" entry warning against
creating an object without an associated index view is obsolete.

## What changed

Removed the line "Creating an object without an index view associated.
Unless this is a technical object, user will need to visualize it."
from:

- the `create-twenty-app` template
(`packages/create-twenty-app/src/constants/template/AGENTS.md`), used
for newly scaffolded apps
- the 14 existing copies across `packages/twenty-apps` (`AGENT.md` /
`AGENTS.md` / `CLAUDE.md` / `LLMS.md` in `examples`, `public`, and
`internal` apps)

The other pitfalls (navigationMenuItem, front-component scroll) are
unchanged since they still apply.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01YYzo4dY3V7QXdDZtvk5FwY)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23667?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. -->
2026-08-01 15:04:30 +00:00

143 lines
7.0 KiB
Plaintext

---
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.
- `openRecordIn` sets where records of this object open when clicked: `ObjectOpenRecordIn.USER_CHOICE` (the default, following each workspace member's own preference from Settings → Experience), `ObjectOpenRecordIn.SIDE_PANEL`, or `ObjectOpenRecordIn.RECORD_PAGE`. Pin it to `RECORD_PAGE` for records that need a full page to be usable, the way workflows and dashboards do, or to `SIDE_PANEL` for records that only make sense as a quick panel, the way calendar events do.
- 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).
<Note>
**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.
</Note>
## 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.
<Note>
**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.
</Note>
```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 [Navigation Menu Items](/developers/extend/apps/layout/navigation-menu-items) to add a sidebar entry; see [Views](/developers/extend/apps/layout/views) to add custom list configurations.