List Records

Query, filter, sort, and paginate records across your organization with the todos query.


Use the recordQueries.todos query to read records from Blue with filtering, sorting, and pagination. Records are Record objects in the API; in the app they appear as the cards inside a workspace’s lists. A single call can span one organization, several workspaces, or a precise slice defined by assignees, tags, dates, and custom-field conditions.

This is the only operation that filters records server-side. (In-app search filters client-side after records load, so it can’t reach records beyond what the client has fetched.)

Request

The query lives under the recordQueries namespace. The only required input is companyIds — one or more organization IDs or slugs.

query ListRecords {
  recordQueries {
    todos(filter: { companyIds: ["company_123"] }) {
      items {
        id
        title
        done
        duedAt
      }
      pageInfo {
        totalItems
        hasNextPage
      }
    }
  }
}

The companyIds, projectIds, and todoListIds arrays each accept IDs or slugs.

Parameters

Arguments

ArgumentTypeRequiredDescription
filterTodosFilter!YesFilter criteria (below). companyIds is the only required field.
sort[TodosSort!]NoOrdering. Defaults to [] (the engine’s natural order). Multiple keys apply in sequence.
limitIntNoItems per page. Omit to get 20. Any value over 500 — and any value <= 0 — is coerced to 500 (the max).
skipIntNoItems to skip for offset pagination. Defaults to 0. Cannot be combined with filter.rankBefore.
afterStringNoRemoved. Any value returns a BAD_USER_INPUT error — see Cursor pagination.

TodosFilter

The flat fields below combine with AND by default. For mixed AND/OR logic and the full set of conditions (record-name match, completion-date range, last-updated-by actor), use groups instead — see Group filtering.

FieldTypeDescription
companyIds[String!]!Required. Organization IDs or slugs to query.
projectIds[String!]Restrict to these workspace IDs or slugs.
todoIds[String!]Return only these specific records.
assigneeIds[String!]Records assigned to any of these users.
unassignedBooleanWhen true, only records with no assignees.
tagIds[String!]Records carrying any of these tags.
tagColors[String!]Filter by tag color.
tagTitles[String!]Filter by tag title.
todoListIds[String!]Records in these lists.
todoListTitles[String!]Records in lists with these titles.
doneBooleanFilter by completion state. Prefer showCompleted for the common “hide completed” case.
showCompletedBooleanWhen false, completed records are excluded.
startedAtDateTimeRecords with this exact start date.
duedAtDateTimeRecords with this exact due date.
dueStartDateTimeDue date on or after this (inclusive).
dueEndDateTimeDue date on or before this (inclusive).
duedAtStartDateTimeAlias of dueStart.
duedAtEndDateTimeAlias of dueEnd.
createdStartDateTimeCreated on or after this.
createdEndDateTimeCreated on or before this.
updatedAt_gtDateTimeUpdated strictly after this.
updatedAt_gteDateTimeUpdated on or after this.
searchStringMatch title and text content (case-insensitive).
qStringAlias of search.
excludeArchivedProjectsBooleanExclude records in archived workspaces.
archivedBooleanArchive scope. Omitted/false → active records (archived excluded); true → only archived records (needs the view-archived permission). See Archive scope.
recordNameStringRecord-title substring search (CONTAINS by default, override with recordNameOp).
coordinatesJSONPolygon coordinates for map-view geo filtering.
notAssigneeIds[String!]Exclude records assigned to any of these users.
notTagIds[String!]Exclude records carrying any of these tags.
notColors[String!]Exclude records with these tag colors.
notTodoListIds[String!]Exclude records in these lists.
hasTagBooleanRecords that have (or, when false, lack) any tag.
hasColorBooleanRecords that have (or lack) a tag color.
hasDueDateBooleanRecords that have (or lack) a due date.
hasDescriptionBooleanRecords that have (or lack) description text.
hasChecklistBooleanRecords that have (or lack) a checklist.
hasDependencyBooleanRecords that have (or lack) a dependency.
hasReferenceBooleanRecords that have (or lack) a reference.
fieldsJSONCustom-field conditions. See Custom-field filtering.
opFilterLogicalOperatorLogical operator applied across fields entries. Defaults to AND.
groups[TodoFilterGroupInput!]Nested filter groups. When provided, every flat field above is ignored in favor of the groups. See Group filtering.
groupLinks[FilterLogicalOperator!]Operators linking adjacent groups; length should be groups.length - 1. A shorter list repeats its last operator.

TodosSort

Each value is a field_DIRECTION enum member. Pass an array to sort by multiple keys in order.

