Custom Fields

Extend records with typed custom fields - 26 field types, field-level access control, and the operations to create, read, and write them.


Custom fields add typed, structured data to your records beyond the built-in title, dates, and assignees. A custom field is a CustomField object scoped to a single workspace (a Workspace in the API); once created, it appears on every record (Record) in that workspace, and each record holds its own value.

This section covers the operations to manage field definitions and write values, the full catalog of field types, and the access model that governs who can see and edit each field.

POST https://api.blue.app/graphql
blue-token-id: YOUR_TOKEN_ID
blue-token-secret: YOUR_TOKEN_SECRET
blue-org-id: YOUR_ORG_ID
blue-workspace-id: project_123

The X-Bloo-Project-ID header (also accepted as X-Bloo-Workspace-ID) scopes custom field reads and writes to one workspace. Company and project headers accept either an ID or a slug. Header names are case-insensitive.

Operations

OperationGraphQLDescription
List custom fieldscustomFields queryRetrieve a paginated list of field definitions, filtered by project and type.
Create a custom fieldcreateCustomField mutationAdd a typed field to a workspace.
Edit a custom fieldeditCustomField mutationChange a field’s name, configuration, or access rules. Takes EditCustomFieldInput (a customFieldId plus the fields to change) and returns the updated CustomField.
Set field valuessetRecordCustomField mutationWrite a value to a field on one record.
Delete a custom fielddeleteCustomField mutationRemove a field and all of its values.

Field types

Every custom field has a type drawn from the CustomFieldType enum. The type fixes how values are stored, validated, and read back. Each type below links to its own reference page with create and set-value examples.

Text

TypeStoresPage
TEXT_SINGLESingle-line textSingle-line text
TEXT_MULTIMulti-line textMulti-line text
EMAILEmail addressEmail
URLWeb link, optionally shown as a buttonURL
PHONEPhone number with country codePhone

Selection

TypeStoresPage
SELECT_SINGLEOne option from a defined listSingle-select
SELECT_MULTIMultiple options from a defined listMulti-select
CHECKBOXA booleanCheckbox

Numeric

TypeStoresPage
NUMBERA numberNumber
CURRENCYA monetary amount in a fixed currencyCurrency
CURRENCY_CONVERSIONAn amount converted between currenciesCurrency conversion
PERCENTA percentagePercent
RATINGA star rating on a custom scaleRating
FORMULAA value computed from other fieldsFormula

Date and time

TypeStoresPage
DATEA date or date rangeDate
TIME_DURATIONAn elapsed-time measurementTime duration

Location

TypeStoresPage
LOCATIONA geographic point (latitude/longitude)Location
COUNTRYOne or more country codesCountry

People

TypeStoresPage
ASSIGNEEOne or more users assigned via the fieldAssignee

Relationships

TypeStoresPage
REFERENCELinks to records in another workspaceReference
REFERENCED_BYRecords in another workspace that reference this oneReferenced by
LOOKUPA value pulled from a referenced recordLookup
ROLLUPAn aggregate over referenced or referencing recordsRollup

Files, identifiers, and actions

TypeStoresPage
FILEFile attachmentsFile
UNIQUE_IDAn auto-generated identifierUnique ID
BUTTONAn action trigger rendered as a buttonButton
Computed types are read-only

FORMULA, LOOKUP, ROLLUP, and REFERENCED_BY fields are computed by Blue. You read their values like any other field, but setRecordCustomField rejects them with a BAD_USER_INPUT error — they have no value to set directly.

Reading field values

A record exposes its fields through Record.customFields, which returns [CustomField!]! — a flat list of CustomField objects, each already carrying the value for that record. Select the value field that matches the type directly on the array element; there is no wrapper object.

Records are read through recordQueries. The companyIds filter field is required by the schema but may be an empty array — pass [] to fall back to the company in the X-Bloo-Company-ID header.

