For the complete documentation index, see llms.txt. This page is also available as Markdown.

Defining and using custom fields

Define and use custom fields to attach domain-specific data to entities.

In Business Data Repository, assets and geo objects each come with a set of built-in fields such as title, workspace, and type. Custom fields let you store your own data in these entities, such as a VIN number, a fuel type, an inspection date, or an access level, without any changes to the platform schema.

How custom fields work

Custom fields can be user-defined or predefined. User-defined fields are ones you create, and each belongs to a single entity type: a cf_vin field defined on the "Vehicle" asset type appears on vehicles and nowhere else. Predefined fields are built into the platform for certain entity types. For example, geo objects have geojson_data, which you read through its own field, GeoObject.geojsonData.

Every custom field has a FieldType that determines what kind of data it stores and what validation options are available. You can add any number of fields with different types to a given entity type.

Field type reference

Field type

Use for

Key params

Value in set

STRING

Short text, codes, identifiers

isRequired, minLength, maxLength, defaultString, trim

{ string: "1HGBH41JXMN109186" }

TEXT

Long descriptions, notes

isRequired, maxLength, defaultText, trim

{ string: "Installed under dashboard" }

DECIMAL

Precise measurements, currency, weights

isRequired, minDecimal, maxDecimal, scale, defaultDecimal

{ decimal: "42.50" }

INTEGER

Whole-number quantities, counts

isRequired, minInteger, maxInteger, defaultInteger

{ integer: 42 }

BOOLEAN

Flags, yes/no attributes

isRequired, defaultBoolean

{ boolean: true }

DATE

Calendar dates

isRequired, defaultDate

{ date: "2025-06-01" }

DATETIME

Timestamps

isRequired, defaultDatetime

{ datetime: "2025-06-01T09:00:00Z" }

GEOJSON

Geometry data

isRequired, allowedTypes (GeoJsonGeometryType)

{ geojson: {"type":"Point","coordinates":[...]} }

OPTIONS

Predefined choices (single or multi)

isRequired, isMulti, options[], defaultOptions

{ option: "diesel" } or { options: ["diesel"] }

DEVICE

Links to device records. Definable on an AssetType only

isRequired, refSubtypeIds

{ device: { id: "...", isPrimary: true } }

REFERENCE

Links to any other entity, including catalog items and tags

isRequired, isMulti, refEntityTypeCode, refSubtypeIds, defaultRefIds

{ reference: { id: "...", isPrimary: false } } or { references: { ids: [...], isPrimary: false } }

There is no separate field type for schedules, catalog items, or tags. Use REFERENCE and fix the target with refEntityTypeCode, for example schedule, tag, or user_catalog_item. Devices are the exception: they keep their own DEVICE type. To find out which types a given owner accepts, and which entity types its REFERENCE fields may point at, query customFieldTypes.

Each field is defined by CustomFieldDefinition, a metadata record that specifies the field's code, display title, type, and validation rules. When you create or update an entity, you supply field values through the customFields field in the mutation input, and the API validates each value against the corresponding definition.

Writing custom field values

In any create or update mutation, customFields accepts a CustomFieldsPatchInput with two sub-fields:

Field
Type
Description

set

Typed field values to create or overwrite.

unset

[Code!]

List of field codes to remove entirely.

Each entry in set has three parts:

Field
Type
Description

code

The field code, including the cf_ prefix.

value

The typed value. Uses @oneOf, so provide exactly one option, the one matching the field's declared type. Set it to null to clear the value but keep the key, which is different from unset.

isDefault

Boolean

Overrides the definition's isDefault for this entity, in either direction. Omit to leave any existing override untouched.

This is the patch model: fields you don't mention are left unchanged. You can set and unset in the same mutation. For example, to update a license plate and remove an assigned driver in one call, add this code:

Omitting customFields altogether leaves all existing values untouched.

Example scenario: Enriching fleet records with metadata