ValueSorts by
rank_ASC / rank_DESCManual (drag) order within the list. rank_DESC is the default list order — highest rank first, matching the board top to bottom.
title_ASC / title_DESCRecord title, alphabetically.
createdAt_ASC / createdAt_DESCCreation date.
updatedAt_ASC / updatedAt_DESCLast-updated date.
completedAt_ASC / completedAt_DESCCompletion date.
startedAt_ASC / startedAt_DESCStart date.
duedAt_ASC / duedAt_DESCDue date.
createdBy_ASC / createdBy_DESCCreator.
assignees_ASC / assignees_DESCAssignees.
todoTags_ASC / todoTags_DESCTags.
todoListTitle_ASC / todoListTitle_DESCList title.
projectName_ASC / projectName_DESCWorkspace name.
checklistTitle_ASC / checklistTitle_DESCChecklist title.
checklistItemTitle_ASC / checklistItemTitle_DESCChecklist-item title.
todoCustomFieldDate_ASC / todoCustomFieldDate_DESCA date custom field.
todoCustomFieldSelectSingle_ASC / todoCustomFieldSelectSingle_DESCA single-select custom field.
todoCustomFieldSelectMulti_ASC / todoCustomFieldSelectMulti_DESCA multi-select custom field.
todoCustomFieldAssignee_ASC / todoCustomFieldAssignee_DESCAn assignee custom field.
todoCustomFieldRollup_ASC / todoCustomFieldRollup_DESCA rollup custom field.

Rank replaces position for manual order

rank is the manual-order key for records. The retired position_ASC and position_DESC values are no longer part of TodosSort.

Treat each rank as an opaque string. Compare ranks only between records in the same list. Do not parse a rank, generate one, or compare ranks from different lists. To place or move a record, send previousId and/or nextId; Blue allocates the new rank.

A rank sort must be the only sort and the filter must resolve to exactly one list. Use rank_DESC for the board’s top-to-bottom order. For queries that span lists, choose another sort field because each list has its own rank space.

FilterLogicalOperator

ValueMeaning
ANDAll conditions must match.
ORAny condition may match.

Response

{
  "data": {
    "recordQueries": {
      "todos": {
        "items": [
          {
            "id": "clm4n8qwx000008l0g4oxdqn7",
            "title": "Draft Q3 launch plan",
            "done": false,
            "duedAt": "2026-07-15T17:00:00.000Z"
          },
          {
            "id": "clm4n8qwx000108l0haz1f2pe",
            "title": "Review pricing page copy",
            "done": true,
            "duedAt": null
          }
        ],
        "pageInfo": {
          "totalItems": 2,
          "hasNextPage": false
        }
      }
    }
  }
}

TodosResult

FieldTypeDescription
items[Record!]!The records on this page.
pageInfoPageInfo!Pagination metadata.

PageInfo

FieldTypeDescription
totalItemsIntTotal records matching the filter, across all pages.
totalPagesIntTotal pages at the current limit.
pageIntCurrent page, derived from skip and limit.
perPageIntPage size in effect (the resolved limit).
hasNextPageBoolean!Whether a next page exists.
hasPreviousPageBoolean!Whether a previous page exists.
nextCursorStringDeprecated. Always null on this query — the keyset cursor it fed was retired with position ordering.
startCursorStringDeprecated. See nextCursor.
endCursorStringDeprecated. See nextCursor.

Record

The most useful fields on each returned record. The Record type exposes more — these are the ones you’ll select most often.

FieldTypeDescription
idID!Unique identifier.
uidString!Short, human-facing identifier.
rankStringOpaque manual-order key within its list. Compare two records’ ranks as strings; do not parse or generate one.
titleString!Record title.
textString!Description as plain text.
htmlString!Description as HTML.
startedAtDateTimeStart date.
duedAtDateTimeDue date.
timezoneStringTimezone for the record’s dates.
colorStringColor label.
coverStringCover image URL.
coverLockedBooleanWhether the cover is locked from auto-updates.
doneBoolean!Completion state. There is no status enum — completion is this boolean.
completedAtDateTimeWhen the record was last marked complete.
archivedBoolean!Whether the record is archived.
createdAtDateTime!Creation timestamp.
updatedAtDateTime!Last-update timestamp.
createdByUserThe user who created the record. Select fullName/email, not name.
lastUpdatedActorTodoActorPolymorphic actor (user, automation, API key, system, or form) that last updated the record. Nullable for older rows.
commentCountInt!Number of comments.
checklistCountInt!Total checklist items.
checklistCompletedCountInt!Completed checklist items.
isRepeatingBoolean!Whether the record recurs.
repeatingJSONRecurrence configuration.
reminderJSONReminder configuration.
isReadBooleanRead state for the current user.
isSeenBooleanSeen state for the current user.
todoListRecordList!The list this record belongs to.
todoLists[RecordList!]All lists this record appears in (for cross-list records).
repeatingTodoListRecordListThe list a recurring record was generated into.
users[User!]!Assigned users. Select fullName/email, not name.
tags[Tag!]!Tags. Select title/color (a Tag has no name).
checklists[Checklist!]!Checklists on the record.
customFields[CustomField!]!Custom-field values. Select value fields on each element (name, type, value, text, number, checked, selectedOption { title }, selectedOptions { title }).
dependOn[Record!]Records this one depends on (blocking).
dependBy[Record!]Records that depend on this one.
timeTrackingTimeTrackingTime-tracking summary.

