Update Record

Edit a record's title, description, dates, color, cover, and tags with editRecord, and write custom field values with setRecordCustomField.


Use the editRecord mutation to change a record’s built-in fields — title, description, due dates, color, cover, and tags — and use the setRecordCustomField mutation to write a value to one custom field on a record. Records are Record objects in the API and custom fields are CustomField objects.

editRecord returns the updated Record. setRecordCustomField returns Boolean! — it does not return the field value, so read it back with List Records when you need to confirm the result.

Request

Update a record’s title and description. todoId is the only required field; include only the fields you want to change.

mutation UpdateRecord {
  editRecord(
    input: {
      todoId: "todo_123"
      title: "Finalize Q3 proposal"
      html: "<p>Send to legal by Friday.</p>"
      text: "Send to legal by Friday."
    }
  ) {
    id
    title
    text
    html
  }
}
Set the workspace header

editRecord resolves the record against the workspace in the X-Bloo-Project-ID header. A record can live in more than one workspace; the header determines which one the move/position change applies to.

Parameters

EditRecordInput

ParameterTypeRequiredDescription
todoIdString!YesID of the record to update.
titleStringNoNew record title. Rejected if the workspace has a name formula enabled (see Errors).
htmlStringNoNew description as sanitized HTML. The matching text is derived automatically — you do not need to send both.
textStringNoNew description as plain text.
startedAtDateTimeNoStart of the record’s due-date range, ISO 8601.
duedAtDateTimeNoEnd of the record’s due-date range (the due date), ISO 8601.
timezoneStringNoIANA timezone used to interpret startedAt/duedAt (e.g. America/New_York). Only affects inference when granularity is omitted.
granularityDateGranularityNoALL_DAY or TIMED. See All-day vs. timed dates. Omit to infer from the values sent.
colorStringNoRecord color as a hex code (see Color options).
coverStringNoCover image URL for the record.
positionFloatNoNew sort position within its list. Applies only in the record’s original workspace.
todoListIdStringNoMove the record to this list. To move across workspaces, prefer Move Record to List.
tags[CreateRecordTagInput!]NoReplaces the record’s entire tag set with these tags. Pass [] to clear all tags. See Tags for an add/remove-style alternative.

All-day vs. timed dates

A due date is either a floating all-day date — the same calendar day for every viewer, RFC 5545 style — or a timed instant rendered per viewer using timezone. granularity picks which one you get:

ValueMeaning
ALL_DAYA calendar date with no time component. Stored and read back as the same day for everyone.
TIMEDA precise instant, rendered in each viewer’s timezone.
mutation SetAllDayDueDate {
  editRecord(input: { todoId: "todo_123", duedAt: "2026-07-10T00:00:00Z", granularity: ALL_DAY }) {
    id
    duedAt
    dueDateGranularity
  }
}
Omitting granularity

If you don’t pass granularity, Blue infers it: a duedAt/startedAt sent as an instant at exactly midnight in the given timezone (or UTC, if no timezone) is treated as ALL_DAY; anything else is TIMED. A caller that sends a midnight-UTC instant meaning a precise deadline (e.g. duedAt: "2026-06-29T00:00:00Z") is now read back as ALL_DAY unless granularity: TIMED is passed explicitly. Existing integrations that don’t write midnight-UTC timestamps are unaffected.

CreateRecordTagInput

Each entry either references an existing tag by id, or creates/matches one by title (and optional color).

ParameterTypeRequiredDescription
idStringNoID of an existing tag in the same workspace.
titleStringNoTag text. If no tag with this title (and color) exists, a new one is created.
colorStringNoHex color for a newly created tag. Defaults to #4a9fff.

Color options

color accepts any of the record colors used in the app:

// Light theme
["#ffc2d4", "#ed8285", "#ffb55e", "#ffe885", "#ccf07d",
 "#91e38c", "#a1f7fa", "#91cfff", "#c29ee0", "#e8bd91"]

