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
  }
}

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.
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
  }
}

Bare dates are the clearest way to say “all day”

A startedAt/duedAt sent as a bare calendar date — YYYY-MM-DD, no time, no offset — is unambiguously an all-day value. It needs no granularity and no timezone, and it round-trips to the same calendar day for every viewer:

mutation {
  editRecord(input: { todoId: "todo_123", duedAt: "2026-07-10" }) {
    id
    duedAt
    dueDateGranularity
  }
}

Stores 2026-07-10T00:00:00.000Z with dueDateGranularity: ALL_DAY.

Prefer this over a midnight timestamp. "2026-07-10T00:00:00Z" reaches the same result today, but only by inference — and that inference is what makes the warning below necessary. A bare date states the intent outright.

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.
rankStringOpaque manual-order key 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, reorder it with previousId/nextId, and set a due-date range in one call.

mutation UpdateRecordFull {
  editRecord(
    input: {
      todoId: "todo_123"
      todoListId: "list_123"
      previousId: "todo_456"
      nextId: "todo_789"
      color: "#91cfff"
      startedAt: "2026-06-01T09:00:00Z"
      duedAt: "2026-06-05T17:00:00Z"
      tags: [{ id: "tag_123" }, { title: "urgent", color: "#ff4b4b" }]
    }
  ) {
    id
    rank
    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.

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.