Skip to content

Commit 68fe862

Browse files
authored
Merge pull request #2997 from appwrite/relationships-ga
Remove experimental tag from relationships
2 parents 5964a6b + 2a80ee5 commit 68fe862

6 files changed

Lines changed: 234 additions & 38 deletions

File tree

.optimize-cache.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,7 @@
984984
"static/images/blog/reducing-cold-starts-appwrite-sites/build-output-sizes-nft.png": "19f7c37c248fa871786fd8a47064475520e4dcb1df36c996871f3983fcd72495",
985985
"static/images/blog/reducing-cold-starts-appwrite-sites/build-output-sizes-non-nft.png": "8b814c3736045e286e95e0866f7d61ae589ab8492985edce7c6b23e18d84dc7e",
986986
"static/images/blog/reducing-cold-starts-appwrite-sites/cover.png": "17a950964dddaaafab1461ee0dbecd6170f4002d357ecdbd7e2152028a39f63e",
987+
"static/images/blog/relationships-are-out-of-beta/cover.png": "7f2a8e8044de859606f69191ae3fded84ad2211180a5183d9b5cc2f5d2d60e9a",
987988
"static/images/blog/remix-3-whats-changing-and-why-it-matters/cover.png": "258303cffbe98e2b76642220c091492f0c77cfedcd1989167a92683709f5f38d",
988989
"static/images/blog/rest-vs-graphql-websockets/cover.png": "74e82a5592d964caac5425b6846c0c361e5f516867f8feaf5b2baca9b7e69860",
989990
"static/images/blog/rethinking-saas-authentication/cover.png": "0240c259c4ab551f07c6a3c7ace5768fe6842b33e6509e34ae624e47d9308d40",
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
layout: post
3+
title: "Database relationships are out of beta"
4+
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.
5+
date: 2026-05-12
6+
cover: /images/blog/relationships-are-out-of-beta/cover.avif
7+
timeToRead: 5
8+
author: jake-barnby
9+
category: announcement
10+
featured: true
11+
faqs:
12+
- question: "What does 'production-ready' mean for database relationships in Appwrite?"
13+
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."
14+
- question: "Do I need to update my code or SDK to benefit from this?"
15+
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."
16+
- question: "Can I filter rows based on related row data?"
17+
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."
18+
- question: "Are there any remaining limitations?"
19+
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."
20+
- question: "Is this available on self-hosted Appwrite?"
21+
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."
22+
---
23+
24+
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.
25+
26+
Over the past year, we've fixed all of that. Today, we're dropping the experimental label.
27+
28+
**Database relationships in Appwrite are now production-ready.** The API is stable, performance is fast, and the feature set is complete.
29+
30+
# What changed over the past year
31+
32+
This isn't a single release, it's the accumulation of a year of work. Here's what landed along the way.
33+
34+
## Opt-in relationship loading
35+
36+
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.
37+
38+
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.
39+
40+
## Filter queries across relationships
41+
42+
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).
43+
44+
You can now filter directly against relationship columns:
45+
46+
```js
47+
// Get all posts where the author's name is 'Jake'
48+
await tablesDB.listRows({
49+
databaseId: 'blog',
50+
tableId: 'posts',
51+
queries: [
52+
Query.equal('author.name', ['Jake'])
53+
],
54+
});
55+
```
56+
57+
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.
58+
59+
## 12-18x faster relationship performance
60+
61+
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.
62+
63+
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.
64+
65+
## CSV export with relationship support
66+
67+
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.
68+
69+
# What "production-ready" actually means
70+
71+
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.
72+
73+
- **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.
74+
- **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.
75+
- **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.
76+
77+
# What this unlocks
78+
79+
With queryable, fast, stable relationships, you can confidently build:
80+
81+
- **Normalized data models** without the duplication anomalies that plague flat schemas.
82+
- **Filtered views across tables**, like "all articles by authors in a specific country" or "all orders containing a product in a category."
83+
- **Search with relational context** that runs in the database instead of your app.
84+
- **Dashboards and reports** that aggregate across related tables in a single query.
85+
- **Snappier UIs** for any view that loads multiple levels of related data.
86+
87+
# Availability
88+
89+
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.
90+
91+
# More resources
92+
93+
- [Read the relationships documentation](/docs/products/databases/relationships)
94+
- [Announcing relationship queries: Filter across related data with ease](/blog/post/announcing-relationship-queries)
95+
- [Announcing opt-in relationship loading](/blog/post/announcing-opt-in-relationship-loading)
96+
- [Simplify your data management with relationships](/blog/post/simplify-your-data-management-with-relationships)
97+
- [Learn about queries in Appwrite Databases](/docs/products/databases/queries)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
layout: changelog
3+
title: "Database relationships are out of beta"
4+
date: 2026-05-13
5+
cover: /images/blog/relationships-are-out-of-beta/cover.avif
6+
---
7+
8+
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.
9+
10+
**What's shipped over the past year**
11+
12+
- **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.
13+
- **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.
14+
- **12-18x faster relationship operations**: A full internal overhaul made every relationship read, write, and join dramatically faster, with no configuration changes required.
15+
- **CSV export support**: Relationship fields export cleanly as IDs from the Console.
16+
17+
{% arrow_link href="/blog/post/relationships-are-out-of-beta" %}
18+
Read the announcement to learn more
19+
{% /arrow_link %}

src/routes/docs/products/databases/legacy/relationships/+page.markdoc

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ Relationships describe how documents in different collections are associated, so
99

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

12-
{% info title="Experimental feature" %}
13-
Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions.
14-
{% /info %}
15-
1612
# Relationship Attributes {% #relationship-attributes %}
1713

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

