Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .optimize-cache.json
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@
"static/images/blog/reducing-cold-starts-appwrite-sites/build-output-sizes-nft.png": "19f7c37c248fa871786fd8a47064475520e4dcb1df36c996871f3983fcd72495",
"static/images/blog/reducing-cold-starts-appwrite-sites/build-output-sizes-non-nft.png": "8b814c3736045e286e95e0866f7d61ae589ab8492985edce7c6b23e18d84dc7e",
"static/images/blog/reducing-cold-starts-appwrite-sites/cover.png": "17a950964dddaaafab1461ee0dbecd6170f4002d357ecdbd7e2152028a39f63e",
"static/images/blog/relationships-are-out-of-beta/cover.png": "7f2a8e8044de859606f69191ae3fded84ad2211180a5183d9b5cc2f5d2d60e9a",
Comment thread
adityaoberai marked this conversation as resolved.
"static/images/blog/remix-3-whats-changing-and-why-it-matters/cover.png": "258303cffbe98e2b76642220c091492f0c77cfedcd1989167a92683709f5f38d",
"static/images/blog/rest-vs-graphql-websockets/cover.png": "74e82a5592d964caac5425b6846c0c361e5f516867f8feaf5b2baca9b7e69860",
"static/images/blog/rethinking-saas-authentication/cover.png": "0240c259c4ab551f07c6a3c7ace5768fe6842b33e6509e34ae624e47d9308d40",
Expand Down
97 changes: 97 additions & 0 deletions src/routes/blog/post/relationships-are-out-of-beta/+page.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
layout: post
title: "Database relationships are out of beta"
description: After a year of performance overhauls, opt-in loading, and full query support, database relationships in Appwrite are graduating from experimental to production-ready.
date: 2026-05-12
cover: /images/blog/relationships-are-out-of-beta/cover.avif
timeToRead: 5
Comment thread
greptile-apps[bot] marked this conversation as resolved.
author: jake-barnby
category: announcement
featured: true
faqs:
- question: "What does 'production-ready' mean for database relationships in Appwrite?"
answer: "It means the API and behavior are stable, performance is fast enough for real workloads, and the feature set is complete. Column types, directionality, on-delete behaviors, nested creation, query selection, and dot-notation filter queries are all locked in. We'll evolve database relationships, but we won't break them."
- question: "Do I need to update my code or SDK to benefit from this?"
answer: "No. If you're already using relationships, you don't need to do anything. The 12-18x performance improvements apply automatically, and existing queries continue to work. Opt-in relationship loading is fully backward compatible, so older SDK versions retain their previous behavior."
- question: "Can I filter rows based on related row data?"
answer: "Yes. You can use filter queries directly on relationship columns using dot notation, like `Query.equal('author.name', ['Jake'])`. All comparison operators are supported, including equal, notEqual, greaterThan, lessThan, between, contains, and spatial queries."
- question: "Are there any remaining limitations?"
answer: "Relationships are restricted to a maximum nesting depth of three levels. Relationship column key, type, and directionality cannot be updated after creation, only the on-delete behavior can be changed. These limits keep query performance predictable."
- question: "Is this available on self-hosted Appwrite?"
answer: "Yes. Database relationships are production-ready on both Appwrite Cloud and self-hosted installations. The performance gains, query support, and stability guarantees apply to both."
---

Database relationships have been part of Appwrite for a while, but they shipped with an "experimental" label and a real set of rough edges. Payloads got bloated, queries couldn't reach across related rows, and performance wasn't where it needed to be for production workloads.

Over the past year, we've fixed all of that. Today, we're dropping the experimental label.

**Database relationships in Appwrite are now production-ready.** The API is stable, performance is fast, and the feature set is complete.

# What changed over the past year

This isn't a single release, it's the accumulation of a year of work. Here's what landed along the way.

## Opt-in relationship loading

The first major shift came in August 2025 with [opt-in relationship loading](/blog/post/announcing-opt-in-relationship-loading). Previously, querying a row pulled in every related row automatically, which often meant fetching data you didn't need and shipping bloated JSON payloads back to your app.

We flipped the default. Now, rows return only their own fields unless you explicitly request related data through query selection. The result: smaller payloads, less bandwidth, faster responses, and no more accidental N+1 surprises.

## Filter queries across relationships

For a long time, the answer to "how do I find all posts by a specific author?" was "fetch everything and filter in your app." That changed in February 2026 with [relationship queries](/blog/post/announcing-relationship-queries).