A logistics company needs to store operational metadata on their vehicle assets: a VIN number for compliance, a fuel type for route planning, and a next service date for maintenance management. All examples in this guide use these three fields.

Adding this metadata requires the following steps:

1

Choose a field type

Before creating a definition, pick the fieldType that best matches your data. See the field type reference.

2

Create field definitions

Custom field definitions belong to the type whose entities will have the fields. In this scenario, that's an AssetType such as "Vehicle". There's no separate mutation for definitions: include them in the customFieldDefinitions list when you update the type itself with assetTypeUpdate. The whole mutation succeeds or fails as one change: either every definition in the list is created, updated, or deleted under one type version, or none of them are.

The version field is optional (see Optimistic locking) but recommended when modifying a shared catalog item that other users may be editing concurrently.

2.1 Check the existing definitions

Before adding new fields, check which fields the type already has. Fetch the type and include customFieldDefinitions in the query:

Response (if the fields already exist):

If no custom fields have been created yet, customFieldDefinitions is an empty array. The same pattern works for geoObjectTypes, the other owner that has custom fields.

2.2 Choose codes for your fields

Choose a code for each field before creating its definition. The code is what you use to read and write the field's values in every query and mutation. Once entities store values under a code, avoid changing it: a code can only be changed by deleting the definition and creating a new one, and the stored values are lost with it.

If you omit code, it's auto-generated from title (transliterated to lowercase Latin, spaces replaced with _, truncated at 30 characters, with a numeric suffix on collision). Explicitly setting a code gives you control over how that key appears in your data.

Codes can contain ASCII letters, digits, underscores, dots, and hyphens, and must start with a letter or digit (max 64 characters).

2.3 Create the field definitions

Add all three fields in a single mutation. Each entry in customFieldDefinitions names one operation, create in this case, because the input is @oneOf: exactly one of create, update, delete, archive, or restore per entry.

The response returns the updated type with incremented version and the full list of definitions:

The same pattern applies to geoObjectTypeUpdate, with just the parent type's input changing. Only create is allowed for a catalog item creation mutation such as assetTypeCreate. The full set of operations (update, delete, archive, restore) is available for assetTypeUpdate.

3

Set and update values

Pass customFields in the create mutation with the initial values under set. The following example creates a vehicle asset with all three fields populated:

The response returns the created asset's id, version, and customFields:

The same pattern applies to geoObjectCreate.

4

Update custom field values

Use set to overwrite specific fields and unset to remove them. Fields omitted from both are left unchanged.

The following mutation updates the next service date after a completed maintenance and removes a temporary inspection hold:

The response returns the updated asset's id, version, and customFields. Fields that were unset no longer appear in the list:

You can include version in update mutations to enable optimistic locking. Fetch the entity first if you don't have the latest version.

5

Read custom field values

customFields on any entity returns a list of typed values, one per field that has a value. Each element's type matches the field's declared fieldType: a STRING field comes back as a StringCustomFieldValue, a DATE field as a DateCustomFieldValue, and so on. All of them share the CustomFieldValue interface, which only guarantees code and isDefault. To read the actual values, use inline fragments, the ... on TypeName { } blocks in the query below. Each block says "if the element is this type, also return these fields" (see Inline fragments for interfaces). By default, all fields are returned. Run the following query:

Response:

StringCustomFieldValue and DateCustomFieldValue both have a field named value, but with different data types (String! and Date!). GraphQL rejects a query that selects both under the same name, so one of them needs a new name, which is what an alias does. That's why the query above uses stringValue: value and dateValue: value.

isDefault says whether the field is on by default for this entity. Its value is copied from the definition's isDefault, unless the entity has overridden it through CustomFieldValueInput.isDefault.

To retrieve only specific fields, use the codes argument. This keeps responses smaller when an entity type has many fields:

The response contains only the requested fields:

6

Filter entities by custom field value

