---
title: Custom objects
description: "Adding an object and its fields at runtime: what the module gates, which keys are refused, the limits, and how a relation makes a related list appear."
---

The `custom_objects` module lets a workspace add its own objects and fields alongside the fourteen standard ones. Everything else about them is the same: the same field kinds, the same record endpoints, the same permission checks.

**The module gates the schema, not the data.** Creating an object or a field needs it. Reading and writing records never does, in a custom object as much as a standard one, so a client that only moves data works whether the module is on or off.

## Creating an object

[`POST /objects`](/reference/crm/post-api-v1-objects) takes three things:

| Field | Shape |
| --- | --- |
| `key` | `^[a-z][a-z0-9_]{0,62}$` — lower case, starts with a letter, up to 63 characters. This is what paths and filters use |
| `label` | 1 to 120 characters. The plural name a list shows |
| `singularLabel` | 1 to 120 characters. The name one record shows |

Two kinds of key are refused with `object_key_reserved`: the key of a standard object, and any key that collides with a name the platform uses internally. Pick something the product owns rather than something generic.

## Changing an object

[`PATCH /objects/{objectId}`](/reference/crm/patch-api-v1-objects-by-object-id) accepts `key`, `label`, `singularLabel` and the `revision` it was read at.

**An object's key can change. A field's cannot.** Renaming an object's key works, and every path and saved filter that named the old one stops matching, so it is not a free change. A field is the opposite: its update endpoint accepts `key`, `type` and `targetObjectId` but refuses to change any of them — `field_key_immutable`, `field_type_immutable`, `field_target_immutable`. Only `label` and `required` actually move.

Turning `required` on is refused while any record is missing that value, and a standard field's `required` cannot be changed at all (`field_system_locked`). A virtual kind cannot be required, because nothing writes it.

## Fields

Fields are added with [`POST /objects/{objectId}/fields`](/reference/crm/post-api-v1-objects-by-object-id-fields), in the same nineteen kinds the standard objects use.

**An object holds at most 256 fields.** The 257th is refused with `object_field_limit`.

Ten keys are taken by the columns every object table carries, and a field may not use any of them:

`id` · `name` · `organization_id` · `object_id` · `revision` · `created_at` · `updated_at` · `archived_at` · `created_by` · `updated_by`

Using one answers `field_key_reserved`; reusing a key the object already has answers `field_key_taken`.

## Relations make related lists

A `relation` field names the object it points at. The reverse direction is read from the other side: [`GET /objects/{objectId}/related`](/reference/crm/get-api-v1-objects-by-object-id-related) answers the objects whose relation fields point at this one, which is what a related list is built from.

So a custom object becomes visible in a standard object's context by pointing at it, not by being registered anywhere. Add a relation from your object to `account`, and accounts gain a related list of your records.

## What does not change

A custom object's records use the same endpoints as a standard object's — `/objects/{objectId}/records` and the rest — and pass the same four [permission](/guides/concepts/permissions) checks. There is no separate scope for custom data: `records.read` and `records.write` cover every object the workspace has.