You can now filter directly against relationship columns:

```js
// Get all posts where the author's name is 'Jake'
await tablesDB.listRows({
databaseId: 'blog',
tableId: 'posts',
queries: [
Query.equal('author.name', ['Jake'])
],
});
```

Every comparison operator works on relationship fields: `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and the full set of spatial queries. Filtering happens in the database, not in your application layer.

## 12-18x faster relationship performance

Alongside [query support](/blog/post/announcing-relationship-queries), we rewrote the internals of how relationships are resolved. Reads, writes, and joins across related tables are now **12-18x faster** across the board. There's nothing to configure, no flag to flip. If you're using relationships today, they're already faster.

That performance work is what made it safe to drop the experimental label. Relationships are now fast enough to be the default way you model connected data.

## CSV export with relationship support

The Console now [exports relationship fields cleanly as IDs](/blog/post/announcing-csv-export) when you download a table as CSV. Small thing, but it means relationships don't break your export pipelines or reporting workflows anymore.

# What "production-ready" actually means

When we called relationships experimental, we were telling you two things: the API might change, and the performance might not hold up under real load. Both of those caveats are gone.

- **The API is stable.** Column types, directionality, on-delete behaviors, nested creation, dot-notation queries, and query selection are all locked in. We'll evolve them, but we won't break them.
- **Performance holds up under load.** With the 12-18x improvements and opt-in loading, relationships handle real workloads without the payload bloat or latency footguns that originally earned the experimental label.
- **The feature set is complete.** All four relationship types, both directionalities, all three on-delete behaviors, full query support, and full permission inheritance. You can model what you need to model.

# What this unlocks

With queryable, fast, stable relationships, you can confidently build:

- **Normalized data models** without the duplication anomalies that plague flat schemas.
- **Filtered views across tables**, like "all articles by authors in a specific country" or "all orders containing a product in a category."
- **Search with relational context** that runs in the database instead of your app.
- **Dashboards and reports** that aggregate across related tables in a single query.
- **Snappier UIs** for any view that loads multiple levels of related data.

# Availability

Relationships are production-ready on both **Appwrite Cloud** and **self-hosted**. If you're already using relationships, you don't need to do anything, the performance and stability gains have been rolling out to you all along.

# More resources

- [Read the relationships documentation](/docs/products/databases/relationships)
- [Announcing relationship queries: Filter across related data with ease](/blog/post/announcing-relationship-queries)
- [Announcing opt-in relationship loading](/blog/post/announcing-opt-in-relationship-loading)
- [Simplify your data management with relationships](/blog/post/simplify-your-data-management-with-relationships)
- [Learn about queries in Appwrite Databases](/docs/products/databases/queries)
19 changes: 19 additions & 0 deletions src/routes/changelog/(entries)/2026-05-13.markdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
layout: changelog
title: "Database relationships are out of beta"
date: 2026-05-13
cover: /images/blog/relationships-are-out-of-beta/cover.avif
---

Database relationships in Appwrite are graduating from experimental to **production-ready**. After a year of performance, ergonomics, and capability improvements, they are now a first-class way to model connected data.

**What's shipped over the past year**

- **Opt-in relationship loading**: Related rows are no longer pulled in automatically, you select exactly which relationships to load using query selection. Smaller payloads, faster responses.
- **Filter queries on relationships**: Filter across related data using dot notation, like `Query.equal('author.name', ['Jake'])`. All comparison operators are supported, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and spatial queries.
- **12-18x faster relationship operations**: A full internal overhaul made every relationship read, write, and join dramatically faster, with no configuration changes required.
- **CSV export support**: Relationship fields export cleanly as IDs from the Console.

{% arrow_link href="/blog/post/relationships-are-out-of-beta" %}
Read the announcement to learn more
{% /arrow_link %}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ Relationships describe how documents in different collections are associated, so

These types of association between entities can be modeled in Appwrite using relationships.

{% info title="Experimental feature" %}
Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions.
{% /info %}

# Relationship Attributes {% #relationship-attributes %}

Relationships are represented in a collection using **relationship attributes**.
Expand Down Expand Up @@ -518,7 +514,92 @@ databases.createDocument(
{% /tabs %}

# Queries {% #queries %}
Queries are currently not available in the experimental version of Appwrite Relationships but will be added in a later version.

You can use filter queries directly against relationship attributes using dot notation. This lets you filter documents based on the values of their related documents, such as filtering posts by an author's name or filtering orders by a product's category.

Use the format `relationshipKey.field` to reference fields on related documents.

{% multicode %}
```js
const { Client, Databases, Query } = require('node-appwrite');