Full example

Filter to open, high-priority records in two workspaces, due in 2026, sorted by due date then list title, with a rich selection set.

query ListRecordsAdvanced {
  recordQueries {
    todos(
      filter: {
        companyIds: ["company_123"]
        projectIds: ["project_123", "project_456"]
        assigneeIds: ["user_123"]
        tagIds: ["tag_123"]
        showCompleted: false
        dueStart: "2026-01-01T00:00:00Z"
        dueEnd: "2026-12-31T23:59:59Z"
        search: "launch"
        excludeArchivedProjects: true
        fields: [
          {
            type: "CUSTOM_FIELD"
            customFieldId: "field_123"
            customFieldType: "SELECT_SINGLE"
            op: "IN"
            values: ["option_123", "option_456"]
          }
        ]
        op: AND
      }
      sort: [duedAt_ASC, todoListTitle_ASC]
      limit: 50
      skip: 0
    ) {
      items {
        id
        uid
        title
        text
        duedAt
        done
        completedAt
        todoList {
          id
          title
        }
        users {
          id
          fullName
          email
        }
        tags {
          id
          title
          color
        }
        customFields {
          id
          name
          type
          value
          selectedOption {
            title
          }
        }
        createdBy {
          id
          fullName
        }
      }
      pageInfo {
        totalPages
        totalItems
        page
        perPage
        hasNextPage
        hasPreviousPage
      }
    }
  }
}

Archive scope

Archiving takes a record out of its list without deleting it (see Archive a record). The archived filter selects which side of that partition you read:

archived valueReturns
omittedActive records only — archived records are excluded. Default.
falseActive records only — identical to omitting it.
trueArchived records only. Active records are excluded.

There is no value that returns active and archived records together in a single call — the filter is a partition, not an inclusion toggle, so each query reads one side or the other. To assemble a complete picture, run the query twice (once with archived: false or omitted, once with archived: true) and merge the two result sets client-side.

query ArchivedRecords {
  recordQueries {
    todos(filter: { companyIds: ["company_123"], projectIds: ["project_123"], archived: true }) {
      items {
        id
        title
        archived
        archivedAt
      }
      pageInfo {
        totalItems
      }
    }
  }
}

List all records in a workspace

To read every record in one workspace, filter by that workspace and page through the results. companyIds is always required; add the workspace to projectIds and omit todoListIds so the query spans every list:

query AllWorkspaceRecords {
  recordQueries {
    todos(
      filter: { companyIds: ["company_123"], projectIds: ["project_123"] }
      limit: 500
      skip: 0
    ) {
      items {
        id
        title
        todoList {
          id
          title
        }
      }
      pageInfo {
        totalItems
        hasNextPage
      }
    }
  }
}

A few things to keep in mind:

  • Paginate to completion. A page is capped at 500 (the limit ceiling). Increment skip by your page size until pageInfo.hasNextPage is false; pageInfo.totalItems gives you the full count up front so you can size the loop.
  • This returns active records only. Archived records are excluded by default — see Archive scope. To include them, run the same query a second time with archived: true and combine the pages.
  • One query covers the whole workspace. There’s no per-list call to make — a single query with no todoListIds returns records from every list in the workspace.
  • Span workspaces or the whole org. projectIds accepts several IDs to cover multiple workspaces at once; drop projectIds entirely to read across every workspace in the organizations named in companyIds.
  • Visibility still applies. Records the caller can’t see — restricted lists, assigned-only roles, tag-based rules — are filtered out silently rather than erroring.

Cursor pagination

Offset pagination (skip) is simple, but it gets more expensive the deeper you page — reaching row 100,000 still means the database walks past the 99,999 rows before it. For large single-list scans, filter.rankBefore provides a keyset cursor that seeks directly to the page boundary through an index, so page 100 costs the same as page 1.

rankBefore is opt-in and deliberately narrow. It is honored only when all of the following hold; violating any one returns a BAD_USER_INPUT error rather than silently falling back to offset mode:

  • filter.todoListIds names exactly one list.
  • sort is exactly one of [rank_ASC] or [rank_DESC].
  • rankBefore is not combined with skip.

To start a chain, run the first page with sort and todoListIds set and no rankBefore. Select rankPage { status generation } alongside items. Take the last item’s rank and id, plus that generation, and pass all three back as rankBefore on the next request. Keep going until a page returns fewer items than limit.

