Date Field

Store a single date, a date range, or an all-day event on a record, with timezone-aware storage.


A date field stores a point in time or a span of time on a record — a deadline, an event window, a milestone. Each value has a startDate, an endDate, and an optional timezone. A single date is just a range whose start and end are equal. Dates are stored in UTC; the timezone is preserved separately so the value can be rendered correctly per viewer.

Date fields are CustomField objects with type: DATE. Records are Record objects in the API; a workspace is a Workspace.

Overview

CustomFieldTypeDATE
Set withsetRecordCustomFieldstartDate, endDate, timezone, granularity
Reads back asCustomField.value{ startDate, endDate, timezone }, plus CustomField.dateGranularity
StoresUTC timestamps + a timezone identifier + an ALL_DAY/TIMED granularity

Mark a date field as the record’s due date with isDueDate: true. Due-date fields can drive automations (DUE_DATE_CHANGED, DUE_DATE_REMOVED) and scheduled reminders.

Create

Create a date field with the createCustomField mutation. The field is scoped to the workspace in your blue-workspace-id header — there is no projectId argument.

mutation CreateDateField {
  createCustomField(input: { name: "Deadline", type: DATE }) {
    id
    name
    type
  }
}

Create a due-date field that automations can act on:

mutation CreateDueDateField {
  createCustomField(
    input: {
      name: "Contract Expiration"
      type: DATE
      isDueDate: true
      description: "When the contract expires and needs renewal"
    }
  ) {
    id
    name
    type
    isDueDate
  }
}

CreateCustomFieldInput

ParameterTypeRequiredDescription
nameString!YesDisplay name of the field.
typeCustomFieldType!YesMust be DATE.
isDueDateBooleanNoTreat this field as the record’s due date so it can drive due-date automations.
descriptionStringNoHelp text shown next to the field.

Set a value

Set a date with setRecordCustomField, supplying startDate, endDate, and an optional timezone. The mutation returns Boolean! — it does not return the updated value, so select no subfields. Read the value back with a follow-up query (see Read a value).

A single date has the same startDate and endDate:

mutation SetSingleDate {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      startDate: "2025-01-15T10:00:00Z"
      endDate: "2025-01-15T10:00:00Z"
      timezone: "America/New_York"
    }
  )
}

A date range spans two distinct timestamps:

mutation SetDateRange {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      startDate: "2025-01-01T09:00:00Z"
      endDate: "2025-01-31T17:00:00Z"
      timezone: "Europe/London"
    }
  )
}

An all-day event is a floating calendar date — the same day for every viewer, independent of timezone. Pass granularity: ALL_DAY explicitly (recommended):

mutation SetAllDayEvent {
  setRecordCustomField(
    input: {
      todoId: "todo_123"
      customFieldId: "field_123"
      startDate: "2025-01-15T00:00:00Z"
      endDate: "2025-01-15T00:00:00Z"
      granularity: ALL_DAY
    }
  )
}
Legacy heuristic still supported

If granularity is omitted, Blue still infers ALL_DAY from a value spanning 00:00 to 23:59 in the field’s timezone (the old detection method) — but an explicit granularity always takes precedence and is the more reliable way to set an all-day value going forward.

To clear a date, set both startDate and endDate to null.

SetRecordCustomFieldInput

ParameterTypeRequiredDescription
todoIdString!YesID of the record to update.
customFieldIdString!YesID of the date field.
startDateDateTimeNoStart of the range, in ISO 8601 (UTC stored).
endDateDateTimeNoEnd of the range, in ISO 8601. For a single date, set it equal to startDate.
timezoneStringNoIANA timezone identifier (e.g. America/New_York). Defaults to the calling user’s detected timezone if omitted.
granularityDateGranularityNoALL_DAY or TIMED. Omit to infer from startDate/endDate/timezone (see callout above).
endDate is not auto-filled

setRecordCustomField writes startDate and endDate exactly as given — supplying only startDate leaves endDate empty. Always send both (set them equal for a single date). The auto-fill behaviour described below applies only to the createRecord shorthand.

Response

{
  "data": {
    "setRecordCustomField": true
  }
}
FieldTypeDescription
setRecordCustomFieldBoolean!true when the value was written.

Set a value at record creation

When creating a record, pass date fields inline through customFields. Each entry takes a single value string. For a date field, value is startDate and endDate joined by a comma — "start,end". Supplying a single date (no comma) sets startDate to that date and endDate to the end of the same day.

