Relational Data
Read a To-One Relation
Fetch an article with its author by requesting fields from the related collection. Use dot notation in REST or nested arrays in the SDK.
const article = await client.Articles.readOne({
key: 1,
fields: ['id', 'title', { author: ['name', 'email'] }],
});
curl -g "https://example.monospace.io/api/blog/items/articles/1?fields=id,title,author.name,author.email" \
-H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({
fields: 'id,title,author.name,author.email',
});
const response = await fetch(
`https://example.monospace.io/api/blog/items/articles/1?${params}`,
{
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
},
);
const { data } = await response.json();
// data.author.name — "Alice"
Read a To-Many Relation
Fetch an author with all their articles. The related items are returned as an array.
const author = await client.Authors.readOne({
key: 1,
fields: ['id', 'name', { articles: ['title', 'status'] }],
});
curl -g "https://example.monospace.io/api/blog/items/authors/1?fields=id,name,articles.title,articles.status" \
-H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({
fields: 'id,name,articles.title,articles.status',
});
const response = await fetch(
`https://example.monospace.io/api/blog/items/authors/1?${params}`,
{
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
},
);
const { data } = await response.json();
// data.articles — { data: [{ title: "...", status: "published" }, ...] }
Filter, Sort, and Paginate Nested Relations
Apply filters, sorting, and pagination to related items. In REST, use deep with underscore-prefixed keys. In the SDK, use expanded field selection syntax — there is no top-level deep parameter.
const author = await client.Authors.readOne({
key: 1,
fields: [
'id',
'name',
{
articles: {
fields: ['title', 'status'],
filter: { status: { _eq: 'published' } },
sort: [{ created_at: { direction: 'desc' } }],
limit: 5,
},
},
],
});
curl -g "https://example.monospace.io/api/blog/items/authors/1?fields=id,name,articles.title,articles.status&deep[articles][_filter][status][_eq]=published&deep[articles][_sort][0][created_at][direction]=desc&deep[articles][_limit]=5" \
-H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({
'fields': 'id,name,articles.title,articles.status',
'deep[articles][_filter][status][_eq]': 'published',
'deep[articles][_sort][0][created_at][direction]': 'desc',
'deep[articles][_limit]': '5',
});
const response = await fetch(
`https://example.monospace.io/api/blog/items/authors/1?${params}`,
{
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
},
);
const { data } = await response.json();
_filter, _sort, _limit, _offset. The SDK extracts these automatically from the expanded field selection syntax.For more on field selection patterns, see Field Selection.
Nested Relational Operations
When creating or updating items, you can modify related items in the same request using nested relational operations. This avoids multiple round-trips and ensures all changes happen atomically within a single transaction.
There are five operations available:
| Operation | Description | Available In |
|---|---|---|
_connect | Link an existing item by its primary key | Create, Update |
_create | Create a new related item inline | Create, Update |
_disconnect | Unlink a related item without deleting it | Update |
_update | Modify a related item's fields in place | Update |
_delete | Permanently remove a related item | Update |
In create context, only _connect and _create are available — you cannot disconnect, update, or delete relations that don't exist yet. In update context, all five operations are available and can be combined in a single request.
Operations are expressed as entries in an array on the relation field. They execute in the order they appear in the array, giving you precise control over the sequence of changes.
Connect (Link Existing Items)
Use _connect to link an existing item by its primary key. Available in both create and update contexts.
To-One
To-one _connect uses key (singular object). In create context, the relation field is a singular object. In update context, it is wrapped in an array.
// Create context — singular object
const article = await client.Articles.createOne({
data: {
title: 'New Article',
author: {
_connect: { key: { id: 7 } },
},
},
fields: ['id', 'title', { author: ['id', 'name'] }],
});
// Update context — array-wrapped
const article = await client.Articles.updateOne({
key: 1,
data: {
author: [{ _connect: { key: { id: 7 } } }],
},
fields: ['id', 'title', { author: ['id', 'name'] }],
});
# Create context
curl -X POST https://example.monospace.io/api/blog/items/articles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{
"title": "New Article",
"author": {
"_connect": { "key": { "id": 7 } }
}
}]'
# Update context
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"author": [{ "_connect": { "key": { "id": 7 } } }]
}'
// Create context
const response = await fetch('https://example.monospace.io/api/blog/items/articles', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify([{
title: 'New Article',
author: {
_connect: { key: { id: 7 } },
},
}]),
});
// Update context
await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
author: [{ _connect: { key: { id: 7 } } }],
}),
});
To-Many
To-many _connect uses keys (plural, array of key objects).
const article = await client.Articles.updateOne({
key: 1,
data: {
tags: [
{ _connect: { keys: [{ id: 1 }, { id: 3 }, { id: 5 }] } },
],
},
fields: ['id', { tags: { fields: ['id', 'name'] } }],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tags": [
{
"_connect": {
"keys": [{ "id": 1 }, { "id": 3 }, { "id": 5 }]
}
}
]
}'
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tags: [
{
_connect: {
keys: [{ id: 1 }, { id: 3 }, { id: 5 }],
},
},
],
}),
});
const { data } = await response.json();
_connect uses key (singular object). To-many _connect uses keys (plural, array of objects).Create (Inline Related Items)
Use _create to create a new related item and link it in one operation. Available in both create and update contexts.
To-One
To-one _create takes a singular data object.
const article = await client.Articles.createOne({
data: {
title: 'Getting Started',
author: {
_create: { data: { name: 'Jane Doe', email: 'jane@example.com' } },
},
},
fields: ['id', 'title', { author: ['id', 'name'] }],
});
curl -X POST https://example.monospace.io/api/blog/items/articles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{
"title": "Getting Started",
"author": {
"_create": {
"data": { "name": "Jane Doe", "email": "jane@example.com" }
}
}
}]'
const response = await fetch('https://example.monospace.io/api/blog/items/articles', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify([{
title: 'Getting Started',
author: {
_create: {
data: { name: 'Jane Doe', email: 'jane@example.com' },
},
},
}]),
});
const { data } = await response.json();
To-Many
To-many _create takes an array of objects in data.
// Create context — create parent with new related items
const author = await client.Authors.createOne({
data: {
name: 'New Author',
articles: [
{
_create: {
data: [
{ title: 'First Article' },
{ title: 'Second Article' },
],
},
},
],
},
fields: ['id', 'name', { articles: { fields: ['title'] } }],
});
// Update context — add new related items to existing parent
const article = await client.Articles.updateOne({
key: 1,
data: {
comments: [
{ _create: { data: [{ body: 'Great post!' }, { body: 'Thanks for sharing!' }] } },
],
},
fields: ['id', { comments: { fields: ['id', 'body'] } }],
});
# Create context
curl -X POST https://example.monospace.io/api/blog/items/authors \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{
"name": "New Author",
"articles": [
{
"_create": {
"data": [
{ "title": "First Article" },
{ "title": "Second Article" }
]
}
}
]
}]'
# Update context
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"comments": [
{
"_create": {
"data": [
{ "body": "Great post!" },
{ "body": "Thanks for sharing!" }
]
}
}
]
}'
// Create context
const response = await fetch('https://example.monospace.io/api/blog/items/authors', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify([{
name: 'New Author',
articles: [
{
_create: {
data: [
{ title: 'First Article' },
{ title: 'Second Article' },
],
},
},
],
}]),
});
// Update context
await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
comments: [
{
_create: {
data: [
{ body: 'Great post!' },
{ body: 'Thanks for sharing!' },
],
},
},
],
}),
});
_create takes a singular data object. To-many _create takes an array of objects.Disconnect (Unlink Without Deleting)
Use _disconnect to remove a link without deleting the related item. Only available in update context.
To-One
To-one _disconnect takes an empty object {} — there is only one link to remove.
const article = await client.Articles.updateOne({
key: 1,
data: {
author: [{ _disconnect: {} }],
},
fields: ['id', 'title'],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"author": [
{ "_disconnect": {} }
]
}'
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
author: [
{ _disconnect: {} },
],
}),
});
const { data } = await response.json();
_disconnect instead of _delete when you want to keep the related item. For example, unassigning an author from an article without deleting the author.To-Many
To-many _disconnect takes a filter object. Use an empty filter {} to disconnect all.
// Disconnect items matching a filter
const article = await client.Articles.updateOne({
key: 1,
data: {
comments: [
{ _disconnect: { filter: { status: { _eq: 'draft' } } } },
],
},
fields: ['id', { comments: { fields: ['id', 'body', 'status'] } }],
});
// Disconnect all
const article = await client.Articles.updateOne({
key: 1,
data: {
tags: [{ _disconnect: { filter: {} } }],
},
fields: ['id'],
});
# Disconnect items matching a filter
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"comments": [
{
"_disconnect": {
"filter": { "status": { "_eq": "draft" } }
}
}
]
}'
# Disconnect all
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tags": [{ "_disconnect": { "filter": {} } }]
}'
// Disconnect items matching a filter
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
comments: [
{
_disconnect: {
filter: { status: { _eq: 'draft' } },
},
},
],
}),
});
// Disconnect all
await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tags: [{ _disconnect: { filter: {} } }],
}),
});
Update (Modify Related Items in Place)
Use _update to modify a related item's fields without changing the link itself. Only available in update context. Takes data (the fields to change) and an optional filter to target specific items.
To-One
const article = await client.Articles.updateOne({
key: 1,
data: {
author: [{ _update: { data: { name: 'Jane Smith' } } }],
},
fields: ['id', { author: ['id', 'name'] }],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"author": [
{
"_update": {
"data": { "name": "Jane Smith" }
}
}
]
}'
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
author: [
{
_update: {
data: { name: 'Jane Smith' },
},
},
],
}),
});
const { data } = await response.json();
To-Many
Pass both filter and data. Omit filter to update all related items.
// Update matching items by filter
const article = await client.Articles.updateOne({
key: 1,
data: {
comments: [
{ _update: { filter: { status: { _eq: 'pending' } }, data: { status: 'approved' } } },
],
},
fields: ['id', { comments: { fields: ['id', 'body', 'status'] } }],
});
// Update all (omit filter)
const article = await client.Articles.updateOne({
key: 1,
data: {
comments: [
{ _update: { data: { reviewed: true } } },
],
},
fields: ['id'],
});
# Update matching items by filter
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"comments": [
{
"_update": {
"filter": { "status": { "_eq": "pending" } },
"data": { "status": "approved" }
}
}
]
}'
# Update all (omit filter)
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"comments": [{ "_update": { "data": { "reviewed": true } } }]
}'
// Update matching items by filter
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
comments: [
{
_update: {
filter: { status: { _eq: 'pending' } },
data: { status: 'approved' },
},
},
],
}),
});
// Update all (omit filter)
await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
comments: [{ _update: { data: { reviewed: true } } }],
}),
});
Delete (Permanently Remove Related Items)
Use _delete to permanently remove related items from the database. Only available in update context. Takes an optional filter to target specific items.
To-One
const article = await client.Articles.updateOne({
key: 1,
data: {
author: [{ _delete: { filter: {} } }],
},
fields: ['id', 'title'],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"author": [
{ "_delete": { "filter": {} } }
]
}'
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
author: [
{ _delete: { filter: {} } },
],
}),
});
const { data } = await response.json();
To-Many
const article = await client.Articles.updateOne({
key: 1,
data: {
comments: [
{ _delete: { filter: { flagged: { _eq: true } } } },
],
},
fields: ['id', { comments: { fields: ['id', 'body'] } }],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"comments": [
{
"_delete": {
"filter": { "flagged": { "_eq": true } }
}
}
]
}'
const response = await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
comments: [
{
_delete: {
filter: { flagged: { _eq: true } },
},
},
],
}),
});
const { data } = await response.json();
_delete permanently removes items from the database. Use _disconnect to remove only the link.Combine Multiple Operations
Multiple relational operations on the same field are expressed as separate entries in the array. Operations process in the order they appear.
const article = await client.Articles.updateOne({
key: 1,
data: {
tags: [
// 1. Remove links to specific tags
{ _disconnect: { filter: { name: { _starts_with: 'draft' } } } },
// 2. Delete tags that are obsolete
{ _delete: { filter: { id: { _in: [10, 11] } } } },
// 3. Update remaining tags
{ _update: { data: { reviewed: true } } },
// 4. Create a new tag
{ _create: { data: [{ name: 'graphql' }] } },
// 5. Connect existing tags
{ _connect: { keys: [{ id: 7 }] } },
],
},
fields: ['id', { tags: { fields: ['id', 'name'] } }],
});
curl -X PATCH https://example.monospace.io/api/blog/items/articles/1 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tags": [
{ "_disconnect": { "filter": { "name": { "_starts_with": "draft" } } } },
{ "_delete": { "filter": { "id": { "_in": [10, 11] } } } },
{ "_update": { "data": { "reviewed": true } } },
{ "_create": { "data": [{ "name": "graphql" }] } },
{ "_connect": { "keys": [{ "id": 7 }] } }
]
}'
await fetch('https://example.monospace.io/api/blog/items/articles/1', {
method: 'PATCH',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tags: [
{ _disconnect: { filter: { name: { _starts_with: 'draft' } } } },
{ _delete: { filter: { id: { _in: [10, 11] } } } },
{ _update: { data: { reviewed: true } } },
{ _create: { data: [{ name: 'graphql' }] } },
{ _connect: { keys: [{ id: 7 }] } },
],
}),
});
Nest Across Multiple Levels
Relational operations compose at any depth. Create an article with a new author and comments, where comments themselves link to existing authors.
const article = await client.Articles.createOne({
data: {
title: 'New Article',
author: { _create: { data: { name: 'Jane' } } },
comments: [
{
_create: {
data: [
{ body: 'First!', author: { _connect: { key: { id: 1 } } } },
],
},
},
],
},
fields: ['id', 'title', { author: ['id', 'name'] }, { comments: { fields: ['id', 'body', { author: ['id', 'name'] }] } }],
});
curl -X POST https://example.monospace.io/api/blog/items/articles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{
"title": "New Article",
"author": {
"_create": { "data": { "name": "Jane" } }
},
"comments": [
{
"_create": {
"data": [
{
"body": "First!",
"author": { "_connect": { "key": { "id": 1 } } }
}
]
}
}
]
}]'
const response = await fetch('https://example.monospace.io/api/blog/items/articles', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify([{
title: 'New Article',
author: {
_create: { data: { name: 'Jane' } },
},
comments: [
{
_create: {
data: [
{
body: 'First!',
author: { _connect: { key: { id: 1 } } },
},
],
},
},
],
}]),
});
const { data } = await response.json();
Context Differences (Create vs Update)
The available operations and syntax differ between create and update contexts.
Create Context
Only _connect and _create are available. You cannot disconnect, update, or delete relations that do not exist yet.
- To-one: the relation field is a singular object containing one operation
- To-many: the relation field is an array where each element is one operation
const article = await client.Articles.createOne({
data: {
title: 'New Article',
// To-one: singular object
author: { _connect: { key: { id: 1 } } },
// To-many: array of operations
comments: [
{ _create: { data: [{ body: 'Welcome!' }] } },
{ _connect: { keys: [{ id: 100 }, { id: 101 }] } },
],
},
fields: ['id'],
});
Update Context
All five operations are available: _connect, _create, _disconnect, _update, _delete.
Both to-one and to-many relations use arrays in update context. This enables combining multiple operations on the same field — for example, disconnecting the current author then connecting a new one:
const article = await client.Articles.updateOne({
key: 1,
data: {
// To-one: array wrapping in update context
author: [
{ _disconnect: {} },
{ _connect: { key: { id: 5 } } },
],
// To-many: also array wrapping
comments: [
{ _disconnect: { filter: { status: { _eq: 'spam' } } } },
{ _delete: { filter: { flagged: { _eq: true } } } },
{ _update: { data: { status: 'reviewed' }, filter: { status: { _eq: 'pending' } } } },
{ _create: { data: [{ body: 'Pinned comment' }] } },
],
},
fields: ['id'],
});
Quick Reference
| Operation | To-One (Create) | To-One (Update) | To-Many (Create) | To-Many (Update) |
|---|---|---|---|---|
| Create | { _create: { data: { ... } } } | [{ _create: { data: { ... } } }] | [{ _create: { data: [{ ... }] } }] | [{ _create: { data: [{ ... }] } }] |
| Connect | { _connect: { key: { id } } } | [{ _connect: { key: { id } } }] | [{ _connect: { keys: [{ id }] } }] | [{ _connect: { keys: [{ id }] } }] |
| Disconnect | N/A | [{ _disconnect: {} }] | N/A | [{ _disconnect: { filter: { ... } } }] |
| Update | N/A | [{ _update: { data: { ... } } }] | N/A | [{ _update: { filter: { ... }, data: { ... } } }] |
| Delete | N/A | [{ _delete: { filter: {} } }] | N/A | [{ _delete: { filter: { ... } } }] |
Key differences:
- Create context supports only
_connectand_create. Update context supports all five operations. - To-one in create context is a singular object. All update context relations use arrays.
- Connect uses
key(singular) for to-one,keys(plural array) for to-many. - Disconnect takes an empty object
{}for to-one (there is only one link), afilterfor to-many. - Create takes a single
dataobject for to-one, an array for to-many.
Atomicity
All relational operations within a single request execute atomically within a single data source. If any operation fails, the entire request rolls back — no partial writes are persisted.
See Also
- Field Selection — wildcards, nested fields, and expanded relation syntax
- Filtering — filter syntax used in
_disconnect,_update, and_delete - Writing Data — basic create, update, and delete operations
- Type System — input types for create and update payloads