Custom fields
Custom fields extend selected pillar-owned records without replacing their native data model. A tenant declares a versioned schema for an entity type, and values are stored in that entity’s custom_fields JSON object.
Supported field types
The current declaration and write validators support ten types:
| Type | Value shape | Main declaration rules |
|---|---|---|
text | String | Optional max_length |
rich_text | String | Optional max_length |
number | JSON number or numeric string | Optional min and max |
money | JSON number or decimal string | Optional min and max |
date | YYYY-MM-DD or RFC 3339 string | No additional rule |
boolean | Boolean | Boolean defaults only |
picklist | One string | Non-empty validation.options |
multi_select | Array of strings | Non-empty validation.options |
reference | Non-empty record ID string | validation.entity_type is required |
formula | Computed value | validation.cel_expression is required |
Formula fields cannot be required, cannot have literal defaults, and cannot be written directly. Field names must be lower-case snake case, start with a letter, be at most 63 characters, and avoid reserved native column names.
Declaration shape
The schema write surface accepts the complete proposed field list:
POST /api/entity-schemaAuthorization: Bearer <access-token>Content-Type: application/json{ "entity_type": "crm_contact", "fields": [ { "name": "referred_by", "type": "picklist", "required": false, "validation": { "options": ["Instagram", "Event", "Referral"] }, "indexed": true }, { "name": "lifetime_visits", "type": "number", "validation": { "min": 0 } } ]}The response reports change as none, additive, or breaking, plus the prior and active schema versions. The endpoint is tenant-scoped and authenticated through Portal authorization.
Only text, number, money, date, boolean, and picklist may set indexed: true. A schema may promote at most eight fields. Promotion creates typed indexing support; it is separate from the base JSON storage.
Schema changes
Additive changes activate in place. Examples include adding an optional field, widening a picklist, loosening a constraint, or changing an index flag.
Breaking changes create a new active version and preserve the prior version. Examples include removing a field, changing its type, narrowing options, making a field required, or changing a formula or reference target. Formula dependencies are checked before activation.
After a breaking change, run the idempotent migration endpoint until has_more is false:
POST /api/entity-schema/backfillAuthorization: Bearer <access-token>Content-Type: application/json
{ "entity_type": "crm_contact", "max_chunks": 10}The response reports scanned, retagged, coerced, and voided values. Values that cannot be migrated are voided from the active record rather than silently reinterpreted; the backfill audit preserves their originals.
Writing values
For agent workflows, use inblack_update_record. The tool validates the patch against the active schema and merges it into the selected record. Unknown fields, type mismatches, invalid options, and writes to formulas are rejected.
Create-time validation requires every declared required field. A partial update may omit a required field because the existing value remains, but an explicitly empty required value fails validation.
There is no current MCP tool for declaring schemas. inblack_update_record changes record values, not field definitions.
Reading and filtering
inblack_query_records returns custom values with the record and supports CEL filters such as:
custom_fields.referred_by == "Instagram"The current translator reads a custom value with PostgreSQL ->>, which produces text. Equality filters are supported, but numeric-looking values are not cast for numeric ordering. Do not use custom_fields.lifetime_visits > 10 as a numeric comparison. Also do not assume a JSON GIN index accelerates ->> predicates; use an explicitly promoted field when query performance matters.
Entity boundary
Custom fields are available only on substrate-extensible entities. Accounting primitives such as journal entries, journal lines, balances, and periods are closed because their native invariants must remain explicit.
See The dimensional substrate for the current entity registry and Custom fields: overview for the operator-facing distinction between substrate and Commerce product-group definitions.