Custom fields
A custom field is a typed extension you define on a core object (customer, subscription, product, invoice, plan, promotion, tax rule, aggregator). Unlike free-form metadata, custom fields are validated against a declared shape, are filterable on list endpoints, and carry visibility + write-access controls. Use them for domain attributes you query and report on; use metadata for opaque integration payloads.
Field definitions
Definitions live at the org level. One CustomFieldDefinition declares the data type, the entity types it can attach to, validation rules, UI hints, and access controls.
| Field | Notes |
|---|---|
key | Stable identifier matching ^[a-z][a-z0-9_]{1,63}$. Used in API requests and filter syntax. Immutable after create - the only way to "rename" is define a new field, migrate values, archive the old |
display_name | Human-readable label shown in dashboards |
description | Optional longer help text |
field_type | One of the 9 types. Immutable after create |
entity_types[] | Which entities the field can attach to. A single definition can target multiple entity types at once |
field_group, sort_order | UI grouping + ordering |
visibility | PUBLIC or INTERNAL - INTERNAL fields stay hidden from customer-facing surfaces |
validation | Per-type rule set (see Validation rules) |
default_value | Applied when the entity is created without an explicit value |
enum_options[] | Required for ENUM field type - array of {value, label} pairs |
entity_ref_config | Required for ENTITY_REF - {target_entity_type, display_field} |
write_access | ALL, ADMIN_ONLY, or SYSTEM_ONLY |
depends_on | Optional cross-field dependency: {field_key, condition, value, effect} |
auditable | When true, value changes write an audit row |
status | active → deprecated → archived lifecycle |
version | Bumps on every update for optimistic-concurrency |
Data types
field_type | Storage | Notes |
|---|---|---|
STRING | value_string | Optional regex via validation.regex_pattern |
NUMBER | value_number (float64) | Optional min_value / max_value |
BOOLEAN | value_boolean | Real bool, not a string |
DATE | value_date | ISO 8601 date |
DATETIME | value_datetime | ISO 8601 datetime (UTC) |
ENUM | value_string | Restricted to enum_options[].value |
URL | value_string | Format-validated URL |
EMAIL | value_string | Format-validated email |
ENTITY_REF | value_string | UUID of another Kontorion entity; entity_ref_config.target_entity_type declares the target |
Validation rules
validation accepts these constraints (per type - applied where they make sense):
| Rule | Applies to |
|---|---|
required | Any type |
unique_per_org | Any type |
min_length, max_length | STRING, URL, EMAIL |
min_value, max_value | NUMBER |
regex_pattern, regex_message | STRING |
required enforces presence on the entity create - missing required keys block the call.
Defining a field
Code
Updates are scoped - key and field_type are immutable; display_name, visibility, validation, enum_options, default_value, etc. are editable. Pass expected_version for optimistic-concurrency safety.
Setting values
Field values flow through the entity's standard create/update endpoints under the custom_fields map (key → value):
Code
Validation runs against the matching definition. Unknown keys, type mismatches, and out-of-range values fail with field-level validation errors. Validation errors carry custom_fields.<key> as the field path so a UI can highlight the right input.
Querying
Custom field values are filterable on list endpoints via the custom_fields.<key> query parameter. Operators are appended with __:
| Filter | Meaning |
|---|---|
?custom_fields.industry=fintech | Equality (default) |
?custom_fields.amount__gte=100 | Greater than or equal |
?custom_fields.amount__lte=500 | Less than or equal |
?custom_fields.label__contains=acme | Substring match (string types) |
?custom_fields.country__in=DE,FR,NL | Any of (comma-separated) |
Code
The operator suffix mirrors the pagination filter operators but uses the __ separator (instead of .) so the filter parser can disambiguate field name from operator.
Lifecycle
Definitions move through three states with explicit transitions:
| State | Meaning | How to enter |
|---|---|---|
active | Editable, visible, applied to entity create/update | Default on create |
deprecated | Hidden from new entity-create UIs, existing values preserved, still readable + filterable | POST /v1/custom-fields/{id}/deprecate |
archived | Soft-deleted - hidden everywhere; existing values preserved for audit | DELETE /v1/custom-fields/{id} |
For irreversible cleanup of an archived definition + every value that referenced it:
Code
purge hard-deletes both the definition and every custom_field_values row that pointed at it. Irreversible - use only after you're certain no audit need remains.
Visibility & write access
| Setting | Effect |
|---|---|
visibility: PUBLIC | Returned on customer-facing reads (default) |
visibility: INTERNAL | Hidden from customer-facing surfaces; visible to operator tooling only |
write_access: ALL | Any caller with entity write access can set |
write_access: ADMIN_ONLY | Restricted to admin role |
write_access: SYSTEM_ONLY | Set by Kontorion internals only - API writes are rejected |
Endpoints
All Custom Field endpoints - GET /v1/custom-fields, GET /v1/custom-fields/{id}, POST /v1/custom-fields, PUT /v1/custom-fields/{id}, POST /v1/custom-fields/{id}/deprecate, DELETE /v1/custom-fields/{id} (archive), POST /v1/custom-fields/{id}/purge.
Related
- Pagination, filtering & sorting - the filter operator family
custom_fields.<key>__<op>extends - Errors -
field: custom_fields.<key>shape on per-value validation failures - Customers, Subscriptions, Products, Plans, Invoices, Promotions, Taxes - the eight entity types definitions can attach to (plus aggregators)