generation guards against a rank rebalance landing mid-scan. If the list’s ranks are rewritten between pages, the server returns an empty page with rankPage.status: RESET_REQUIRED and the new generation, instead of silently skipping or repeating records. Restart the chain from page 1 when you see that.

query RankPage {
  recordQueries {
    todos(
      filter: {
        companyIds: ["company_123"]
        todoListIds: ["list_123"]
        rankBefore: { rank: "a0V", id: "record_456", generation: 3 }
      }
      sort: [rank_DESC]
      limit: 100
    ) {
      items {
        id
        title
        rank
      }
      rankPage {
        status
        generation
      }
    }
  }
}

Custom-field filtering

TodosFilter.fields is typed JSON. Each entry must include a type discriminator — entries without one are silently dropped. The query layer routes on type:

typeFilters by
CUSTOM_FIELDA custom field’s value.
FIELDA built-in record field.
TIME_IN_LISTHow long a record has sat in its current list.
RELATIVE_DUEDATEA due date relative to now (e.g. “overdue”, “due this week”).

A CUSTOM_FIELD entry has this shape:

{
  "type": "CUSTOM_FIELD",
  "customFieldId": "field_123",
  "customFieldType": "SELECT_SINGLE",
  "op": "IN",
  "values": ["option_123", "option_456"]
}
KeyTypeDescription
typeStringMust be "CUSTOM_FIELD".
customFieldIdStringThe custom field’s ID.
customFieldTypeStringOne of the CustomFieldType enum values (below). Determines how the value is matched.
opStringA FilterComparisonOperator: IS, NOT, EQ, NE, IN, NIN, GT, GTE, LT, LTE, CONTAINS, NOT_CONTAINS. Which operators apply depends on the field type.
valuesdepends on typeOption IDs for selects, a number for numeric types, a boolean for CHECKBOX, a string for text types.

Filterable custom-field types

customFieldType must be a CustomFieldType enum value. These types can be filtered:

TEXT_SINGLE, TEXT_MULTI, URL, EMAIL, PHONE, UNIQUE_ID (text matching), NUMBER, CURRENCY, PERCENT, RATING, FORMULA (numeric comparison), CHECKBOX (IS true / IS false), SELECT_SINGLE, SELECT_MULTI (option matching), COUNTRY, DATE, ASSIGNEE, REFERENCE, LOOKUP, and ROLLUP.

The remaining CustomFieldType values are not filterable: LOCATION, FILE, TIME_DURATION, BUTTON, CURRENCY_CONVERSION, and REFERENCED_BY.

Group filtering

For mixed AND/OR logic, supply groups. When groups is present, the flat TodosFilter fields are ignored. Each group is a TodoFilterGroupInput with its own internal operator (op), and adjacent groups are linked by groupLinks.

query ListRecordsByGroups {
  recordQueries {
    todos(
      filter: {
        companyIds: ["company_123"]
        groups: [
          { op: AND, tagIds: ["tag_123"], showCompleted: false }
          { op: OR, recordName: "launch", recordNameOp: CONTAINS }
        ]
        groupLinks: [AND]
      }
      limit: 50
    ) {
      items {
        id
        title
      }
      pageInfo {
        totalItems
      }
    }
  }
}

TodoFilterGroupInput accepts the same assignee/tag/list/date/existence fields as the flat filter, plus group-only conditions:

FieldTypeDescription
opFilterLogicalOperatorHow conditions combine within this group.
completedStartDateTimeCompleted on or after this (latest mark-complete timestamp on currently-done records).
completedEndDateTimeCompleted on or before this.
recordNameStringMatch a substring anywhere in the record title.
recordNameOpFilterComparisonOperatorOperator for recordName (e.g. CONTAINS, EQ).
lastUpdatedByUserIds[String!]Records last updated by any of these users.
lastUpdatedByAutomationIds[String!]Records last updated by any of these automations.
lastUpdatedByActorTypes[ActorType!]Records whose last-update actor is one of these types: USER, AUTOMATION, API_KEY, SYSTEM, FORM.

The three lastUpdatedBy* axes compose with OR — a record matches if it satisfies any supplied axis. fields works exactly as in Custom-field filtering.

Errors

This query degrades gracefully: an inaccessible workspace or a non-existent record ID inside an organization you can access simply contributes no rows, rather than raising an error. Errors are reserved for authentication and authorization.

CodeWhen
UNAUTHENTICATEDThe request carries no valid credentials.
FORBIDDENThe token is valid but is not a member of the requested organization, or the organization is inactive.

Permissions

The caller must be authenticated and a member of every organization in companyIds; the organization must be active. Beyond that, visibility is scoped per user:

  • Records in workspaces the user can’t access are excluded.
  • Users restricted to assigned-only records see only their own.
  • Hidden lists (per role configuration) are excluded automatically.
  • Tag-based access rules further narrow results.

This filtering is applied silently — restricted records are absent from items, not flagged.