query RecordWithCustomFields {
  recordQueries {
    todos(filter: { companyIds: [], projectIds: ["project_123"] }, limit: 5) {
      items {
        id
        title
        customFields {
          id
          name
          type
          text # TEXT_SINGLE, TEXT_MULTI, EMAIL, URL, PHONE
          number # NUMBER, CURRENCY, PERCENT, RATING
          checked # CHECKBOX
          selectedOption {
            title
          } # SELECT_SINGLE
          selectedOptions {
            title
          } # SELECT_MULTI
          startDate # DATE (single date or range start)
          endDate # DATE range end
          value # JSON fallback for any type
        }
      }
    }
  }
}
{
  "data": {
    "recordQueries": {
      "todos": {
        "items": [
          {
            "id": "clm4n8qwx000008l0g4oxdqn7",
            "title": "Acme onboarding",
            "customFields": [
              {
                "id": "clm4paopt000108l0d2b7f1zr",
                "name": "Priority",
                "type": "SELECT_SINGLE",
                "text": null,
                "number": null,
                "checked": null,
                "selectedOption": { "title": "High" },
                "selectedOptions": null,
                "startDate": null,
                "endDate": null,
                "value": "High"
              },
              {
                "id": "clm4pb12x000208l0h9c3e8ws",
                "name": "Contract value",
                "type": "CURRENCY",
                "text": null,
                "number": 24000,
                "checked": null,
                "selectedOption": null,
                "selectedOptions": null,
                "startDate": null,
                "endDate": null,
                "value": 24000
              }
            ]
          }
        ]
      }
    }
  }
}

The value field is a JSON scalar that returns the field’s value in a type-appropriate shape — useful when you don’t want to branch on type. For per-type details, see each field type’s page.

Writing field values

Use setRecordCustomField to write a value to one field on one record. It is an upsert and returns Boolean! — it has no selection set, so do not query subfields on the result. Read the value back with the Record.customFields query shown above.

mutation SetPriority {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      customFieldOptionId: "option_123" # SELECT_SINGLE
    }
  )
}
{ "data": { "setRecordCustomField": true } }

The input parameter you send depends on the field type — text, number, checked, customFieldOptionId/customFieldOptionIds, startDate/endDate, countryCodes, latitude/longitude, assigneeUserIds, or customFieldReferenceTodoIds. See Set field values for the full mapping.

To seed values when a record is first created, pass customFields to createRecord. Each entry is a CreateRecordInputCustomField of { customFieldId, value }, where value is a string the API parses according to the field’s type.

mutation CreateRecordWithFields {
  createRecord(
    input: {
      todoListId: "list_123"
      title: "New deal"
      customFields: [
        { customFieldId: "field_123", value: "option_123" }
        { customFieldId: "field_456", value: "24000" }
      ]
    }
  ) {
    id
    title
    customFields {
      name
      type
      value
    }
  }
}

Access control

Visibility and edit rights for a field follow the caller’s role in the workspace, refined by optional per-field access rules.

Workspace roles

A user’s access level in a workspace is one of the UserAccessLevel values:

Access levelCan see field valuesCan set field valuesCan create/edit/delete fields
OWNERYesYesYes
ADMINYesYesYes
MEMBERYesYesNo
COMMENT_ONLYYesNoNo
VIEW_ONLYYesNoNo
CLIENTSubject to field rulesSubject to field rulesNo

Field-level rules

When creating or editing a field, you can restrict it to specific users, custom roles, or access levels:

Input fieldTypeDescription
allowedUserIds[String!]Restrict the field to these users.
allowedRoleIds[String!]Restrict the field to these custom project roles.
allowedLevels[UserAccessLevel!]Restrict the field to these access levels.

These rules drive the per-caller booleans on CustomField: viewable (the caller may read this field) and editable (the caller may set values on it). workspaceUserRole returns the custom role resolved for the caller. A field with no rules is governed entirely by the workspace role table above.

Errors

These are the error codes the custom field operations return.

CodeWhen
CUSTOM_FIELD_NOT_FOUNDThe customFieldId does not exist or is not in the header’s workspace.
PROJECT_NOT_FOUNDA referenced project (for REFERENCE/REFERENCED_BY/ROLLUP) does not exist.
BAD_USER_INPUTThe value is malformed, or the target field is a computed type that cannot be set directly.
CUSTOM_FIELD_VALUE_PARSE_ERRORA value passed to createRecord’s customFields cannot be parsed for the field’s type.
FORBIDDENThe caller’s role or the field’s access rules forbid the read or write.
CUSTOM_FIELD_LIMITThe workspace has reached its custom field limit (upgrade to add more).
PRO_REQUIREDThe field type requires a Pro subscription (for example, ROLLUP).
FIELD_REFERENCED_BY_FORMULAThe field cannot be deleted because the workspace’s name formula uses it.
metadata is deprecated

CustomField.metadata (and the matching metadata input on create/edit) is @deprecated and no longer used. Do not write configuration into it; use the typed configuration fields documented on each field type’s page.