The asset and geo object list queries support filtering by custom field values through CustomFieldFilter. Add one or more conditions to the customFields filter array. Multiple conditions are applied as AND.

For the full operator list and value formats by field type, see Filtering and sorting.

How to filter by an OPTIONS value

Find all electric vehicle assets:

Response:

How to filter by a DATE value

Find vehicles with a service date before a deadline:

Response:

How to combine multiple conditions

Find electric vehicle assets whose next service date has passed:

Response:

Omit value (or set it to null) when using the IS_NULL and IS_NOT_NULL operators.

Discovering available field types

Not every field type can be defined on every owner. DEVICE fields, for example, may only be defined on an AssetType, and a REFERENCE field can only point at the entity types the platform accepts for that owner. Rather than hardcoding those rules, ask the API with customFieldTypes:

Response:

referenceableEntityTypes lists the values accepted by ReferenceFieldParamsInput.refEntityTypeCode, and it's empty for every field type other than REFERENCE. Creating a definition whose fieldType is absent from this list fails with a validation error on input.fieldType.

Managing definitions

How to update a custom field definition

You can update the title, description, order, params, and isDefault. The code and fieldType cannot be changed after creation.

Add an update operation to the parent type's customFieldDefinitions array. The field is identified by its code:

The response returns the updated type with incremented version and the updated definition.

For OPTIONS fields, you can add new options or archive existing ones. Archiving an option (isArchived: true) hides it from new selections without affecting records that already carry it:

How to archive and restore a custom field definition

Archiving is the preferred method of deactivating a field you no longer need. An archived field preserves all existing values (which remain readable and visible in history and exports), but the field stops accepting new input and no longer appears in forms.

To archive a field, run this mutation:

To reactivate an archived field, use restore:

How to delete a custom field definition

Deletion is permanent and cannot be undone. Use archiving instead unless you explicitly need to remove the field and its data.

By default, deletion is rejected if any entity currently has a value for the field. Pass onValues: CASCADE to force deletion along with all associated values across every entity record:

The response shows that cf_vin no longer appears in the list, confirming that the definition and all its stored values have been removed.

onValues: CASCADE permanently removes the field's values from every entity record. They cannot be recovered. Use archive for non-destructive deactivation.

If you create a new definition with the same code later, existing records don't keep any value for it. The data removed by CASCADE is not recoverable.

Constraints and considerations

Keep in mind the following:

  • Validation errors reject the entire mutation: Mutations that include invalid custom field values are rejected in full. See Error handling for the error format.

  • fieldType** is immutable:** To change a field's type, delete its definition and create a new one. Deleting the definition removes its values from all entity records.

  • code** is stable once in use:** code must start with cf_ and be unique within the owner type and workspace. As this is the key used to read and write values across all entity mutations and queries, avoid recreating it under a different name if records contain values paired with it. If you rely on auto-generation, verify the generated code before any records are written under it.

  • params** takes exactly one params block:** FieldParamsInput is @oneOf, so provide exactly the block that matches your fieldType. Providing string: { ... } when fieldType is DECIMAL returns a validation error.

  • Multi-value fields and filtering: For OPTIONS and REFERENCE fields configured with isMulti: true, a filter matches if any value in the list satisfies the condition. For example, if an asset has cf_fuel_type set to ["diesel", "hybrid"], filtering with EQ: "diesel" matches it.

  • Predefined fields: The platform manages geojson_data, which is excluded from customFields responses and returned through GeoObject.geojsonData instead. Its code is reserved and can't be used for a user-defined field.

  • Owners are Customizable types only: custom fields are defined on an AssetType or a GeoObjectType, so assets and geo objects are the entities that have them. DEVICE fields are narrower still: they may only be defined on an AssetType.

  • isPrimary** is required on DEVICE and REFERENCE values:** stating it on every write prevents an update from silently demoting the entity's current primary value.

See also

Last updated

Was this helpful?