Database Records
Read and write records in any database, including field values, files, colors, deletion, and restoration.
A workspace can contain several databases. A record can belong to more than one database, and it does not need a list. Use explicit databaseId values when your integration works across databases.
These GraphQL operations are available to integrations with the required workspace, database, and field permissions. The sync* mutations support durable client writes and require their documented protocol versions, sequences, and replay handling. For a plain field update, setRecordFieldValue needs none of that sync metadata.
Find databases, records, and fields
Use GraphQL to discover databases, records, and visible fields:
query ListDatabases($workspaceId: ID!) {
recordDatabases(workspaceId: $workspaceId) { id name }
}query ListDatabaseRecords($databaseId: ID!, $after: String) {
recordDatabaseRows(input: { databaseId: $databaseId, first: 50, after: $after }) {
rows {
record { id createdAt updatedAt }
databaseId
fields { id name type value editable }
}
nextCursor
hasMore
}
}Pass nextCursor as after for the next page. This GraphQL input uses after; the REST wrapper calls the same parameter cursor.
query ListDatabaseFields($workspaceId: ID!, $databaseId: ID!) {
recordDatabaseFields(workspaceId: $workspaceId, databaseId: $databaseId, forView: true) {
id
name
type
options { id title color }
}
}forView: true returns caller-visible fields and option choices. Without it, the schema-editor projection requires field-management access and may omit options. Use returned field IDs and option IDs in writes.
The same discovery operations are also available through REST database records. You do not need to switch to REST to list databases, records, or fields.
Read one record
query ReadDatabaseRecord($id: String!, $databaseId: ID!) {
record(id: $id, databaseId: $databaseId) {
id
title
customFields { id name value }
list { id title }
}
}The selected database determines the title and fields. Field permissions still apply. list can be null for a listless record.
todo(id, databaseId) is the legacy alias. Without a database ID, reads preserve the workspace’s migrated database home when the record belongs to it; otherwise they use its sole live home in that workspace. Multiple native homes require an explicit database ID. An invalid explicit database never falls back to another home.
You can also use list queries filtered by record IDs. You do not need to use a list query solely to read one record.
Write field values
setRecordFieldValue is the public online GraphQL writer. It returns true on success and a GraphQL error if the write cannot be applied. It requires no protocol version, mutation ID, or sequence number.
mutation WriteDatabaseField($input: SetRecordFieldValueInput!) {
setRecordFieldValue(input: $input)
}Variables for a text field:
{
"input": {
"workspaceId": "workspace-id",
"databaseId": "database-id",
"recordId": "record-id",
"fieldId": "field-id",
"kind": "text",
"value": { "text": "Updated value" }
}
}Use lowercase write kinds, not the uppercase field type names:
| Field type | kind | value example |
|---|---|---|
| TEXT_SINGLE | text | {"text":"Example"} |
| TEXT_MULTI | text | {"html":"<p>Example</p>"} |
| SELECT_SINGLE | select | ["option-id"] |
| SELECT_MULTI | select | ["first-option-id","second-option-id"] |
| REFERENCE | reference | ["target-record-id"] |
| FILE | file | ["file-id"] |
| ASSIGNEE | assignee | ["user-id"] |
| CHECKBOX | checkbox | true |
| NUMBER | number | {"number":42} |
| DATE | date | {"start":"2026-10-15","end":null,"timezone":"UTC","granularity":"ALL_DAY"} |
Array values replace the whole set. Send [] to clear a selection, reference, assignee set, or file set. Select IDs must belong to the field; references and files must satisfy the field’s access and scope rules. FILE values use File.id, not File.uid.
The existing setRecordCustomField / setTodoCustomField mutations also accept databaseId for supported field values. Their type-specific input parameters remain valid.
Attach a file without replacing other attachments
Complete the upload flow first, then attach the returned file UID:
mutation AttachDatabaseFile {
createRecordCustomFieldFile(input: {
todoId: "record-id"
databaseId: "database-id"
customFieldId: "file-field-id"
fileUid: "uploaded-file-uid"
})
}This adds one attachment. Its legacy alias is createTodoCustomFieldFile. Unlike a whole-set kind: "file" write, this operation takes the file UID and preserves other attachments.
Create, edit, and copy
Use createRecordInDatabase for explicit database creation:
mutation CreateDatabaseRecord {
createRecordInDatabase(input: {
workspaceId: "workspace-id"
databaseId: "database-id"
title: "New record"
}) { recordId databaseId created }
}Optional groupOptionId selects a live option on a mounted single-select field. fieldValues accepts {fieldId, kind, value} objects. createRecord / createTodo retain their workspace/list creation contract.
For title and color edits, pass databaseId to editRecord:
mutation SetDatabaseRecordColor {
editRecord(input: {
todoId: "record-id"
databaseId: "database-id"
color: "#ff4b4b"
}) { id color }
}Colors require a valid hex string and follow the database’s color field and permissions. Titles follow its display field; a name formula can prevent direct title edits. copyRecord accepts sourceDatabaseId to select the source home and retains its destination-list contract.
Legacy assignee, date, tag, and bulk operations require the database’s corresponding legacy field bindings. An ordinary field of a similar type is not automatically that built-in field. For native database fields, use their field IDs with setRecordFieldValue. Separate field calls are not an atomic replacement for a bulk mutation.
Delete and restore
mutation DeleteDatabaseRecord {
deleteRecord(input: { todoId: "record-id", databaseId: "database-id" }) {
success
}
}mutation RestoreDatabaseRecord {
restoreRecord(id: "record-id", databaseId: "database-id") { id }
}Deletion moves the record to Trash; it does not permanently purge it. Restore requires Trash permissions, with a short undo exception for the original deleter while they still have delete permission. A record deleted with its list must be restored through that list. Archive is separate from deletion.
TABLE fields
Pass the same databaseId when reading todoTableRows, reading todoCustomFieldVersion, and saving with setTodoTableRows. Read the current version before saving; a stale version is refused to prevent overwriting another edit. TABLE row writes use these dedicated operations, not setRecordFieldValue.