mutation CreateRecordWithDate {
  createRecord(
    input: {
      title: "Project Milestone"
      todoListId: "list_123"
      customFields: [{ customFieldId: "field_123", value: "2025-02-15,2025-02-28" }]
    }
  ) {
    id
    title
  }
}

CreateRecordInputCustomField

ParameterTypeRequiredDescription
customFieldIdStringNoID of the date field.
valueStringNo"startDate,endDate". A single date with no comma sets startDate to that date and endDate to end-of-day.
value stringResult
"2025-01-15"Single date: start = 2025-01-15, end = end of that day.
"2025-01-15T10:00:00Z"Single timestamp.
"2025-01-01,2025-01-31"Range from start to end.

Read a value

A date value comes back on the CustomField.value field as { startDate, endDate, timezone }. startDate and endDate are ISO 8601 strings (or null); value itself is null until the field is read in a record context. There is no RecordCustomField wrapper — Record.customFields returns CustomField objects directly.

query GetRecordDates {
  todo(id: "todo_123") {
    id
    title
    customFields {
      id
      name
      type
      value
    }
  }
}
{
  "data": {
    "todo": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "title": "Project Milestone",
      "customFields": [
        {
          "id": "clm4n8qwx000108l0abcd1234",
          "name": "Deadline",
          "type": "DATE",
          "value": {
            "startDate": "2025-01-15T10:00:00.000Z",
            "endDate": "2025-01-15T10:00:00.000Z",
            "timezone": "America/New_York"
          }
        }
      ]
    }
  }
}

CustomField date fields

FieldTypeDescription
valueJSON{ startDate, endDate, timezone } for a DATE field in a record context; null otherwise.
startDateDateTimeStart of the range (also exposed directly on CustomField).
endDateDateTimeEnd of the range.
timezoneStringIANA timezone identifier stored with the value.
dateGranularityDateGranularityALL_DAY or TIMED — see Set a value.
isDueDateBooleanWhether this field is configured as the record’s due date.

Filter records by date

To filter records on a date, use the record query recordQueries { todos(filter: TodosFilter!) }. For a field marked isDueDate: true, the dedicated TodosFilter due-date fields are the simplest path.

query DueThisQuarter {
  recordQueries {
    todos(
      filter: {
        companyIds: ["company_123"]
        projectIds: ["project_123"]
        duedAtStart: "2025-01-01T00:00:00Z"
        duedAtEnd: "2025-03-31T23:59:59Z"
      }
    ) {
      items {
        id
        title
        duedAt
      }
    }
  }
}

Find records that have no due date set with the hasDueDate existence flag:

query MissingDueDate {
  recordQueries {
    todos(filter: { companyIds: ["company_123"], hasDueDate: false }) {
      items {
        id
        title
      }
    }
  }
}

Relevant TodosFilter fields: duedAt, duedAtStart, duedAtEnd, hasDueDate, plus createdStart / createdEnd for creation-date ranges. For non-due-date custom fields, filter through the fields JSON argument. See List records for the full filter surface.

Notes

  • Storage is UTC. Timestamps are normalised to UTC on write; the timezone is stored alongside so values render correctly per viewer.
  • All-day detection. Pass granularity: ALL_DAY explicitly to store a floating calendar date (recommended). If omitted, a value spanning 00:00 to 23:59 in its timezone is still inferred as all-day for backward compatibility.
  • No end > start validation. A range with endDate before startDate is accepted as-is; validate ordering in your client.
  • Time requires a date. You cannot store a time without a date — every value has at least a startDate.
  • createRecord end-of-day default. A single date passed to createRecord sets endDate to the end of that day. setRecordCustomField does not auto-fill; send both dates.

Errors

CodeWhen
CUSTOM_FIELD_VALUE_PARSE_ERRORThe supplied date string can’t be parsed as ISO 8601.
CUSTOM_FIELD_NOT_FOUNDNo date field matches customFieldId in the record’s workspace.
TODO_NOT_FOUNDNo record matches todoId.
BAD_USER_INPUTThe field does not belong to the record’s workspace, or another input constraint fails.
FORBIDDENYour project role lacks edit access to this field or record.

Permissions

  • Create or update the field: OWNER or ADMIN role on an active workspace.
  • Set a value: standard record edit access (VIEW_ONLY and COMMENT_ONLY roles are denied).
  • Read a value: standard record view access.