// Dark theme
["#ff8ebe", "#ff4b4b", "#ff9e4b", "#ffdc6b", "#b4e051",
 "#66d37e", "#4fd2ff", "#4a9fff", "#a17ee8", "#e89e64"]

Response

{
  "data": {
    "editRecord": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "title": "Finalize Q3 proposal",
      "text": "Send to legal by Friday.",
      "html": "<p>Send to legal by Friday.</p>"
    }
  }
}

Returns

editRecord returns the updated Record. Commonly selected fields:

FieldTypeDescription
idID!Record ID.
titleString!Record title.
textString!Description as plain text.
htmlString!Description as HTML.
positionFloat!Sort position within the list.
colorStringRecord color hex code.
coverStringCover image URL.
startedAtDateTimeStart of the due-date range.
duedAtDateTimeDue date (end of the range).
dueDateGranularityDateGranularityALL_DAY or TIMED — see All-day vs. timed dates.
doneBoolean!Whether the record is complete.
tags[Tag!]!The record’s tags (each has id, title, color).

Full example

Replace the tag set, move the record to a new list, set a position, and set a due-date range in one call.

mutation UpdateRecordFull {
  editRecord(
    input: {
      todoId: "todo_123"
      todoListId: "list_123"
      position: 65535
      color: "#91cfff"
      startedAt: "2026-06-01T09:00:00Z"
      duedAt: "2026-06-05T17:00:00Z"
      tags: [{ id: "tag_123" }, { title: "urgent", color: "#ff4b4b" }]
    }
  ) {
    id
    position
    color
    startedAt
    duedAt
    tags {
      id
      title
      color
    }
  }
}

Update custom fields

Use the setRecordCustomField mutation to write one custom field value on a record. Send the field-specific parameter for the field’s CustomFieldType — passing the wrong parameter is silently ignored, so match the table below. The mutation returns Boolean!.

mutation SetCustomFieldValue {
  setRecordCustomField(input: { todoId: "todo_123", customFieldId: "field_123", text: "Acme Corp" })
}
{ "data": { "setRecordCustomField": true } }

SetRecordCustomFieldInput

ParameterTypeRequiredDescription
todoIdString!YesThe record to write to.
customFieldIdString!YesThe custom field to set.
textStringNoText value (TEXT_SINGLE, TEXT_MULTI, URL, EMAIL, PHONE).
numberFloatNoNumeric value (NUMBER, PERCENT, RATING, CURRENCY).
currencyStringNoThree-letter currency code for a CURRENCY field. Falls back to the field’s default when omitted.
checkedBooleanNoCheckbox state (CHECKBOX).
regionCodeStringNoISO 3166-1 region code paired with text for a PHONE field.
countryCodes[String!]NoISO 3166-1 alpha-2 codes for a COUNTRY field.
latitudeFloatNoLatitude for a LOCATION field (with longitude and text).
longitudeFloatNoLongitude for a LOCATION field.
startDateDateTimeNoStart of a DATE field’s range, ISO 8601.
endDateDateTimeNoEnd of a DATE field’s range (the date itself for a single date), ISO 8601.
timezoneStringNoIANA timezone for a DATE field (e.g. America/New_York).
granularityDateGranularityNoALL_DAY or TIMED for a DATE field. Omit to infer from startDate/endDate (see Date Field).
customFieldOptionIdStringNoSelected option ID for a SELECT_SINGLE field.
customFieldOptionIds[String!]NoSelected option IDs for a SELECT_MULTI field.
customFieldReferenceTodoIds[String!]NoLinked record IDs for a REFERENCE field. Replaces the field’s link set.
assigneeUserIds[String!]NoUser IDs for an ASSIGNEE field. Replaces the field’s assignees.
Computed fields can't be set

FORMULA, LOOKUP, ROLLUP, REFERENCED_BY, UNIQUE_ID, and CURRENCY_CONVERSION fields are derived from other data. Calling setRecordCustomField on them returns a BAD_USER_INPUT error.