const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<PROJECT_ID>'); // Your project ID

const databases = new Databases(client);

await databases.listDocuments(
'marvel',
'movies',
[
Query.equal('reviews.author', ['Bob'])
]
);
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');

final databases = Databases(client);

await databases.listDocuments(
databaseId: 'marvel',
collectionId: 'movies',
queries: [
Query.equal('reviews.author', ['Bob']),
],
);
```

```swift
import Appwrite

let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")

let databases = Databases(client)

databases.listDocuments(
databaseId: "marvel",
collectionId: "movies",
queries: [
Query.equal("reviews.author", value: ["Bob"])
]
)
```

```kotlin
import io.appwrite.Client
import io.appwrite.services.Databases
import io.appwrite.Query

val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")

val databases = Databases(client)

databases.listDocuments(
databaseId = "marvel",
collectionId = "movies",
queries = listOf(
Query.equal("reviews.author", listOf("Bob"))
)
)
```
{% /multicode %}

All filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/legacy/queries#comparison).

{% arrow_link href="/docs/products/databases/legacy/queries#relationship-select" %}
Learn how to select and load relationship data
{% /arrow_link %}

# Update Relationships {% #update %}
Relationships can be updated by updating the relationship attribute.
Expand Down
64 changes: 31 additions & 33 deletions src/routes/docs/products/databases/relationships/+page.markdoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ Relationships describe how rows in different tables are associated, so that rela

These types of association between entities can be modeled in Appwrite using relationships.

{% info title="Experimental feature" %}
Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions.
{% /info %}

# Relationship columns {% #relationship-columns %}

Relationships are represented in a table using **relationship columns**.
Expand Down Expand Up @@ -96,16 +92,16 @@ const client = new Client()

const tablesDB = new TablesDB(client);

tablesDB.createRelationshipColumn(
'marvel', // Database ID
'movies', // Table ID
'reviews', // Related table ID
'oneToMany', // Relationship type
true, // Is two-way
'reviews', // Column key
'movie', // Two-way column key
'cascade' // On delete action
);
tablesDB.createRelationshipColumn({
databaseId: 'marvel', // Database ID
tableId: 'movies', // Table ID
relatedTableId: 'reviews', // Related table ID
type: 'oneToMany', // Relationship type
twoWay: true, // Is two-way
key: 'reviews', // Column key
twoWayKey: 'movie', // Two-way column key
onDelete: 'cascade' // On delete action
});
```


Expand Down Expand Up @@ -417,6 +413,8 @@ const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<PROJECT_ID>'); // Your project ID

const tablesDB = new TablesDB(client);

await tablesDB.createRow({
databaseId: 'marvel',
tableId: 'movies',
Expand All @@ -427,9 +425,9 @@ await tablesDB.createRow({
reviews: [
'<REVIEW_ID_1>',
'<REVIEW_ID_2>'
});
]
}
)
});
```

```dart
Expand Down Expand Up @@ -610,19 +608,19 @@ const client = new Client()

const tablesDB = new TablesDB(client);

await tablesDB.updateRow(
'marvel',
'movies',
'spiderman',
{
await tablesDB.updateRow({
databaseId: 'marvel',
tableId: 'movies',
rowId: 'spiderman',
data: {
title: 'Spiderman',
year: 2002,
reviews: [
'review4',
'review5'
]
}
);
});
```

```dart
Expand Down Expand Up @@ -729,11 +727,11 @@ const client = new Client()

const tablesDB = new TablesDB(client);

await tablesDB.deleteRow(
'marvel',
'movies',
'spiderman'
);
await tablesDB.deleteRow({
databaseId: 'marvel',
tableId: 'movies',
rowId: 'spiderman'
});
```

```dart
Expand Down Expand Up @@ -804,11 +802,11 @@ const client = new Client()

const tablesDB = new TablesDB(client);

await tablesDB.createRow(
'marvel',
'movies',
ID.unique(),
{
await tablesDB.createRow({
databaseId: 'marvel',
tableId: 'movies',
rowId: ID.unique(),
data: {
title: 'Spiderman',
year: 2002,
reviews: [
Expand All @@ -821,7 +819,7 @@ await tablesDB.createRow(
},
]
}
);
});
```
```dart
import 'package:appwrite/appwrite.dart';
Expand Down
Binary file not shown.
Loading