520516
# Queries {% #queries %}
521-
Queries are currently not available in the experimental version of Appwrite Relationships but will be added in a later version.
517+
518+
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.
519+
520+
Use the format `relationshipKey.field` to reference fields on related documents.
521+
522+
{% multicode %}
523+
```js
524+
const { Client, Databases, Query } = require('node-appwrite');
525+
526+
const client = new Client()
527+
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
528+
.setProject('<PROJECT_ID>'); // Your project ID
529+
530+
const databases = new Databases(client);
531+
532+
await databases.listDocuments(
533+
'marvel',
534+
'movies',
535+
[
536+
Query.equal('reviews.author', ['Bob'])
537+
]
538+
);
539+
```
540+
541+
```dart
542+
import 'package:appwrite/appwrite.dart';
543+
544+
final client = Client()
545+
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
546+
.setProject('<PROJECT_ID>');
547+
548+
final databases = Databases(client);
549+
550+
await databases.listDocuments(
551+
databaseId: 'marvel',
552+
collectionId: 'movies',
553+
queries: [
554+
Query.equal('reviews.author', ['Bob']),
555+
],
556+
);
557+
```
558+
559+
```swift
560+
import Appwrite
561+
562+
let client = Client()
563+
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
564+
.setProject("<PROJECT_ID>")
565+
566+
let databases = Databases(client)
567+
568+
databases.listDocuments(
569+
databaseId: "marvel",
570+
collectionId: "movies",
571+
queries: [
572+
Query.equal("reviews.author", value: ["Bob"])
573+
]
574+
)
575+
```
576+
577+
```kotlin
578+
import io.appwrite.Client
579+
import io.appwrite.services.Databases
580+
import io.appwrite.Query
581+
582+
val client = Client()
583+
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
584+
.setProject("<PROJECT_ID>")
585+
586+
val databases = Databases(client)
587+
588+
databases.listDocuments(
589+
databaseId = "marvel",
590+
collectionId = "movies",
591+
queries = listOf(
592+
Query.equal("reviews.author", listOf("Bob"))
593+
)
594+
)
595+
```
596+
{% /multicode %}
597+
598+
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).
599+
600+
{% arrow_link href="/docs/products/databases/legacy/queries#relationship-select" %}
601+
Learn how to select and load relationship data
602+
{% /arrow_link %}
522603

523604
# Update Relationships {% #update %}
524605
Relationships can be updated by updating the relationship attribute.

src/routes/docs/products/databases/relationships/+page.markdoc

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ Relationships describe how rows in different tables are associated, so that rela
99

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

12-
{% info title="Experimental feature" %}
13-
Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions.
14-
{% /info %}
15-
1612
# Relationship columns {% #relationship-columns %}
1713

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

9793
const tablesDB = new TablesDB(client);
9894

99-
tablesDB.createRelationshipColumn(
100-
'marvel', // Database ID
101-
'movies', // Table ID
102-
'reviews', // Related table ID
103-
'oneToMany', // Relationship type
104-
true, // Is two-way
105-
'reviews', // Column key
106-
'movie', // Two-way column key
107-
'cascade' // On delete action
108-
);
95+
tablesDB.createRelationshipColumn({
96+
databaseId: 'marvel', // Database ID
97+
tableId: 'movies', // Table ID
98+
relatedTableId: 'reviews', // Related table ID
99+
type: 'oneToMany', // Relationship type
100+
twoWay: true, // Is two-way
101+
key: 'reviews', // Column key
102+
twoWayKey: 'movie', // Two-way column key
103+
onDelete: 'cascade' // On delete action
104+
});
109105
```
110106

111107

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

416+
const tablesDB = new TablesDB(client);
417+
420418
await tablesDB.createRow({
421419
databaseId: 'marvel',
422420
tableId: 'movies',
@@ -427,9 +425,9 @@ await tablesDB.createRow({
427425
reviews: [
428426
'<REVIEW_ID_1>',
429427
'<REVIEW_ID_2>'
430-
});
428+
]
431429
}
432-
)
430+
});
433431
```
434432

435433
```dart
@@ -610,19 +608,19 @@ const client = new Client()
610608

611609
const tablesDB = new TablesDB(client);
612610

613-
await tablesDB.updateRow(
614-
'marvel',
615-
'movies',
616-
'spiderman',
617-
{
611+
await tablesDB.updateRow({
612+
databaseId: 'marvel',
613+
tableId: 'movies',
614+
rowId: 'spiderman',
615+
data: {
618616
title: 'Spiderman',
619617
year: 2002,
620618
reviews: [
621619
'review4',
622620
'review5'
623621
]
624622
}
625-
);
623+
});
626624
```
627625

628626
```dart
@@ -729,11 +727,11 @@ const client = new Client()
729727

730728
const tablesDB = new TablesDB(client);
731729

732-
await tablesDB.deleteRow(
733-
'marvel',
734-
'movies',
735-
'spiderman'
736-
);
730+
await tablesDB.deleteRow({
731+
databaseId: 'marvel',
732+
tableId: 'movies',
733+
rowId: 'spiderman'
734+
});
737735
```
738736

739737
```dart
@@ -804,11 +802,11 @@ const client = new Client()
804802

805803
const tablesDB = new TablesDB(client);
806804

807-
await tablesDB.createRow(
808-
'marvel',
809-
'movies',
810-
ID.unique(),
811-
{
805+
await tablesDB.createRow({
806+
databaseId: 'marvel',
807+
tableId: 'movies',
808+
rowId: ID.unique(),
809+
data: {
812810
title: 'Spiderman',
813811
year: 2002,
814812
reviews: [
@@ -821,7 +819,7 @@ await tablesDB.createRow(
821819
},
822820
]
823821
}
824-
);
822+
});
825823
```
826824
```dart
827825
import 'package:appwrite/appwrite.dart';
17.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)