Examples by field type

Text, URL, email (TEXT_SINGLE, TEXT_MULTI, URL, EMAIL)

mutation {
  setRecordCustomField(
    input: { todoId: "todo_123", customFieldId: "field_123", text: "hello@acme.com" }
  )
}

Number, percent, rating (NUMBER, PERCENT, RATING) — number is a Float, unquoted.

mutation {
  setRecordCustomField(input: { todoId: "todo_123", customFieldId: "field_123", number: 42 })
}

Currency (CURRENCY) — amount plus a currency code. See Currency Field.

mutation {
  setRecordCustomField(
    input: { todoId: "todo_123", customFieldId: "field_123", number: 1500.50, currency: "USD" }
  )
}

Checkbox (CHECKBOX)

mutation {
  setRecordCustomField(input: { todoId: "todo_123", customFieldId: "field_123", checked: true })
}

Single-select (SELECT_SINGLE) — one option ID.

mutation {
  setRecordCustomField(
    input: { todoId: "todo_123", customFieldId: "field_123", customFieldOptionId: "option_123" }
  )
}

Multi-select (SELECT_MULTI) — the full set of selected option IDs.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      customFieldOptionIds: ["option_123", "option_456"]
    }
  )
}

Date (DATE) — endDate is the date; add startDate for a range, timezone for timezone-aware storage, and granularity: ALL_DAY for a floating all-day date. See Date Field.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      startDate: "2026-06-01T00:00:00Z"
      endDate: "2026-06-05T00:00:00Z"
      timezone: "America/New_York"
    }
  )
}

Phone (PHONE) — E.164 number in text with the matching regionCode.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      text: "+33642526644"
      regionCode: "FR"
    }
  )
}

Country (COUNTRY) — ISO 3166-1 alpha-2 codes.

mutation {
  setRecordCustomField(
    input: { todoId: "todo_123", customFieldId: "field_123", countryCodes: ["AF", "AL", "DZ"] }
  )
}

Location (LOCATION) — coordinates plus a display text.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      latitude: 42.2923323
      longitude: 12.1266212
      text: "Capranica, Italy"
    }
  )
}

Reference (REFERENCE) — the records to link. Replaces the existing link set. See Reference Field.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      customFieldReferenceTodoIds: ["todo_456", "todo_789"]
    }
  )
}

Assignee (ASSIGNEE) — the users assigned through the field. Replaces the existing assignees. See Assignee Field.

mutation {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      assigneeUserIds: ["user_123", "user_456"]
    }
  )
}

Errors

editRecord:

CodeWhen
FORBIDDENCaller has VIEW_ONLY or COMMENT_ONLY access, or lacks permission to edit the field being changed.
TODO_NOT_FOUNDNo record with todoId is accessible to the caller.
TODO_LIST_NOT_FOUNDtodoListId does not resolve to a list the caller can access.
TITLE_EDIT_NOT_ALLOWEDtitle was set on a record in a workspace that has a name formula enabled.

setRecordCustomField:

CodeWhen
FORBIDDENCaller has VIEW_ONLY/COMMENT_ONLY access, or the field is not editable for the caller’s role.
CUSTOM_FIELD_NOT_FOUNDNo custom field with customFieldId is accessible to the caller.
TODO_NOT_FOUNDNo record with todoId is accessible to the caller.
BAD_USER_INPUTThe field is computed and cannot be set; the field does not belong to the record’s workspace; an option ID is unknown; or an assigneeUserIds user is ineligible.

Permissions

Access levelCan update records
OWNERYes
ADMINYes
MEMBERYes
CLIENTYes
COMMENT_ONLYNo
VIEW_ONLYNo

setRecordCustomField additionally honors per-field role permissions: if a custom field is marked non-editable for the caller’s role, the call is rejected with FORBIDDEN.