From 2a3923bb2512b43940e65ad741d4ebadd245c4dd Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Fri, 26 Jun 2026 17:20:23 +0530 Subject: [PATCH 1/5] Restructure databases docs into per-database-type sections Split the Databases docs into a menu of database types. The /docs/products/databases overview becomes a menu (Appwrite databases: TablesDB, DocumentsDB, VectorsDB; Native databases: PostgreSQL, MySQL). Existing TablesDB content moves under /tablesdb with its full sidebar; DocumentsDB, VectorsDB, PostgreSQL, and MySQL get their own sections with placeholder overviews. Adds redirects for all moved pages. --- src/redirects.json | 142 +++++++++++++++++- .../databases/(overview)/+layout.svelte | 64 ++++++++ .../databases/(overview)/+page.markdoc | 40 +++++ .../databases/documentsdb/+layout.svelte | 29 ++++ .../databases/documentsdb/+page.markdoc | 11 ++ .../products/databases/mysql/+layout.svelte | 29 ++++ .../products/databases/mysql/+page.markdoc | 11 ++ .../databases/postgresql/+layout.svelte | 29 ++++ .../databases/postgresql/+page.markdoc | 11 ++ .../databases/{ => tablesdb}/+layout.svelte | 54 +++---- .../databases/{ => tablesdb}/+page.markdoc | 6 +- .../ai-suggestions/+page.markdoc | 0 .../atomic-numeric-operations/+page.markdoc | 0 .../{ => tablesdb}/backups/+page.markdoc | 0 .../bulk-operations/+page.markdoc | 0 .../{ => tablesdb}/csv-exports/+page.markdoc | 0 .../{ => tablesdb}/csv-imports/+page.markdoc | 0 .../{ => tablesdb}/databases/+page.markdoc | 0 .../{ => tablesdb}/geo-queries/+page.markdoc | 0 .../{ => tablesdb}/legacy/+layout.svelte | 0 .../databases/{ => tablesdb}/legacy/+page.ts | 0 .../atomic-numeric-operations/+page.markdoc | 0 .../legacy/bulk-operations/+page.markdoc | 0 .../legacy/collections/+page.markdoc | 0 .../legacy/databases/+page.markdoc | 0 .../legacy/documents/+page.markdoc | 0 .../{ => tablesdb}/legacy/order/+page.markdoc | 0 .../legacy/pagination/+page.markdoc | 0 .../legacy/permissions/+page.markdoc | 0 .../legacy/queries/+page.markdoc | 0 .../legacy/quick-start/+page.markdoc | 0 .../legacy/relationships/+page.markdoc | 0 .../legacy/type-generation/+page.markdoc | 0 .../{ => tablesdb}/offline/+page.markdoc | 0 .../{ => tablesdb}/operators/+page.markdoc | 0 .../{ => tablesdb}/order/+page.markdoc | 0 .../{ => tablesdb}/pagination/+page.markdoc | 0 .../{ => tablesdb}/permissions/+page.markdoc | 0 .../{ => tablesdb}/queries/+page.markdoc | 0 .../{ => tablesdb}/quick-start/+page.markdoc | 0 .../relationships/+page.markdoc | 0 .../{ => tablesdb}/rows/+page.markdoc | 0 .../{ => tablesdb}/tables/+page.markdoc | 0 .../timestamp-overrides/+page.markdoc | 0 .../{ => tablesdb}/transactions/+page.markdoc | 0 .../type-generation/+page.markdoc | 0 .../databases/vectorsdb/+layout.svelte | 29 ++++ .../databases/vectorsdb/+page.markdoc | 11 ++ 48 files changed, 431 insertions(+), 35 deletions(-) create mode 100644 src/routes/docs/products/databases/(overview)/+layout.svelte create mode 100644 src/routes/docs/products/databases/(overview)/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/+layout.svelte create mode 100644 src/routes/docs/products/databases/documentsdb/+page.markdoc create mode 100644 src/routes/docs/products/databases/mysql/+layout.svelte create mode 100644 src/routes/docs/products/databases/mysql/+page.markdoc create mode 100644 src/routes/docs/products/databases/postgresql/+layout.svelte create mode 100644 src/routes/docs/products/databases/postgresql/+page.markdoc rename src/routes/docs/products/databases/{ => tablesdb}/+layout.svelte (70%) rename src/routes/docs/products/databases/{ => tablesdb}/+page.markdoc (70%) rename src/routes/docs/products/databases/{ => tablesdb}/ai-suggestions/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/atomic-numeric-operations/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/backups/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/bulk-operations/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/csv-exports/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/csv-imports/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/databases/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/geo-queries/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/+layout.svelte (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/+page.ts (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/atomic-numeric-operations/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/bulk-operations/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/collections/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/databases/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/documents/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/order/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/pagination/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/permissions/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/queries/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/quick-start/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/relationships/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/legacy/type-generation/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/offline/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/operators/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/order/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/pagination/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/permissions/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/queries/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/quick-start/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/relationships/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/rows/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/tables/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/timestamp-overrides/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/transactions/+page.markdoc (100%) rename src/routes/docs/products/databases/{ => tablesdb}/type-generation/+page.markdoc (100%) create mode 100644 src/routes/docs/products/databases/vectorsdb/+layout.svelte create mode 100644 src/routes/docs/products/databases/vectorsdb/+page.markdoc diff --git a/src/redirects.json b/src/redirects.json index 17340a23b84..65ebd11afc0 100644 --- a/src/redirects.json +++ b/src/redirects.json @@ -97,15 +97,15 @@ }, { "link": "/docs/databases-queries", - "redirect": "/docs/products/databases/queries" + "redirect": "/docs/products/databases/tablesdb/queries" }, { "link": "/docs/databases-pagination", - "redirect": "/docs/products/databases/pagination" + "redirect": "/docs/products/databases/tablesdb/pagination" }, { "link": "/docs/databases-relationships", - "redirect": "/docs/products/databases/relationships" + "redirect": "/docs/products/databases/tablesdb/relationships" }, { "link": "/docs/storage", @@ -213,11 +213,11 @@ }, { "link": "/docs/queries", - "redirect": "/docs/products/databases/queries" + "redirect": "/docs/products/databases/tablesdb/queries" }, { "link": "/docs/pagination", - "redirect": "/docs/products/databases/pagination" + "redirect": "/docs/products/databases/tablesdb/pagination" }, { "link": "/docs/webhooks", @@ -915,5 +915,137 @@ { "link": "/heroes", "redirect": "/" + }, + { + "link": "/docs/products/databases/ai-suggestions", + "redirect": "/docs/products/databases/tablesdb/ai-suggestions" + }, + { + "link": "/docs/products/databases/atomic-numeric-operations", + "redirect": "/docs/products/databases/tablesdb/atomic-numeric-operations" + }, + { + "link": "/docs/products/databases/backups", + "redirect": "/docs/products/databases/tablesdb/backups" + }, + { + "link": "/docs/products/databases/bulk-operations", + "redirect": "/docs/products/databases/tablesdb/bulk-operations" + }, + { + "link": "/docs/products/databases/csv-exports", + "redirect": "/docs/products/databases/tablesdb/csv-exports" + }, + { + "link": "/docs/products/databases/csv-imports", + "redirect": "/docs/products/databases/tablesdb/csv-imports" + }, + { + "link": "/docs/products/databases/databases", + "redirect": "/docs/products/databases/tablesdb/databases" + }, + { + "link": "/docs/products/databases/geo-queries", + "redirect": "/docs/products/databases/tablesdb/geo-queries" + }, + { + "link": "/docs/products/databases/legacy/atomic-numeric-operations", + "redirect": "/docs/products/databases/tablesdb/legacy/atomic-numeric-operations" + }, + { + "link": "/docs/products/databases/legacy/bulk-operations", + "redirect": "/docs/products/databases/tablesdb/legacy/bulk-operations" + }, + { + "link": "/docs/products/databases/legacy/collections", + "redirect": "/docs/products/databases/tablesdb/legacy/collections" + }, + { + "link": "/docs/products/databases/legacy/databases", + "redirect": "/docs/products/databases/tablesdb/legacy/databases" + }, + { + "link": "/docs/products/databases/legacy/documents", + "redirect": "/docs/products/databases/tablesdb/legacy/documents" + }, + { + "link": "/docs/products/databases/legacy/order", + "redirect": "/docs/products/databases/tablesdb/legacy/order" + }, + { + "link": "/docs/products/databases/legacy/pagination", + "redirect": "/docs/products/databases/tablesdb/legacy/pagination" + }, + { + "link": "/docs/products/databases/legacy/permissions", + "redirect": "/docs/products/databases/tablesdb/legacy/permissions" + }, + { + "link": "/docs/products/databases/legacy/queries", + "redirect": "/docs/products/databases/tablesdb/legacy/queries" + }, + { + "link": "/docs/products/databases/legacy/quick-start", + "redirect": "/docs/products/databases/tablesdb/legacy/quick-start" + }, + { + "link": "/docs/products/databases/legacy/relationships", + "redirect": "/docs/products/databases/tablesdb/legacy/relationships" + }, + { + "link": "/docs/products/databases/legacy/type-generation", + "redirect": "/docs/products/databases/tablesdb/legacy/type-generation" + }, + { + "link": "/docs/products/databases/offline", + "redirect": "/docs/products/databases/tablesdb/offline" + }, + { + "link": "/docs/products/databases/operators", + "redirect": "/docs/products/databases/tablesdb/operators" + }, + { + "link": "/docs/products/databases/order", + "redirect": "/docs/products/databases/tablesdb/order" + }, + { + "link": "/docs/products/databases/pagination", + "redirect": "/docs/products/databases/tablesdb/pagination" + }, + { + "link": "/docs/products/databases/permissions", + "redirect": "/docs/products/databases/tablesdb/permissions" + }, + { + "link": "/docs/products/databases/queries", + "redirect": "/docs/products/databases/tablesdb/queries" + }, + { + "link": "/docs/products/databases/quick-start", + "redirect": "/docs/products/databases/tablesdb/quick-start" + }, + { + "link": "/docs/products/databases/relationships", + "redirect": "/docs/products/databases/tablesdb/relationships" + }, + { + "link": "/docs/products/databases/rows", + "redirect": "/docs/products/databases/tablesdb/rows" + }, + { + "link": "/docs/products/databases/tables", + "redirect": "/docs/products/databases/tablesdb/tables" + }, + { + "link": "/docs/products/databases/timestamp-overrides", + "redirect": "/docs/products/databases/tablesdb/timestamp-overrides" + }, + { + "link": "/docs/products/databases/transactions", + "redirect": "/docs/products/databases/tablesdb/transactions" + }, + { + "link": "/docs/products/databases/type-generation", + "redirect": "/docs/products/databases/tablesdb/type-generation" } ] diff --git a/src/routes/docs/products/databases/(overview)/+layout.svelte b/src/routes/docs/products/databases/(overview)/+layout.svelte new file mode 100644 index 00000000000..ae270c5d9e4 --- /dev/null +++ b/src/routes/docs/products/databases/(overview)/+layout.svelte @@ -0,0 +1,64 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/(overview)/+page.markdoc b/src/routes/docs/products/databases/(overview)/+page.markdoc new file mode 100644 index 00000000000..635ec2fd952 --- /dev/null +++ b/src/routes/docs/products/databases/(overview)/+page.markdoc @@ -0,0 +1,40 @@ +--- +layout: article +title: Databases +description: Store and query your application data with Appwrite Databases. Choose between Appwrite databases with managed APIs and dedicated native databases with direct access. +--- + +Appwrite Databases provide performant and scalable storage for your application, business, and user data. Choose the database that fits your use case, from managed APIs with permissions and realtime to dedicated native engines you connect to directly. + +{% info title="Looking for file storage?" %} +Databases store data. If you need to store files like images, PDFs, or videos, use [Appwrite Storage](/docs/products/storage). +{% /info %} + +# Appwrite databases {% #appwrite-databases %} + +Managed databases with an Appwrite API on top, including permissions, indexes, queries, and realtime. Available on shared and dedicated infrastructure. + +{% cards %} +{% cards_item href="/docs/products/databases/tablesdb" title="TablesDB" %} +Structured, relational data with typed columns, rows, relationships, and indexes. +{% /cards_item %} +{% cards_item href="/docs/products/databases/documentsdb" title="DocumentsDB" %} +Schemaless document storage for flexible, JSON-style data. +{% /cards_item %} +{% cards_item href="/docs/products/databases/vectorsdb" title="VectorsDB" %} +Store embeddings and run similarity search to power AI features. +{% /cards_item %} +{% /cards %} + +# Native databases {% #native-databases %} + +Native database engines provisioned for your project with direct connection access and no abstraction layer. Available on dedicated infrastructure. + +{% cards %} +{% cards_item href="/docs/products/databases/postgresql" title="PostgreSQL" %} +A dedicated, native PostgreSQL database you connect to directly. +{% /cards_item %} +{% cards_item href="/docs/products/databases/mysql" title="MySQL" %} +A dedicated, native MySQL database you connect to directly. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/documentsdb/+layout.svelte b/src/routes/docs/products/databases/documentsdb/+layout.svelte new file mode 100644 index 00000000000..3d2666cc56d --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/+layout.svelte @@ -0,0 +1,29 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/documentsdb/+page.markdoc b/src/routes/docs/products/databases/documentsdb/+page.markdoc new file mode 100644 index 00000000000..9c70cfae243 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/+page.markdoc @@ -0,0 +1,11 @@ +--- +layout: article +title: DocumentsDB +description: Store schemaless documents in collections with Appwrite DocumentsDB for flexible, JSON-style application data. +--- + +Appwrite DocumentsDB stores schemaless documents in collections, giving you flexible, JSON-style data without a fixed schema. It shares the same permissions, indexes, queries, transactions, and realtime concepts as the rest of Appwrite Databases. + +{% info title="Documentation in progress" %} +Detailed DocumentsDB guides are on the way. For now, the concepts in [TablesDB](/docs/products/databases/tablesdb) are the closest reference. +{% /info %} diff --git a/src/routes/docs/products/databases/mysql/+layout.svelte b/src/routes/docs/products/databases/mysql/+layout.svelte new file mode 100644 index 00000000000..f6fe631eea8 --- /dev/null +++ b/src/routes/docs/products/databases/mysql/+layout.svelte @@ -0,0 +1,29 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/mysql/+page.markdoc b/src/routes/docs/products/databases/mysql/+page.markdoc new file mode 100644 index 00000000000..de42e29c3bd --- /dev/null +++ b/src/routes/docs/products/databases/mysql/+page.markdoc @@ -0,0 +1,11 @@ +--- +layout: article +title: MySQL +description: Run a dedicated, native MySQL database provisioned for your project and connect to it directly with standard MySQL clients. +--- + +Run a dedicated, native MySQL database provisioned for your project. Connect to it directly with standard MySQL clients and tools, with no Appwrite abstraction layer in between. Native databases are available on dedicated infrastructure. + +{% info title="Documentation in progress" %} +Detailed MySQL setup and connection guides are on the way. +{% /info %} diff --git a/src/routes/docs/products/databases/postgresql/+layout.svelte b/src/routes/docs/products/databases/postgresql/+layout.svelte new file mode 100644 index 00000000000..de7adff91d5 --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/+layout.svelte @@ -0,0 +1,29 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/postgresql/+page.markdoc b/src/routes/docs/products/databases/postgresql/+page.markdoc new file mode 100644 index 00000000000..4e7c251d8da --- /dev/null +++ b/src/routes/docs/products/databases/postgresql/+page.markdoc @@ -0,0 +1,11 @@ +--- +layout: article +title: PostgreSQL +description: Run a dedicated, native PostgreSQL database provisioned for your project and connect to it directly with standard PostgreSQL clients. +--- + +Run a dedicated, native PostgreSQL database provisioned for your project. Connect to it directly with standard PostgreSQL clients and tools, with no Appwrite abstraction layer in between. Native databases are available on dedicated infrastructure. + +{% info title="Documentation in progress" %} +Detailed PostgreSQL setup and connection guides are on the way. +{% /info %} diff --git a/src/routes/docs/products/databases/+layout.svelte b/src/routes/docs/products/databases/tablesdb/+layout.svelte similarity index 70% rename from src/routes/docs/products/databases/+layout.svelte rename to src/routes/docs/products/databases/tablesdb/+layout.svelte index 87f1ffb5a32..ca05c54b16a 100644 --- a/src/routes/docs/products/databases/+layout.svelte +++ b/src/routes/docs/products/databases/tablesdb/+layout.svelte @@ -10,7 +10,7 @@ let { children } = $props(); const parent: NavParent = { - href: '/docs', + href: '/docs/products/databases', label: 'Databases' }; @@ -20,11 +20,11 @@ items: [ { label: 'Overview', - href: '/docs/products/databases' + href: '/docs/products/databases/tablesdb' }, { label: 'Quick start', - href: '/docs/products/databases/quick-start' + href: '/docs/products/databases/tablesdb/quick-start' } ] }, @@ -33,45 +33,45 @@ items: [ { label: 'Databases', - href: '/docs/products/databases/databases' + href: '/docs/products/databases/tablesdb/databases' }, { label: 'Tables', - href: '/docs/products/databases/tables' + href: '/docs/products/databases/tablesdb/tables' }, { label: 'Rows', - href: '/docs/products/databases/rows' + href: '/docs/products/databases/tablesdb/rows' }, { label: 'Permissions', - href: '/docs/products/databases/permissions' + href: '/docs/products/databases/tablesdb/permissions' }, { label: 'Relationships', - href: '/docs/products/databases/relationships' + href: '/docs/products/databases/tablesdb/relationships' }, { label: 'Queries', - href: '/docs/products/databases/queries' + href: '/docs/products/databases/tablesdb/queries' }, { label: 'Order', - href: '/docs/products/databases/order' + href: '/docs/products/databases/tablesdb/order' }, { label: 'Operators', - href: '/docs/products/databases/operators', + href: '/docs/products/databases/tablesdb/operators', new: isNewUntil('31 Dec 2025') }, { label: 'Geo queries', - href: '/docs/products/databases/geo-queries', + href: '/docs/products/databases/tablesdb/geo-queries', new: isNewUntil('30 Sep 2025') }, { label: 'Backups', - href: '/docs/products/databases/backups' + href: '/docs/products/databases/tablesdb/backups' } ] }, @@ -80,50 +80,50 @@ items: [ { label: 'Pagination', - href: '/docs/products/databases/pagination' + href: '/docs/products/databases/tablesdb/pagination' }, { label: 'Transactions', - href: '/docs/products/databases/transactions', + href: '/docs/products/databases/tablesdb/transactions', new: isNewUntil('31 Oct 2025') }, { label: 'Type generation', - href: '/docs/products/databases/type-generation', + href: '/docs/products/databases/tablesdb/type-generation', new: isNewUntil('31 Jul 2025') }, { label: 'Offline sync', - href: '/docs/products/databases/offline' + href: '/docs/products/databases/tablesdb/offline' }, { label: 'Bulk operations', - href: '/docs/products/databases/bulk-operations', + href: '/docs/products/databases/tablesdb/bulk-operations', new: isNewUntil('31 Jul 2025') }, { label: 'Atomic numeric operations', - href: '/docs/products/databases/atomic-numeric-operations', + href: '/docs/products/databases/tablesdb/atomic-numeric-operations', new: isNewUntil('31 Jul 2025') }, { label: 'CSV imports', - href: '/docs/products/databases/csv-imports', + href: '/docs/products/databases/tablesdb/csv-imports', new: isNewUntil('31 Jul 2025') }, { label: 'CSV exports', - href: '/docs/products/databases/csv-exports', + href: '/docs/products/databases/tablesdb/csv-exports', new: isNewUntil('28 Feb 2026') }, { label: 'AI suggestions', - href: '/docs/products/databases/ai-suggestions', + href: '/docs/products/databases/tablesdb/ai-suggestions', new: isNewUntil('31 Dec 2025') }, { label: 'Timestamp overrides', - href: '/docs/products/databases/timestamp-overrides' + href: '/docs/products/databases/tablesdb/timestamp-overrides' } ] }, @@ -145,16 +145,16 @@ const legacyUrl = $derived( page.url.pathname - .replace('/products/databases', '/products/databases/legacy') - .replace('rows', 'documents') - .replace('tables', 'collections') + .replace('/tablesdb/', '/tablesdb/legacy/') + .replace(/\/rows(\/|$)/, '/documents$1') + .replace(/\/tables(\/|$)/, '/collections$1') ); const hideSubtitleRoutes = ['offline', 'backups', 'csv-imports', 'csv-exports']; const shouldShowSubtitle = $derived( !hideSubtitleRoutes.some((segment) => page.route.id?.includes(segment)) && - !page.url.pathname.endsWith('products/databases') + !page.url.pathname.endsWith('products/databases/tablesdb') ); const headerSectionInfoAlert = writable(null); diff --git a/src/routes/docs/products/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/+page.markdoc similarity index 70% rename from src/routes/docs/products/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/+page.markdoc index 9217bde1e68..e9f01089844 100644 --- a/src/routes/docs/products/databases/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/+page.markdoc @@ -1,7 +1,7 @@ --- layout: article -title: Databases -description: Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data. +title: TablesDB +description: Store and query structured data with Appwrite TablesDB. Tables provide performant and scalable storage for your application, business, and user data. --- Appwrite Databases let you store and query structured data. @@ -14,6 +14,6 @@ Databases store data, if you need to store files like images, PDFs or videos, us You can organize data into databases, tables, and rows. You can also paginate, order, and query rows. For complex business logic, Appwrite supports relationships to help you model your data. -{% arrow_link href="/docs/products/databases/quick-start" %} +{% arrow_link href="/docs/products/databases/tablesdb/quick-start" %} Quick start {% /arrow_link %} \ No newline at end of file diff --git a/src/routes/docs/products/databases/ai-suggestions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/ai-suggestions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/ai-suggestions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/ai-suggestions/+page.markdoc diff --git a/src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/atomic-numeric-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/atomic-numeric-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/backups/+page.markdoc b/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/backups/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/backups/+page.markdoc diff --git a/src/routes/docs/products/databases/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/bulk-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/csv-exports/+page.markdoc b/src/routes/docs/products/databases/tablesdb/csv-exports/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/csv-exports/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/csv-exports/+page.markdoc diff --git a/src/routes/docs/products/databases/csv-imports/+page.markdoc b/src/routes/docs/products/databases/tablesdb/csv-imports/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/csv-imports/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/csv-imports/+page.markdoc diff --git a/src/routes/docs/products/databases/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/databases/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/databases/+page.markdoc diff --git a/src/routes/docs/products/databases/geo-queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/geo-queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/geo-queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/geo-queries/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/+layout.svelte b/src/routes/docs/products/databases/tablesdb/legacy/+layout.svelte similarity index 100% rename from src/routes/docs/products/databases/legacy/+layout.svelte rename to src/routes/docs/products/databases/tablesdb/legacy/+layout.svelte diff --git a/src/routes/docs/products/databases/legacy/+page.ts b/src/routes/docs/products/databases/tablesdb/legacy/+page.ts similarity index 100% rename from src/routes/docs/products/databases/legacy/+page.ts rename to src/routes/docs/products/databases/tablesdb/legacy/+page.ts diff --git a/src/routes/docs/products/databases/legacy/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/atomic-numeric-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/atomic-numeric-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/atomic-numeric-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/bulk-operations/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/bulk-operations/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/bulk-operations/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/collections/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/collections/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/collections/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/collections/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/databases/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/databases/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/databases/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/databases/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/documents/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/documents/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/documents/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/documents/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/order/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/order/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/order/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/order/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/pagination/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/pagination/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/pagination/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/pagination/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/permissions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/permissions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/permissions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/permissions/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/queries/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/quick-start/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/quick-start/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/quick-start/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/quick-start/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/relationships/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/relationships/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/relationships/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/relationships/+page.markdoc diff --git a/src/routes/docs/products/databases/legacy/type-generation/+page.markdoc b/src/routes/docs/products/databases/tablesdb/legacy/type-generation/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/legacy/type-generation/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/legacy/type-generation/+page.markdoc diff --git a/src/routes/docs/products/databases/offline/+page.markdoc b/src/routes/docs/products/databases/tablesdb/offline/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/offline/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/offline/+page.markdoc diff --git a/src/routes/docs/products/databases/operators/+page.markdoc b/src/routes/docs/products/databases/tablesdb/operators/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/operators/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/operators/+page.markdoc diff --git a/src/routes/docs/products/databases/order/+page.markdoc b/src/routes/docs/products/databases/tablesdb/order/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/order/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/order/+page.markdoc diff --git a/src/routes/docs/products/databases/pagination/+page.markdoc b/src/routes/docs/products/databases/tablesdb/pagination/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/pagination/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/pagination/+page.markdoc diff --git a/src/routes/docs/products/databases/permissions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/permissions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/permissions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/permissions/+page.markdoc diff --git a/src/routes/docs/products/databases/queries/+page.markdoc b/src/routes/docs/products/databases/tablesdb/queries/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/queries/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/queries/+page.markdoc diff --git a/src/routes/docs/products/databases/quick-start/+page.markdoc b/src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/quick-start/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/quick-start/+page.markdoc diff --git a/src/routes/docs/products/databases/relationships/+page.markdoc b/src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/relationships/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/relationships/+page.markdoc diff --git a/src/routes/docs/products/databases/rows/+page.markdoc b/src/routes/docs/products/databases/tablesdb/rows/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/rows/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/rows/+page.markdoc diff --git a/src/routes/docs/products/databases/tables/+page.markdoc b/src/routes/docs/products/databases/tablesdb/tables/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/tables/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/tables/+page.markdoc diff --git a/src/routes/docs/products/databases/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/timestamp-overrides/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/timestamp-overrides/+page.markdoc diff --git a/src/routes/docs/products/databases/transactions/+page.markdoc b/src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/transactions/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/transactions/+page.markdoc diff --git a/src/routes/docs/products/databases/type-generation/+page.markdoc b/src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc similarity index 100% rename from src/routes/docs/products/databases/type-generation/+page.markdoc rename to src/routes/docs/products/databases/tablesdb/type-generation/+page.markdoc diff --git a/src/routes/docs/products/databases/vectorsdb/+layout.svelte b/src/routes/docs/products/databases/vectorsdb/+layout.svelte new file mode 100644 index 00000000000..bd502da28b6 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/+layout.svelte @@ -0,0 +1,29 @@ + + + + + + {@render children()} + diff --git a/src/routes/docs/products/databases/vectorsdb/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/+page.markdoc new file mode 100644 index 00000000000..f0391bc33d3 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/+page.markdoc @@ -0,0 +1,11 @@ +--- +layout: article +title: VectorsDB +description: Store vector embeddings and run similarity search with Appwrite VectorsDB to power semantic search, recommendations, and other AI features. +--- + +Appwrite VectorsDB stores vector embeddings and runs similarity search, so you can build semantic search, recommendations, and other AI features. It also exposes an endpoint to generate text embeddings, and shares the permissions, indexes, transactions, and realtime concepts used across Appwrite Databases. + +{% info title="Documentation in progress" %} +Detailed VectorsDB guides are on the way. For now, the concepts in [TablesDB](/docs/products/databases/tablesdb) are the closest reference. +{% /info %} From dcdb713fe1f5b386a098873c7ceb7cb22eccde39 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Mon, 29 Jun 2026 20:12:49 +0530 Subject: [PATCH 2/5] Add DocumentsDB documentation section Add DocumentsDB docs (concepts and journeys) under databases/documentsdb, move TablesDB within the databases section with redirects from the previous URLs, and align the backups and bulk operations pages. --- .optimize-cache.json | 8 + src/redirects.json | 4 + .../databases/documentsdb/+layout.svelte | 64 +- .../databases/documentsdb/+page.markdoc | 15 +- .../atomic-numeric-operations/+page.markdoc | 867 ++++++++ .../documentsdb/backups/+page.markdoc | 89 + .../documentsdb/bulk-operations/+page.markdoc | 567 +++++ .../documentsdb/collections/+page.markdoc | 526 +++++ .../documentsdb/databases/+page.markdoc | 218 ++ .../documentsdb/documents/+page.markdoc | 1189 +++++++++++ .../databases/documentsdb/order/+page.markdoc | 336 +++ .../documentsdb/pagination/+page.markdoc | 540 +++++ .../documentsdb/permissions/+page.markdoc | 35 + .../documentsdb/queries/+page.markdoc | 1855 +++++++++++++++++ .../documentsdb/quick-start/+page.markdoc | 243 +++ .../timestamp-overrides/+page.markdoc | 1266 +++++++++++ .../documentsdb/transactions/+page.markdoc | 1274 +++++++++++ .../databases/tablesdb/backups/+page.markdoc | 14 +- .../tablesdb/bulk-operations/+page.markdoc | 42 +- .../databases/documentsdb/backup-policy.avif | Bin 0 -> 10913 bytes .../databases/documentsdb/backups-tab.avif | Bin 0 -> 26847 bytes .../documentsdb/create-database.avif | Bin 0 -> 31320 bytes .../documentsdb/dark/backup-policy.avif | Bin 0 -> 11350 bytes .../documentsdb/dark/backups-tab.avif | Bin 0 -> 28535 bytes .../documentsdb/dark/create-database.avif | Bin 0 -> 34142 bytes .../documentsdb/dark/manual-backup.avif | Bin 0 -> 10490 bytes .../databases/documentsdb/manual-backup.avif | Bin 0 -> 10120 bytes 27 files changed, 9119 insertions(+), 33 deletions(-) create mode 100644 src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/backups/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/collections/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/databases/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/documents/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/order/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/queries/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc create mode 100644 static/images/docs/databases/documentsdb/backup-policy.avif create mode 100644 static/images/docs/databases/documentsdb/backups-tab.avif create mode 100644 static/images/docs/databases/documentsdb/create-database.avif create mode 100644 static/images/docs/databases/documentsdb/dark/backup-policy.avif create mode 100644 static/images/docs/databases/documentsdb/dark/backups-tab.avif create mode 100644 static/images/docs/databases/documentsdb/dark/create-database.avif create mode 100644 static/images/docs/databases/documentsdb/dark/manual-backup.avif create mode 100644 static/images/docs/databases/documentsdb/manual-backup.avif diff --git a/.optimize-cache.json b/.optimize-cache.json index f926af4a597..babde58ee40 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -1455,6 +1455,14 @@ "static/images/docs/databases/dark/scale-custom-policies.png": "0013e987e9b8b917cb9be4c28048f851f2d188f3bfa5ff17a11a7ac7cf9c3ade", "static/images/docs/databases/dark/scale-policies.png": "9ca9523f2e20e9aa993f0ad933cdf1dcd12adbaa35ecb2a0b8b3d2fd65877e1f", "static/images/docs/databases/databases.png": "0278a6bc5672684653f74bcf3c0d022fdd82a08d7a7fd438b28e21bd81b5e5d5", + "static/images/docs/databases/documentsdb/backup-policy.png": "913cacbe957847f8a71efa2d2e704754e52cf9c276dce8978f6c473a9ba6595b", + "static/images/docs/databases/documentsdb/backups-tab.png": "231163729496618d26863c6fa7ad88069875f78a3c320033d4bddc3c2f8da376", + "static/images/docs/databases/documentsdb/create-database.png": "49ba017dcec96631a87f924b13b6b93478add7b516cc62947e87230b5a0d5de1", + "static/images/docs/databases/documentsdb/dark/backup-policy.png": "a29fad13b50374b3de55824f1332fa4be9c2c7a0c4c63f550d03306ffd546551", + "static/images/docs/databases/documentsdb/dark/backups-tab.png": "dedb6cfcdd9217e814e58cb14db3d8309f53e3071cb9f12edd9a0d5f9bad1d7c", + "static/images/docs/databases/documentsdb/dark/create-database.png": "fe4c1340226d31fb3fa1d655563c9c8fdd150b4d0aac3e217fdd2af40718c272", + "static/images/docs/databases/documentsdb/dark/manual-backup.png": "880f077a3a598c35c9939a1cfcab4bc82a2a88e730be1b4157359657f0a47dcd", + "static/images/docs/databases/documentsdb/manual-backup.png": "0f3432e170b68503b00fac4cf65ed7c5e5a04f34031b5011fe1c80f1eda2bf1a", "static/images/docs/databases/manual-backup.png": "a5854158c5350e333ae14b699db4626c5c26e17b529f3acd137cfae8cb08e9c4", "static/images/docs/databases/pro-policy.png": "b0d35de73c334614dc3f644459dea2bbc56d0da3157db985f24897597ae26302", "static/images/docs/databases/restore.png": "97611c54c654631d2a86a8453a2ea3603c32e85888c065668e945eeeeb894df0", diff --git a/src/redirects.json b/src/redirects.json index 65ebd11afc0..bc286d8f2d0 100644 --- a/src/redirects.json +++ b/src/redirects.json @@ -948,6 +948,10 @@ "link": "/docs/products/databases/geo-queries", "redirect": "/docs/products/databases/tablesdb/geo-queries" }, + { + "link": "/docs/products/databases/legacy", + "redirect": "/docs/products/databases/tablesdb/legacy" + }, { "link": "/docs/products/databases/legacy/atomic-numeric-operations", "redirect": "/docs/products/databases/tablesdb/legacy/atomic-numeric-operations" diff --git a/src/routes/docs/products/databases/documentsdb/+layout.svelte b/src/routes/docs/products/databases/documentsdb/+layout.svelte index 3d2666cc56d..3f63cab9b0a 100644 --- a/src/routes/docs/products/databases/documentsdb/+layout.svelte +++ b/src/routes/docs/products/databases/documentsdb/+layout.svelte @@ -6,7 +6,7 @@ const parent: NavParent = { href: '/docs/products/databases', - label: 'Databases' + label: 'DocumentsDB' }; const navigation: NavTree = [ @@ -16,6 +16,68 @@ { label: 'Overview', href: '/docs/products/databases/documentsdb' + }, + { + label: 'Quick start', + href: '/docs/products/databases/documentsdb/quick-start' + } + ] + }, + { + label: 'Concepts', + items: [ + { + label: 'Databases', + href: '/docs/products/databases/documentsdb/databases' + }, + { + label: 'Collections', + href: '/docs/products/databases/documentsdb/collections' + }, + { + label: 'Documents', + href: '/docs/products/databases/documentsdb/documents' + }, + { + label: 'Permissions', + href: '/docs/products/databases/documentsdb/permissions' + }, + { + label: 'Queries', + href: '/docs/products/databases/documentsdb/queries' + }, + { + label: 'Order', + href: '/docs/products/databases/documentsdb/order' + }, + { + label: 'Backups', + href: '/docs/products/databases/documentsdb/backups' + } + ] + }, + { + label: 'Journeys', + items: [ + { + label: 'Pagination', + href: '/docs/products/databases/documentsdb/pagination' + }, + { + label: 'Transactions', + href: '/docs/products/databases/documentsdb/transactions' + }, + { + label: 'Bulk operations', + href: '/docs/products/databases/documentsdb/bulk-operations' + }, + { + label: 'Atomic numeric operations', + href: '/docs/products/databases/documentsdb/atomic-numeric-operations' + }, + { + label: 'Timestamp overrides', + href: '/docs/products/databases/documentsdb/timestamp-overrides' } ] } diff --git a/src/routes/docs/products/databases/documentsdb/+page.markdoc b/src/routes/docs/products/databases/documentsdb/+page.markdoc index 9c70cfae243..cb74d847aff 100644 --- a/src/routes/docs/products/databases/documentsdb/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/+page.markdoc @@ -1,11 +1,18 @@ --- layout: article title: DocumentsDB -description: Store schemaless documents in collections with Appwrite DocumentsDB for flexible, JSON-style application data. +description: Store and query schemaless documents with Appwrite DocumentsDB. Collections give you flexible, JSON-style storage for your application, business, and user data. --- -Appwrite DocumentsDB stores schemaless documents in collections, giving you flexible, JSON-style data without a fixed schema. It shares the same permissions, indexes, queries, transactions, and realtime concepts as the rest of Appwrite Databases. +Appwrite DocumentsDB lets you store and query schemaless documents. +Collections hold documents as flexible JSON, so you can add fields as your data evolves without defining a schema up front. -{% info title="Documentation in progress" %} -Detailed DocumentsDB guides are on the way. For now, the concepts in [TablesDB](/docs/products/databases/tablesdb) are the closest reference. +{% info title="Looking for file storage?" %} +Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). {% /info %} + +You can organize data into databases, collections, and documents. You can also paginate, order, and query documents. + +{% arrow_link href="/docs/products/databases/documentsdb/quick-start" %} +Quick start +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc new file mode 100644 index 00000000000..9dd095b1a31 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/atomic-numeric-operations/+page.markdoc @@ -0,0 +1,867 @@ +--- +layout: article +title: Atomic numeric operations +description: Safely increment and decrement numeric fields without race conditions. Perfect for counters, quotas, inventory, and usage metrics in high-concurrency applications. +--- + +Atomic numeric operations allow you to safely increase or decrease numeric fields without fetching the full document. This eliminates race conditions and reduces bandwidth usage when updating any numeric values that need to be modified atomically, such as counters, scores, balances, and other fast-moving numeric data. + +These operations work on numeric document fields, whether the value is an integer or a floating-point number. + +# How atomic operations work {% #how-atomic-operations-work %} + +Instead of the traditional read-modify-write pattern, atomic numeric operations use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. + +**Traditional approach:** +1. Fetch document → `{ likes: 42 }` +2. Update client-side → `likes: 43` +3. Write back → `{ likes: 43 }` + +**Atomic approach:** +1. Call `incrementDocumentAttribute()` with the attribute key and the value to increment by +2. Server applies atomically → `likes: 43` + +# When to use atomic operations {% #when-to-use-atomic-operations %} + +Atomic numeric operations work well for: + +- **Social features**: Likes, follows, comment counts +- **Usage metering**: API credits, storage quotas, request limits +- **Game state**: Scores, lives, currency, experience points +- **E-commerce**: Stock counts, inventory levels +- **Workflow tracking**: Retry counts, progress indicators +- **Rate limiting**: Request counters, usage tracking + +# Perform atomic operations {% #perform-atomic-operations %} + +Use the `incrementDocumentAttribute` and `decrementDocumentAttribute` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control. + +## Increment a field {% #increment-field %} + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + +let documents_db = DocumentsDB::new(&client); + +let result = documents_db.increment_document_attribute( + "", + "", + "", + "likes", // attribute + Some(1.0), // value + None, // max + None, // transaction_id +).await?; +``` +```graphql +mutation { + documentsDBIncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +## Decrement a field {% #decrement-field %} + +Use the `decrementDocumentAttribute` method to decrease numeric fields: + +{% multicode %} +```client-web +import { Client, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject(''); // Your project ID + +const documentsDB = new DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +final documentsDB = DocumentsDB(client); + +final document = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 5 +); +``` +```client-apple +import Appwrite +import AppwriteModels + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 +) +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +val documentsDB = DocumentsDB(client) + +val document = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 5 +) +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 5 // value +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 5 # value +) +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + +let documents_db = DocumentsDB::new(&client); + +let result = documents_db.decrement_document_attribute( + "", + "", + "", + "credits", // attribute + Some(5.0), // value + None, // min + None, // transaction_id +).await?; +``` +```graphql +mutation { + documentsDBDecrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 5 + ) { + _id + _collectionId + _databaseId + _createdAt + _updatedAt + _permissions + data + } +} +``` +{% /multicode %} + +# Set constraints and bounds {% #set-constraints-and-bounds %} + +You can set minimum and maximum bounds for individual operations to prevent invalid values. Use the optional `min` and `max` parameters to ensure the final value stays within acceptable limits. If an operation would move the value past the bound, the request is rejected. + +## Example with constraints {% #example-with-constraints %} + +{% multicode %} +```client-web +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```client-flutter +// Increment with maximum constraint +final document = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 100, + max: 1000 +); + +// Decrement with minimum constraint +final document2 = await documentsDB.decrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', + value: 50, + min: 0 +); +``` +```client-apple +// Increment with maximum constraint +let document = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 100, + max: 1000 +) + +// Decrement with minimum constraint +let document2 = try await documentsDB.decrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "credits", + value: 50, + min: 0 +) +``` +```client-android-kotlin +// Increment with maximum constraint +val document = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 100, + max = 1000 +) + +// Decrement with minimum constraint +val document2 = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "credits", + value = 50, + min = 0 +) +``` +```server-nodejs +// Increment with maximum constraint +const result = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 100, // value + max: 1000 // max (optional) +}); + +// Decrement with minimum constraint +const result2 = await documentsDB.decrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'credits', // attribute + value: 50, // value + min: 0 // min (optional) +}); +``` +```server-python +# Increment with maximum constraint +result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 100, # value + max = 1000 # max (optional) +) + +# Decrement with minimum constraint +result2 = documents_db.decrement_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'credits', # attribute + value = 50, # value + min = 0 # min (optional) +) +``` +```rust +// Increment with maximum constraint +let result = documents_db.increment_document_attribute( + "", + "", + "", + "credits", // attribute + Some(100.0), // value + Some(1000.0), // max (optional) + None, // transaction_id +).await?; + +// Decrement with minimum constraint +let result2 = documents_db.decrement_document_attribute( + "", + "", + "", + "credits", // attribute + Some(50.0), // value + Some(0.0), // min (optional) + None, // transaction_id +).await?; +``` +{% /multicode %} + +# Follow best practices {% #follow-best-practices %} + +## Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %} + +Atomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously. + +## Combine with regular updates {% #combine-with-regular-updates %} + +For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls: + +{% multicode %} +```client-web +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +}); +``` +```client-flutter +// First, increment the likes atomically +final likeResult = await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1 +); + +// Then, update other fields +final updateResult = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { + 'lastLikedBy': userId, + 'lastLikedAt': DateTime.now().toIso8601String() + } +); +``` +```client-apple +// First, increment the likes atomically +let likeResult = try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1 +) + +// Then, update other fields +let updateResult = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "lastLikedBy": userId, + "lastLikedAt": ISO8601DateFormatter().string(from: Date()) + ] +) +``` +```client-android-kotlin +// First, increment the likes atomically +val likeResult = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1 +) + +// Then, update other fields +val updateResult = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( + "lastLikedBy" to userId, + "lastLikedAt" to Instant.now().toString() + ) +) +``` +```server-nodejs +// First, increment the likes atomically +const likeResult = await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', // attribute + value: 1 // value +}); + +// Then, update other fields +const updateResult = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + lastLikedBy: userId, + lastLikedAt: new Date().toISOString() + } +}); +``` +```server-python +# First, increment the likes atomically +like_result = documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', # attribute + value = 1 # value +) + +# Then, update other fields +update_result = documents_db.update_document( + database_id = '', + collection_id = '', + document_id = '', + data = { + 'lastLikedBy': user_id, + 'lastLikedAt': datetime.now().isoformat() + } +) +``` +```rust +use serde_json::json; + +// First, increment the likes atomically +let like_result = documents_db.increment_document_attribute( + "", + "", + "", + "likes", // attribute + Some(1.0), // value + None, // max + None, // transaction_id +).await?; + +// Then, update other fields +let update_result = documents_db.update_document( + "", + "", + "", + Some(json!({ + "lastLikedBy": user_id, + "lastLikedAt": chrono::Utc::now().to_rfc3339() + })), + None, // permissions + None, // transaction_id +).await?; +``` +{% /multicode %} + +# Use transactions {% #use-transactions %} + +Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit. + +{% multicode %} +```client-web +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```client-flutter +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```client-apple +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```client-android-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```client-android-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-nodejs +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-deno +await documentsDB.incrementDocumentAttribute({ + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +}); +``` +```server-python +documents_db.increment_document_attribute( + database_id = '', + collection_id = '', + document_id = '', + attribute = 'likes', + value = 1, + transaction_id = '' +) +``` +```rust +documents_db.increment_document_attribute( + "", + "", + "", + "likes", + Some(1.0), + None, // max + Some(""), // transaction_id +).await?; +``` +```server-php +$documentsDB->incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-ruby +documents_db.increment_document_attribute( + database_id: '', + collection_id: '', + document_id: '', + attribute: 'likes', + value: 1, + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.IncrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +); +``` +```server-dart +await documentsDB.incrementDocumentAttribute( + databaseId: '', + collectionId: '', + documentId: '', + attribute: 'likes', + value: 1, + transactionId: '' +); +``` +```server-swift +try await documentsDB.incrementDocumentAttribute( + databaseId: "", + collectionId: "", + documentId: "", + attribute: "likes", + value: 1, + transactionId: "" +) +``` +```server-kotlin +documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "likes", + value = 1, + transactionId = "" +) +``` +```server-java +documentsDB.incrementDocumentAttribute( + "", + "", + "", + "likes", + 1, + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +{% /multicode %} + +## Explore related features + +- [Bulk operations](/docs/products/databases/documentsdb/bulk-operations) - Update multiple documents at once +- [Permissions](/docs/products/databases/documentsdb/permissions) - Control access to documents +- [Queries](/docs/products/databases/documentsdb/queries) - Find documents to update diff --git a/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc new file mode 100644 index 00000000000..a5930313c71 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc @@ -0,0 +1,89 @@ +--- +layout: article +title: Backups +description: Learn how to back up and restore your DocumentsDB databases on Appwrite Cloud, ensuring data security and seamless recovery. +--- + +Appwrite Backups enable seamless, **encrypted** database backups on Cloud. +All backups are **hot** backups, ensuring zero downtime and fast recovery. + +{% info title="Backups are available on Appwrite Cloud for all Pro and Enterprise customers." %} +{% /info %} + +You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. + +{% only_dark %} +![Backups tab](/images/docs/databases/documentsdb/dark/backups-tab.avif) +{% /only_dark %} +{% only_light %} +![Backups tab](/images/docs/databases/documentsdb/backups-tab.avif) +{% /only_light %} + +# Backup policies {% #backup-policies %} + +Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one. + +{% only_dark %} +![Create backup policy](/images/docs/databases/documentsdb/dark/backup-policy.avif) +{% /only_dark %} +{% only_light %} +![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif) +{% /only_light %} + +The available options depend on your plan: + +- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days. +- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained. + +Click **Create** to save the policy. Your database is now set up for automated backups. + +# Manual backups {% #manual-backups %} + +You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**. + +{% only_dark %} +![Manual backup](/images/docs/databases/documentsdb/dark/manual-backup.avif) +{% /only_dark %} +{% only_light %} +![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif) +{% /only_light %} + +Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list. + +# Restoring backups {% #restoring-backups %} + +To restore a database, you need an existing backup. + +1. Open your database's **Backups** tab. +2. In the backups list, open the **Actions** menu for the backup you want to restore. +3. Click **Restore**. +4. Enter a name for the new database and an optional database ID. +5. Click **Restore**. + +Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched. + +# Backup security & performance {% #backup-security-and-performance %} + +All backups created with Appwrite are: + +1. **Encrypted**: + All backups are securely encrypted to ensure your data remains protected at all times. + +2. **Remotely stored**: + Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable. + +3. **Hot backups**: + Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services. + +# Best practices {% #best-practices %} + +To ensure your backups are robust and effective, consider the following best practices: + +1. **Schedule regular backups**: + Add backup policies based on the frequency of database changes. Daily backups are often sufficient for most use cases. + +2. **Retain critical backups longer**: + Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed. + +3. **Optimize backup policies based on data sensitivity**: + Tailor your backup frequency and retention settings according to the sensitivity and importance of the data. diff --git a/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc new file mode 100644 index 00000000000..6522e3bde64 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc @@ -0,0 +1,567 @@ +--- +layout: article +title: Bulk operations +description: Perform bulk operations on documents within your collections for efficient data handling. +--- + +Appwrite Databases supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. + +Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. + +For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. + +{% info title="Important notes" %} +Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type. +{% /info %} + +# Atomic behavior {% #atomic-behavior %} + +Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. + +This atomicity ensures: +- **Data consistency**: Your database remains in a consistent state even if some operations would fail. +- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. +- **Simplified error handling**: You only need to handle complete success or complete failure scenarios. + +For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. + +# Plan limits {% #plan-limits %} + +Bulk operations have different limits based on your Appwrite plan: + +| Plan | Documents per request | +|------|----------------------| +| Free | 100 | +| Pro | 1,000 | + +These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan. + +# Create documents {% #create-documents %} + +You can create multiple documents in a single request using the `createDocuments` method. + +{% info title="Custom timestamps" %} +When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. +{% /info %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + $id: sdk.ID.unique(), + name: 'Document 1' + }, + { + $id: sdk.ID.unique(), + name: 'Document 2' + } + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.create_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': ID.unique(), + 'name': 'Document 1' + }, + { + '$id': ID.unique(), + 'name': 'Document 2' + } + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "Document 1" + }), + json!({ + "$id": ID::unique(), + "name": "Document 2" + }), + ], + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Update documents {% #update-documents %} + +{% info title="Permissions required" %} +You must grant **update** permissions to users at the **collection level** before users can update documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can update multiple documents in a single request using the `updateDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocuments({ + databaseId: '', + collectionId: '', + data: { + status: 'published' + }, + queries: [ + sdk.Query.equal('status', 'draft') + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.update_documents( + database_id = '', + collection_id = '', + data = { + 'status': 'published' + }, + queries = [ + Query.equal('status', 'draft') + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_documents( + "", + "", + Some(json!({ + "status": "published" + })), + Some(vec![ + Query::equal("status", "draft").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +{% info title="Permissions required" %} +You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can upsert multiple documents in a single request using the `upsertDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + $id: sdk.ID.unique(), + name: 'New Document 1' + }, + { + $id: 'document-id-2', // Existing document ID + name: 'New Document 2' + } + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.upsert_documents( + database_id = '', + collection_id = '', + documents = [ + { + '$id': ID.unique(), + 'name': 'New Document 1' + }, + { + '$id': 'document-id-2', # Existing document ID + 'name': 'New Document 2' + } + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "New Document 1" + }), + json!({ + "$id": "document-id-2", // Existing document ID + "name": "New Document 2" + }), + ], + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Delete documents {% #delete-documents %} + +{% info title="Permissions required" %} +You must grant **delete** permissions to users at the **collection level** before users can delete documents. +[Learn more about permissions](/docs/products/databases/documentsdb/permissions) +{% /info %} + +You can delete multiple documents in a single request using the `deleteDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('status', 'archived') + ] +}); +``` + +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +result = documents_db.delete_documents( + database_id = '', + collection_id = '', + queries = [ + Query.equal('status', 'archived') + ] +) +``` + +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.delete_documents( + "", + "", + Some(vec![ + Query::equal("status", "archived").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +{% info title="Queries for deletion" %} + +When deleting documents, you must specify queries to filter which documents to delete. +If no queries are provided, all documents in the collection will be deleted. +[Learn more about queries](/docs/products/databases/documentsdb/queries). + +{% /info %} + +# Use transactions {% #use-transactions %} + +All bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/documentsdb/transactions). + +{% multicode %} +```server-nodejs +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-python +documents_db.create_documents( + database_id = '', + collection_id = '', + documents = [ + { '$id': ID.unique(), 'name': 'One' }, + { '$id': ID.unique(), 'name': 'Two' } + ], + transaction_id = '' +) +``` +```server-deno +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { $id: sdk.ID.unique(), name: 'One' }, + { $id: sdk.ID.unique(), name: 'Two' } + ], + transactionId: '' +}); +``` +```server-php +$documentsDB->createDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ '$id' => ID::unique(), 'name' => 'One' ], + [ '$id' => ID::unique(), 'name' => 'Two' ] + ], + transactionId: '' +); +``` +```server-ruby +documents_db.create_documents( + database_id: '', + collection_id: '', + documents: [ + { '$id' => ID.unique(), 'name' => 'One' }, + { '$id' => ID.unique(), 'name' => 'Two' } + ], + transaction_id: '' +) +``` +```server-dotnet +await documentsDB.CreateDocuments( + databaseId: "", + collectionId: "", + documents: new List> + { + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "One" + }, + new Dictionary + { + ["$id"] = ID.Unique(), + ["name"] = "Two" + } + }, + transactionId: "" +); +``` +```server-dart +await documentsDB.createDocuments( + databaseId: '', + collectionId: '', + documents: [ + { '\$id': ID.unique(), 'name': 'One' }, + { '\$id': ID.unique(), 'name': 'Two' } + ], + transactionId: '' +); +``` +```server-swift +try await documentsDB.createDocuments( + databaseId: "", + collectionId: "", + documents: [ + ["$id": ID.unique(), "name": "One"], + ["$id": ID.unique(), "name": "Two"] + ], + transactionId: "" +) +``` +```server-kotlin +documentsDB.createDocuments( + databaseId = "", + collectionId = "", + documents = listOf( + mapOf("\$id" to ID.unique(), "name" to "One"), + mapOf("\$id" to ID.unique(), "name" to "Two") + ), + transactionId = "" +) +``` +```server-java +documentsDB.createDocuments( + "", + "", + Arrays.asList( + Map.of( + "$id", ID.unique(), + "name", "One" + ), + Map.of( + "$id", ID.unique(), + "name", "Two" + ) + ), + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```rust +let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "name": "One" + }), + json!({ + "$id": ID::unique(), + "name": "Two" + }), + ], + Some(""), +).await?; +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc new file mode 100644 index 00000000000..09ec098633d --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/collections/+page.markdoc @@ -0,0 +1,526 @@ +--- +layout: article +title: Collections +description: Organize documents with Appwrite DocumentsDB collections. Learn how to create collections, configure permissions, and add indexes for fast queries. +--- +Appwrite uses collections as containers of documents. +Collections are schemaless, so documents in the same collection can hold different fields. You shape data in your application instead of defining columns up front. + +# Create collection {% #create-collection %} +You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). +{% tabs %} + +{% tabsitem #console title="Console" %} +Head to the **Databases** page, open a [database](/docs/products/databases/documentsdb/databases), and click **Create collection**. + +{% /tabsitem %} + +{% tabsitem #server-sdk title="Server SDK" %} +You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createCollection({ + databaseId: '', + collectionId: '', + name: '', + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false // optional +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createCollection( + databaseId: '', + collectionId: '', + name: '', + permissions: [Permission::read(Role::any())], // optional + documentSecurity: false // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_collection( + database_id = '', + collection_id = '', + name = '', + permissions = [Permission.read(Role.any())], # optional + document_security = False # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_collection( + database_id: '', + collection_id: '', + name: '', + permissions: [Permission.read(Role.any())], # optional + document_security: false # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Collection result = await documentsDB.CreateCollection( + databaseId: "", + collectionId: "", + name: "", + permissions: new List { Permission.Read(Role.Any()) }, // optional + documentSecurity: false // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Collection result = await documentsDB.createCollection( + databaseId: '', + collectionId: '', + name: '', + permissions: [Permission.read(Role.any())], // (optional) + documentSecurity: false, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createCollection( + databaseId = "", + collectionId = "", + name = "", + permissions = listOf(Permission.read(Role.any())), // optional + documentSecurity = false, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createCollection( + "", + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let collection = try await documentsDB.createCollection( + databaseId: "", + collectionId: "", + name: "", + permissions: [Permission.read(Role.any())], // optional + documentSecurity: false // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_collection( + "", + "", + "", + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + Some(false), // documentSecurity (optional) + None, // enabled (optional) + None, // attributes (optional) + None, // indexes (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} +{% /tabsitem %} +{% /tabs %} + +# Permissions {% #permissions %} +Appwrite uses permissions to control data access. +For security, only users that are granted permissions can access a resource. + +By default, Appwrite doesn't grant permissions to any users when a new collection is created. +This means users can't create documents or read, update, and delete existing documents until you grant access. + +Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document. + +[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions). + +# Indexes {% #indexes %} +Databases use indexes to quickly locate data without scanning every document. +To ensure the best performance, Appwrite recommends an index for every field you query. +If you plan to query multiple fields in a single query, creating an index with **all** queried fields will yield optimal performance. + +The following indexes are currently supported: + +| Type | Description | +|------------|--------------------------------------------------------------------------------------------------------------| +| `key` | Plain index to allow queries. | +| `unique` | Unique index to disallow duplicates. | +| `fulltext` | For searching within text fields. Required for the [search query method](/docs/products/databases/documentsdb/queries). | + +You can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createIndex({ + databaseId: '', + collectionId: '', + key: 'title_index', + type: sdk.DocumentsDBIndexType.Key, + attributes: ['title'] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createIndex({ + databaseId: '', + collectionId: '', + key: 'title_index', + type: sdk.DocumentsDBIndexType.Key, + attributes: ['title'] +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createIndex( + databaseId: '', + collectionId: '', + key: 'title_index', + type: DocumentsDBIndexType::KEY(), + attributes: ['title'] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.enums import DocumentsDBIndexType + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_index( + database_id = '', + collection_id = '', + key = 'title_index', + type = DocumentsDBIndexType.KEY, + attributes = ['title'] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_index( + database_id: '', + collection_id: '', + key: 'title_index', + type: DocumentsDBIndexType::KEY, + attributes: ['title'] +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Index result = await documentsDB.CreateIndex( + databaseId: "", + collectionId: "", + key: "title_index", + type: DocumentsDBIndexType.Key, + attributes: new List { "title" } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart' as enums; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Index result = await documentsDB.createIndex( + databaseId: '', + collectionId: '', + key: 'title_index', + type: enums.DocumentsDBIndexType.key, + attributes: ['title'], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.DocumentsDBIndexType +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createIndex( + databaseId = "", + collectionId = "", + key = "title_index", + type = DocumentsDBIndexType.KEY, + attributes = listOf("title"), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.DocumentsDBIndexType; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createIndex( + "", + "", + "title_index", + DocumentsDBIndexType.KEY, + List.of("title"), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let index = try await documentsDB.createIndex( + databaseId: "", + collectionId: "", + key: "title_index", + type: .key, + attributes: ["title"] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::enums::DocumentsDBIndexType; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_index( + "", + "", + "title_index", + DocumentsDBIndexType::Key, + vec!["title"], + None, // orders (optional) + None, // lengths (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc b/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc new file mode 100644 index 00000000000..f2f21db6e45 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/databases/+page.markdoc @@ -0,0 +1,218 @@ +--- +layout: article +title: Databases +description: Dive deeper into Appwrite DocumentsDB and database configuration. Learn how to create and manage multiple databases for your application. +--- +Databases are the largest organizational unit in Appwrite. +Each database contains a group of [collections](/docs/products/databases/documentsdb/collections). + +# Create in Console {% #create-in-console %} +The easiest way to create a database is using the Appwrite Console. +Navigate to the **Databases** page and click **Create database**, choose **DocumentsDB** as the database type, and select your preferred tier. + +{% only_dark %} +![Create database](/images/docs/databases/documentsdb/dark/create-database.avif) +{% /only_dark %} +{% only_light %} +![Create database](/images/docs/databases/documentsdb/create-database.avif) +{% /only_light %} + +# Create using Server SDKs {% #create-using-server-sdks %} +You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.create({ + databaseId: '', + name: '' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.create({ + databaseId: '', + name: '' +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->create( + databaseId: '', + name: '' +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create( + database_id = '', + name = '' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create( + database_id: '', + name: '' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Database result = await documentsDB.Create( + databaseId: "", + name: "" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Database result = await documentsDB.create( + databaseId: '', + name: '', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.create( + databaseId = "", + name = "", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.create( + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let database = try await documentsDB.create( + databaseId: "", + name: "" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create( + "", + "", + None, // enabled (optional) + None, // dedicatedDatabaseId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +{% /multicode %} + diff --git a/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc new file mode 100644 index 00000000000..6bb47042249 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/documents/+page.markdoc @@ -0,0 +1,1189 @@ +--- +layout: article +title: Documents +description: Create, read, update, and delete documents in Appwrite DocumentsDB. Learn how to work with schemaless JSON documents in your collections. +--- +Each piece of data in Appwrite DocumentsDB is a document. +Documents are schemaless JSON, so each document in a collection can hold its own set of fields. + +# Create documents {% #create-documents %} +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_ before users can create documents. +[Learn more about permissions](#permissions) +{% /info %} + +Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { title: 'Hamlet', year: 1601 }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->createDocument( + databaseId: '', + collectionId: '', + documentId: ID::unique(), + data: ['title' => 'Hamlet', 'year' => 1601], + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.create_document( + database_id = '', + collection_id = '', + document_id = ID.unique(), + data = { "title": "Hamlet", "year": 1601 }, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.create_document( + database_id: '', + collection_id: '', + document_id: ID.unique(), + data: { "title" => "Hamlet", "year" => 1601 }, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.CreateDocument( + databaseId: "", + collectionId: "", + documentId: ID.Unique(), + data: new { title = "Hamlet", year = 1601 }, + permissions: new List { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet", "year": 1601 }, + permissions: [Permission.read(Role.any())], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "Hamlet", "year" to 1601), + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createDocument( + "", + "", + ID.unique(), + Map.of("title", "Hamlet", "year", 1601), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title": "Hamlet", "year": 1601], + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "", + "", + ID::unique(), + serde_json::json!({ "title": "Hamlet", "year": 1601 }), + Some(vec![Permission::read(Role::any()).to_string()]), // optional + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db create-document \ + --database-id \ + --collection-id \ + --document-id 'unique()' \ + --data '{ "title": "Hamlet", "year": 1601 }' +``` +{% /multicode %} + +# List documents {% #list-documents %} +Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/documentsdb/queries) to filter, order, and paginate the results. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', 'Hamlet') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', 'Hamlet') + ] +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query::equal('title', ['Hamlet']) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.list_documents( + database_id = '', + collection_id = '', + queries = [ + Query.equal('title', 'Hamlet') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.list_documents( + database_id: '', + collection_id: '', + queries: [ + Query.equal('title', ['Hamlet']) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +DocumentList result = await documentsDB.ListDocuments( + databaseId: "", + collectionId: "", + queries: new List { + Query.Equal("title", new List { "Hamlet" }) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +DocumentList result = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Hamlet') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Hamlet") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments( + "", + "", + List.of( + Query.equal("title", List.of("Hamlet")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let documentList = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Hamlet") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.list_documents( + "", + "", + Some(vec![Query::equal("title", vec!["Hamlet"])]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db list-documents \ + --database-id \ + --collection-id \ + --queries '{"method":"equal","attribute":"title","values":["Hamlet"]}' +``` +{% /multicode %} + +# Update document {% #update-document %} +Use the `updateDocument` method to update a document. With the patch behavior, you only pass the fields you want to change. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { year: 1602 } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { year: 1602 } +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: ['year' => 1602] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.update_document( + database_id = '', + collection_id = '', + document_id = '', + data = { "year": 1602 } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.update_document( + database_id: '', + collection_id: '', + document_id: '', + data: { "year" => 1602 } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.UpdateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new { year = 1602 } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { "year": 1602 }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("year" to 1602), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.updateDocument( + "", + "", + "", + Map.of("year", 1602), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["year": 1602] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_document( + "", + "", + "", + Some(serde_json::json!({ "year": 1602 })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db update-document \ + --database-id \ + --collection-id \ + --document-id \ + --data '{ "year": 1602 }' +``` +{% /multicode %} + +# Upsert documents {% #upsert-documents %} +Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Hamlet', year: 1603 } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.upsertDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { title: 'Hamlet', year: 1603 } +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->upsertDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: ['title' => 'Hamlet', 'year' => 1603] +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.upsert_document( + database_id = '', + collection_id = '', + document_id = '', + data = { "title": "Hamlet", "year": 1603 } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.upsert_document( + database_id: '', + collection_id: '', + document_id: '', + data: { "title" => "Hamlet", "year" => 1603 } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +Document result = await documentsDB.UpsertDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new { title = "Hamlet", year = 1603 } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +Document result = await documentsDB.upsertDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { "title": "Hamlet", "year": 1603 }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +val response = documentsDB.upsertDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf("title" to "Hamlet", "year" to 1603), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.upsertDocument( + "", + "", + "", + Map.of("title", "Hamlet", "year", 1603), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +let document = try await documentsDB.upsertDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: ["title": "Hamlet", "year": 1603] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_document( + "", + "", + "", + Some(serde_json::json!({ "title": "Hamlet", "year": 1603 })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite documents-db upsert-document \ + --database-id \ + --collection-id \ + --document-id \ + --data '{ "title": "Hamlet", "year": 1603 }' +``` +{% /multicode %} + +# Delete document {% #delete-document %} +Use the `deleteDocument` method to remove a document from a collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +const documentsDB = new sdk.DocumentsDB(client); + +const result = await documentsDB.deleteDocument({ + databaseId: '', + collectionId: '', + documentId: '' +}); +``` +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->deleteDocument( + databaseId: '', + collectionId: '', + documentId: '' +); +``` +```python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +documents_db = DocumentsDB(client) + +result = documents_db.delete_document( + database_id = '', + collection_id = '', + document_id = '' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('') # Your project ID + .set_key('') # Your secret API key + +documents_db = DocumentsDB.new(client) + +result = documents_db.delete_document( + database_id: '', + collection_id: '', + document_id: '' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("") // Your project ID + .SetKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +await documentsDB.DeleteDocument( + databaseId: "", + collectionId: "", + documentId: "" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('') // Your project ID + .setKey(''); // Your secret API key + +DocumentsDB documentsDB = DocumentsDB(client); + +await documentsDB.deleteDocument( + databaseId: '', + collectionId: '', + documentId: '', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +val documentsDB = DocumentsDB(client) + +documentsDB.deleteDocument( + databaseId = "", + collectionId = "", + documentId = "", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey(""); // Your secret API key + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.deleteDocument( + "", + "", + "", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println("Deleted"); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + .setKey("") // Your secret API key + +let documentsDB = DocumentsDB(client) + +try await documentsDB.deleteDocument( + databaseId: "", + collectionId: "", + documentId: "" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project(""); // Your project ID + client.set_key(""); // Your secret API key + + let documents_db = DocumentsDB::new(&client); + + documents_db.delete_document( + "", + "", + "", + None, // transactionId (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite documents-db delete-document \ + --database-id \ + --collection-id \ + --document-id +``` +{% /multicode %} + +# Permissions {% #permissions %} +To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions. +By default, Appwrite doesn't grant any user permissions when a new collection is created. + +You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents. + +[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions). diff --git a/src/routes/docs/products/databases/documentsdb/order/+page.markdoc b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc new file mode 100644 index 00000000000..c8dfeba16c6 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc @@ -0,0 +1,336 @@ +--- +layout: article +title: Order +description: Understand how to do data ordering in Appwrite Databases. Learn how to order and sort your database records for efficient data retrieval. +--- + +You can order results returned by Appwrite Databases by using an order query. +For best performance, create an [index](/docs/products/databases/documentsdb/collections#indexes) on the field you plan to order by. + +# Ordering one field {% #one-field %} + +When querying using the [listDocuments](/docs/products/databases/documentsdb/documents#list-documents) endpoint, +you can specify the order of the documents returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` + +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("title") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = [ + Query.orderAsc("title") + ] + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` + +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: ["orderAsc(\"title\")"] + ) { + total + documents { + _id + data + } + } +} +``` + +{% /multicode %} + +# Multiple fields {% #multiple-fields %} +To sort based on multiple fields, simply provide multiple query methods. +For better performance, create an index on the first field that you order by. + +In the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order. +{% multicode %} + +```js +// Web SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), // Order first by title in ascending order + Query.orderDesc('year'), // Then, order by year in descending order + ] +}); +``` +```dart +// Flutter SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('title'), // Order by title in ascending order + Query.orderDesc('year') // Order by year in descending order + ] + ); +} on AppwriteException catch(e) { + print(e); +} +``` +```kotlin +// Android SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = [ + Query.orderAsc("title"), // Order by title in ascending order + Query.orderDesc("year") // Order by year in descending order + ] + ); +} catch (e: AppwriteException) { + Log.e("Appwrite", e.message); +} +``` +```swift +// Apple SDK code example for sorting based on multiple fields +// ... + +// List documents and sort based on multiple fields +do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("title"), // Order by title in ascending order + Query.orderDesc("year") // Order by year in descending order + ] + ); +} catch { + print(error.localizedDescription); +} +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "", + queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"] + ) { + total + documents { + _id + data + } + } +} +``` +{% /multicode %} + +# Ordering by sequence {% #sequence-ordering %} + +For ordering based on insertion order, you can use the `$sequence` field, which Appwrite automatically adds to all documents. Sorting by `$sequence` returns documents in the order they were created. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('$sequence'), + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.orderAsc('\$sequence') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` + +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.orderAsc("$sequence") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.orderAsc("\$sequence") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` + +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: ["orderAsc(\"$sequence\")"] + ) { + total + documents { + _id + data + } + } +} +``` +{% /multicode %} + +The `$sequence` field is useful when you need: +- Consistent ordering for pagination, especially with high-frequency inserts +- Reliable insertion order tracking when timestamps might not be precise enough +- Simple insertion-order sorting without managing custom counter fields diff --git a/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc new file mode 100644 index 00000000000..1f310e8ffeb --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc @@ -0,0 +1,540 @@ +--- +layout: article +title: Pagination +description: Implement pagination for large data sets in Appwrite Databases. Explore techniques for splitting and displaying data across multiple pages. +--- + +As your collection grows in size, you'll need to paginate the documents returned. +Pagination improves performance by returning a subset of documents that match a query at a time, called a page. + +By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method. +There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**. + +# Offset pagination {% #offset-pagination %} + +Offset pagination divides documents into pages of `N` documents each. +To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`. + +Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. +With `Query.limit()` you define how many documents can be returned from one request. +The `Query.offset()` is the number of documents you wish to skip before selecting documents. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +// Page 1 +const page1 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(0) + ] +}); + +// Page 2 +const page2 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(25) + ] +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + final page1 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(0) + ] + ); + + final page2 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.offset(25) + ] + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + let page1 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.offset(0) + ] + ) + + let page2 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.offset(25) + ] + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + val page1 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.offset(0) + ) + ) + + val page2 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.offset(25) + ) + ) +} +``` + +{% /multicode %} + +{% info title="Drawbacks" %} +While traditional offset pagination is familiar, it comes with some drawbacks. +The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data. +If the data changes frequently, offset pagination will also produce **missing and duplicate** results. +{% /info %} + +# Cursor pagination {% #cursor-pagination %} + +The cursor is a unique identifier for a document that points to where the next page should start. +After reading a page of documents, pass the last document's ID into the `Query.cursorAfter(lastId)` query method to get the next page of documents. +Pass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. + +{% multicode %} + +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject(""); + +const documentsDB = new DocumentsDB(client); + +// Page 1 +const page1 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] +}); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + final page1 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ] + ); + + final lastId = page1.documents[page1.documents.length - 1].$id; + + final page2 = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] + ); +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + let page1 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25) + ] + ) + + let lastId = page1.documents[page1.documents.count - 1].$id + + let page2 = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] + ) +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + val page1 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25) + ) + ) + + val lastId = page1.documents[page1.documents.size - 1].$id + + val page2 = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25), + Query.cursorAfter(lastId) + ) + ) +} +``` + +{% /multicode %} + +# When to use what? {% #when-to-use %} +Offset pagination should be used for collections that rarely change. +Offset pagination lets you build an indicator of the current page number and the total page count. +For example, a list with up to 20 pages or static data like a list of countries or currencies. +Using offset pagination on large and frequently updated collections may result in slow performance and **missing and duplicate** results. + +Cursor pagination should be used for frequently updated collections. +It is best suited for lazy-loaded pages with infinite scrolling. +For example, a feed, comment section, chat history, or high volume datasets. + +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. + +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +page = documents_db.list_documents( + database_id='', + collection_id='', + queries=[ + Query.limit(25) + ], + ttl=60 # Cache for 60 seconds +) +``` +```server-ruby +require 'appwrite' + +client = Appwrite::Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = Appwrite::DocumentsDB.new(client) + +page = documents_db.list_documents( + database_id: '', + collection_id: '', + queries: [ + Appwrite::Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```server-deno +import { Client, Query, DocumentsDB } from "https://deno.land/x/appwrite/mod.ts"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new DocumentsDB(client); + +const page = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$page = $documentsDB->listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + + final documentsDB = DocumentsDB(client); + + final page = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ); +} +``` +```server-swift +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + let documentsDB = DocumentsDB(client) + + let page = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds + ) +} +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + + val documentsDB = DocumentsDB(client) + + val page = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds + ) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let page = documents_db.list_documents( + "", + "", + Some(vec![ + Query::limit(25).to_string(), + ]), + None, // transaction_id + None, // total + Some(60), // ttl - Cache for 60 seconds + ).await?; + + println!("{:?}", page); + Ok(()) +} +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "", + queries: ["limit(25)"], + ttl: 60 + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/documentsdb//collections//documents?ttl=60 HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: +``` +{% /multicode %} + +Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents. diff --git a/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc b/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc new file mode 100644 index 00000000000..5ccd0905b35 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/permissions/+page.markdoc @@ -0,0 +1,35 @@ +--- +layout: article +title: Database permissions +description: Control access to your DocumentsDB data with permissions. Learn how to set collection level and document level access rules. +--- + +Permissions define who can access documents in a collection. By default **no permissions** are granted to any users, so no user can access any documents. +Permissions exist at two levels, collection level and document level permissions. + +In Appwrite, permissions are **granted**, meaning a user has no access by default and receives access when granted. +A user with access granted at either collection level or document level will be able to access a document. +Users **don't need access at both levels** to access documents. + +# Collection level {% #collection-level %} +Collection level permissions apply to every document in the collection. +If a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection. + +Configure collection level permissions by navigating to **Your collection** > **Security** > **Permissions**. + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Document level {% #document-level %} +Document level permissions grant access to individual documents. +If a user has read, create, update, or delete permissions at the document level, the user can access the **individual document**. + +Document level permissions are only applied if row level security is enabled in the security settings of your collection. +Enable document level permissions by navigating to **Your collection** > **Security** > **Row level security (RLS)**. + +Document level permissions are configured on individual documents. + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Common use cases {% #common-use-cases %} + +For examples of how to implement common permission patterns, including creating private documents that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. diff --git a/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc b/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc new file mode 100644 index 00000000000..34d9f057694 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/queries/+page.markdoc @@ -0,0 +1,1855 @@ +--- +layout: article +title: Queries +description: Harness the power of querying with Appwrite DocumentsDB. Discover various query options, filtering, sorting, and advanced querying techniques. +--- + +Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. + +# Query class {% #query-class %} + +Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. + +# Building queries {% #building-queries %} + +Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. + +Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. +For example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`. + +{% info title="Default pagination behavior" %} +By default, results are limited to the **first 25 items**. +You can change this through [pagination](/docs/products/databases/documentsdb/pagination). +{% /info %} + +{% multicode %} + +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greaterThan('year', 1999) + ] +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client(); + +const documentsDB = new sdk.DocumentsDB(client); + +client + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey('') +; + +const promise = documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']), + sdk.Query.greaterThan('year', 1999) + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + '', + '', + [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greaterThan('year', 1999) + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: ["Avatar", "Lord of the Rings"]), + Query.greaterThan("year", value: 1999) + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", listOf("Avatar", "Lord of the Rings")), + Query.greaterThan("year", 1999) + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` +```server-go +package main + +import ( + "fmt" + "log" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + documentsDB := appwrite.NewDocumentsDB(client) + + documents, err := documentsDB.ListDocuments( + "", + "", + documentsDB.WithListDocumentsQueries([]string{ + query.Equal("title", []string{"Avatar", "Lord of the Rings"}), + query.GreaterThan("year", 1999), + }), + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Documents: %+v\n", documents) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::query::Query; +use serde_json::Value; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let documents = documents_db.list_documents( + "", + "", + Some(vec![ + Query::equal("title", Value::Array(vec![ + Value::String("Avatar".to_string()), + Value::String("Lord of the Rings".to_string()), + ])).to_string(), + Query::greater_than("year", 1999).to_string(), + ]), + None, + None, + None, + ).await?; + + println!("{:?}", documents); + Ok(()) +} +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey('') +; + +$documentsDB = new DocumentsDB($client); + +$result = $documentsDB->listDocuments( + '', + '', + [ + Query::equal('title', ['Avatar', 'Lord of the Rings']), + Query::greaterThan('year', 1999) + ] +); +``` +```server-python +from appwrite.client import Client +from appwrite.query import Query +from appwrite.services.documents_db import DocumentsDB + +client = Client() + +(client + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') +) + +documentsDB = DocumentsDB(client) + +result = documentsDB.list_documents( + '', + '', + [ + Query.equal('title', ['Avatar', 'Lord of the Rings']), + Query.greater_than('year', 1999) + ] +) +``` +```graphql +query { + documentsDBListDocuments( + databaseId: "", + collectionId: "" + queries: [ + "{\"method\":\"equal\",\"attribute\":\"title\",\"values\":[\"Avatar\",\"Lord of the Rings\"]}", + "{\"method\":\"greaterThan\",\"attribute\":\"year\",\"values\":[1999]}" + ] + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/documentsdb//collections//documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: +``` +```bash +appwrite documents-db list-documents \ + --database-id \ + --collection-id \ + --queries '{"method":"equal","attribute":"title","values":["Avatar","Lord of the Rings"]}' '{"method":"greaterThan","attribute":"year","values":[1999]}' +``` +{% /multicode %} + +# Query operators {% #query-operators %} + +## Select {% #select %} + +The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need. + +{% multicode %} +```client-web +Query.select(["name", "title"]) +``` +```client-flutter +Query.select(["name", "title"]) +``` +```server-python +Query.select(["name", "title"]) +``` +```server-ruby +Query.select(["name", "title"]) +``` +```server-deno +Query.select(["name", "title"]) +``` +```server-php +Query::select(["name", "title"]) +``` +```client-apple +Query.select(["name", "title"]) +``` +```server-go +query.Select([]string{"name", "title"}) +``` +```server-rust +Query::select(vec!["name", "title"]).to_string() +``` +```http +{"method":"select","values":["name","title"]} +``` +{% /multicode %} + +### Use selection patterns {% #select-patterns %} + +| Pattern | Description | Use case | +|---------|-------------|----------| +| `["field1", "field2"]` | Specific fields only | Minimize response size | +| `["*"]` | All document fields | Get complete document data | + +### Optimize performance {% #select-performance %} + +**Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse. + +**Reduce database load** - Selecting fewer fields reduces database processing time, especially for large documents. + +## Comparison operators {% #comparison %} + +### Equal {% #equal %} + +Returns document if field is equal to any value in the provided array. + +{% multicode %} +```client-web +Query.equal("title", ["Iron Man"]) +``` +```client-flutter +Query.equal("title", ["Iron Man"]) +``` +```server-python +Query.equal("title", ["Iron Man"]) +``` +```server-ruby +Query.equal("title", ["Iron Man"]) +``` +```server-deno +Query.equal("title", ["Iron Man"]) +``` +```server-php +Query::equal("title", ["Iron Man"]) +``` +```client-apple +Query.equal("title", value: ["Iron Man"]) +``` +```server-go +query.Equal("title", []string{"Iron Man"}) +``` +```server-rust +Query::equal("title", Value::Array(vec![Value::String("Iron Man".to_string())])).to_string() +``` +```http +{"method":"equal","attribute":"title","values":["Iron Man"]} +``` +{% /multicode %} + +### Not equal {% #not-equal %} + +Returns document if field is not equal to any value in the provided array. + +{% multicode %} +```client-web +Query.notEqual("title", "Iron Man") +``` +```client-flutter +Query.notEqual("title", "Iron Man") +``` +```server-python +Query.not_equal("title", "Iron Man") +``` +```server-ruby +Query.not_equal("title", "Iron Man") +``` +```server-deno +Query.notEqual("title", "Iron Man") +``` +```server-php +Query::notEqual("title", "Iron Man") +``` +```client-apple +Query.notEqual("title", value: "Iron Man") +``` +```server-go +query.NotEqual("title", "Iron Man") +``` +```server-rust +Query::not_equal("title", "Iron Man").to_string() +``` +```http +{"method":"notEqual","attribute":"title","values":"Iron Man"} +``` +{% /multicode %} + +### Less than {% #less-than %} + +Returns document if field is less than the provided value. + +{% multicode %} +```client-web +Query.lessThan("score", 10) +``` +```client-flutter +Query.lessThan("score", 10) +``` +```server-python +Query.less_than("score", 10) +``` +```server-ruby +Query.less_than("score", 10) +``` +```server-deno +Query.lessThan("score", 10) +``` +```server-php +Query::lessThan("score", 10) +``` +```client-apple +Query.lessThan("score", value: 10) +``` +```server-go +query.LessThan("score", 10) +``` +```server-rust +Query::less_than("score", 10).to_string() +``` +```http +{"method":"lessThan","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Less than or equal {% #less-than-equal %} + +Returns document if field is less than or equal to the provided value. + +{% multicode %} +```client-web +Query.lessThanEqual("score", 10) +``` +```client-flutter +Query.lessThanEqual("score", 10) +``` +```server-python +Query.less_than_equal("score", 10) +``` +```server-ruby +Query.less_than_equal("score", 10) +``` +```server-deno +Query.lessThanEqual("score", 10) +``` +```server-php +Query::lessThanEqual("score", 10) +``` +```client-apple +Query.lessThanEqual("score", value: 10) +``` +```server-go +query.LessThanEqual("score", 10) +``` +```server-rust +Query::less_than_equal("score", 10).to_string() +``` +```http +{"method":"lessThanEqual","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Greater than {% #greater-than %} + +Returns document if field is greater than the provided value. + +{% multicode %} +```client-web +Query.greaterThan("score", 10) +``` +```client-flutter +Query.greaterThan("score", 10) +``` +```server-python +Query.greater_than("score", 10) +``` +```server-ruby +Query.greater_than("score", 10) +``` +```server-deno +Query.greaterThan("score", 10) +``` +```server-php +Query::greaterThan("score", 10) +``` +```client-apple +Query.greaterThan("score", value: 10) +``` +```server-go +query.GreaterThan("score", 10) +``` +```server-rust +Query::greater_than("score", 10).to_string() +``` +```http +{"method":"greaterThan","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Greater than or equal {% #greater-than-equal %} + +Returns document if field is greater than or equal to the provided value. + +{% multicode %} +```client-web +Query.greaterThanEqual("score", 10) +``` +```client-flutter +Query.greaterThanEqual("score", 10) +``` +```server-python +Query.greater_than_equal("score", 10) +``` +```server-ruby +Query.greater_than_equal("score", 10) +``` +```server-deno +Query.greaterThanEqual("score", 10) +``` +```server-php +Query::greaterThanEqual("score", 10) +``` +```client-apple +Query.greaterThanEqual("score", value: 10) +``` +```server-go +query.GreaterThanEqual("score", 10) +``` +```server-rust +Query::greater_than_equal("score", 10).to_string() +``` +```http +{"method":"greaterThanEqual","attribute":"score","values":[10]} +``` +{% /multicode %} + +### Between {% #between %} + +Returns document if field value falls between the two values. The boundary values are inclusive and can be strings or numbers. + +{% multicode %} +```client-web +Query.between("price", 5, 10) +``` +```client-flutter +Query.between("price", 5, 10) +``` +```server-python +Query.between("price", 5, 10) +``` +```server-ruby +Query.between("price", 5, 10) +``` +```server-deno +Query.between("price", 5, 10) +``` +```server-php +Query::between("price", 5, 10) +``` +```client-apple +Query.between("price", start: 5, end: 10) +``` +```server-go +query.Between("price", 5, 10) +``` +```server-rust +Query::between("price", 5, 10).to_string() +``` +```http +{"method":"between","attribute":"price","values":[5,10]} +``` +{% /multicode %} + +### Not between {% #not-between %} + +Returns documents if the field value is outside the range defined by the two values (strictly less than start OR strictly greater than end). +Works with strings or numbers. Boundary values are excluded. + +{% multicode %} +```client-web +Query.notBetween("price", 5, 10) +``` +```client-flutter +Query.notBetween("price", 5, 10) +``` +```client-apple +Query.notBetween("price", start: 5, end: 10) +``` +```client-android-kotlin +Query.notBetween("price", 5, 10) +``` +```client-android-java +Query.notBetween("price", 5, 10) +``` +```server-python +Query.not_between("price", 5, 10) +``` +```server-ruby +Query.not_between("price", 5, 10) +``` +```server-deno +Query.notBetween("price", 5, 10) +``` +```server-nodejs +Query.notBetween("price", 5, 10) +``` +```server-php +Query::notBetween("price", 5, 10) +``` +```server-swift +Query.notBetween("price", start: 5, end: 10) +``` +```server-rust +Query::not_between("price", 5, 10).to_string() +``` +```http +{"method":"notBetween","attribute":"price","values":[5,10]} +``` +{% /multicode %} + +## Null checks {% #null-checks %} + +### Is null {% #is-null %} + +Returns documents where field value is null. + +{% multicode %} +```client-web +Query.isNull("name") +``` +```client-flutter +Query.isNull("name") +``` +```server-python +Query.is_null("name") +``` +```server-ruby +Query.is_null("name") +``` +```server-deno +Query.isNull("name") +``` +```server-php +Query::isNull("name") +``` +```client-apple +Query.isNull("name") +``` +```server-go +query.IsNull("name") +``` +```server-rust +Query::is_null("name").to_string() +``` +```http +{"method":"isNull","attribute":"name"} +``` +{% /multicode %} + +### Is not null {% #is-not-null %} + +Returns documents where field value is **not** null. + +{% multicode %} +```client-web +Query.isNotNull("name") +``` +```client-flutter +Query.isNotNull("name") +``` +```server-python +Query.is_not_null("name") +``` +```server-ruby +Query.is_not_null("name") +``` +```server-deno +Query.isNotNull("name") +``` +```server-php +Query::isNotNull("name") +``` +```client-apple +Query.isNotNull("name") +``` +```server-go +query.IsNotNull("name") +``` +```server-rust +Query::is_not_null("name").to_string() +``` +```http +{"method":"isNotNull","attribute":"name"} +``` +{% /multicode %} + +## String operations {% #string-operations %} + +### Starts with {% #starts-with %} + +Returns documents if a string field starts with a substring. + +{% multicode %} +```client-web +Query.startsWith("name", "Once upon a time") +``` +```client-flutter +Query.startsWith("name", "Once upon a time") +``` +```server-python +Query.starts_with("name", "Once upon a time") +``` +```server-ruby +Query.starts_with("name", "Once upon a time") +``` +```server-deno +Query.startsWith("name", "Once upon a time") +``` +```server-php +Query::startsWith("name", "Once upon a time") +``` +```client-apple +Query.startsWith("name", value: "Once upon a time") +``` +```server-go +query.StartsWith("name", "Once upon a time") +``` +```server-rust +Query::starts_with("name", "Once upon a time").to_string() +``` +```http +{"method":"startsWith","attribute":"name","values":["Once upon a time"]} +``` +{% /multicode %} + +### Not starts with {% #not-starts-with %} + +Returns documents if a string field does not start with a substring. + +{% multicode %} +```client-web +Query.notStartsWith("name", "Once upon a time") +``` +```client-flutter +Query.notStartsWith("name", "Once upon a time") +``` +```client-apple +Query.notStartsWith("name", value: "Once upon a time") +``` +```client-android-kotlin +Query.notStartsWith("name", "Once upon a time") +``` +```client-android-java +Query.notStartsWith("name", "Once upon a time") +``` +```server-python +Query.not_starts_with("name", "Once upon a time") +``` +```server-ruby +Query.not_starts_with("name", "Once upon a time") +``` +```server-deno +Query.notStartsWith("name", "Once upon a time") +``` +```server-nodejs +Query.notStartsWith("name", "Once upon a time") +``` +```server-php +Query::notStartsWith("name", "Once upon a time") +``` +```server-swift +Query.notStartsWith("name", value: "Once upon a time") +``` +```server-rust +Query::not_starts_with("name", "Once upon a time").to_string() +``` +```http +{"method":"notStartsWith","attribute":"name","values":["Once upon a time"]} +``` +{% /multicode %} + +### Ends with {% #ends-with %} + +Returns documents if a string field ends with a substring. + +{% multicode %} +```client-web +Query.endsWith("name", "happily ever after.") +``` +```client-flutter +Query.endsWith("name", "happily ever after.") +``` +```server-python +Query.ends_with("name", "happily ever after.") +``` +```server-ruby +Query.ends_with("name", "happily ever after.") +``` +```server-deno +Query.endsWith("name", "happily ever after.") +``` +```server-php +Query::endsWith("name", "happily ever after.") +``` +```client-apple +Query.endsWith("name", value: "happily ever after.") +``` +```server-go +query.EndsWith("name", "happily ever after.") +``` +```server-rust +Query::ends_with("name", "happily ever after.").to_string() +``` +```http +{"method":"endsWith","attribute":"name","values":["happily ever after."]} +``` +{% /multicode %} + +### Not ends with {% #not-ends-with %} + +Returns documents if a string field does not end with a substring. + +{% multicode %} +```client-web +Query.notEndsWith("name", "happily ever after.") +``` +```client-flutter +Query.notEndsWith("name", "happily ever after.") +``` +```client-apple +Query.notEndsWith("name", value: "happily ever after.") +``` +```client-android-kotlin +Query.notEndsWith("name", "happily ever after.") +``` +```client-android-java +Query.notEndsWith("name", "happily ever after.") +``` +```server-python +Query.not_ends_with("name", "happily ever after.") +``` +```server-ruby +Query.not_ends_with("name", "happily ever after.") +``` +```server-deno +Query.notEndsWith("name", "happily ever after.") +``` +```server-nodejs +Query.notEndsWith("name", "happily ever after.") +``` +```server-php +Query::notEndsWith("name", "happily ever after.") +``` +```server-swift +Query.notEndsWith("name", value: "happily ever after.") +``` +```server-rust +Query::not_ends_with("name", "happily ever after.").to_string() +``` +```http +{"method":"notEndsWith","attribute":"name","values":["happily ever after."]} +``` +{% /multicode %} + +### Contains {% #contains %} + +Returns documents if the array field contains the specified elements or if a string field contains the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```client-flutter +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```server-python +# For arrays +Query.contains("ingredients", ['apple', 'banana']) + +# For strings +Query.contains("name", "Tom") +``` +```server-ruby +# For arrays +Query.contains("ingredients", ['apple', 'banana']) + +# For strings +Query.contains("name", "Tom") +``` +```server-deno +// For arrays +Query.contains("ingredients", ['apple', 'banana']) + +// For strings +Query.contains("name", "Tom") +``` +```server-php +// For arrays +Query::contains("ingredients", ['apple', 'banana']) + +// For strings +Query::contains("name", "Tom") +``` +```client-apple +// For arrays +Query.contains("ingredients", value: ["apple", "banana"]) + +// For strings +Query.contains("name", value: "Tom") +```server-go +// For arrays +query.Contains("ingredients", []string{"apple", "banana"}) + +// For strings +query.Contains("name", "Tom") +``` +```server-rust +// For arrays +Query::contains("ingredients", Value::Array(vec![ + Value::String("apple".to_string()), + Value::String("banana".to_string()), +])).to_string() + +// For strings +Query::contains("name", "Tom").to_string() +``` +```http +# For arrays +{"method":"contains","attribute":"ingredients","values":["apple","banana"]} + +# For strings +{"method":"contains","attribute":"name","values":["Tom"]} +``` +{% /multicode %} + +### Not contains {% #not-contains %} + +Returns documents if the array field does not contain the specified +elements, or if a string field does not contain the specified +substring. + +{% multicode %} +```client-web +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-flutter +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-react-native +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-apple +// For arrays +Query.notContains("ingredients", value: ['apple', 'banana']) + +// For strings +Query.notContains("name", value: "Tom") +``` +```client-android-kotlin +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```client-android-java +// For arrays +Query.notContains("ingredients", Arrays.asList("apple", "banana")) + +// For strings +Query.notContains("name", "Tom") +``` +```server-python +# For arrays +Query.not_contains("ingredients", ['apple', 'banana']) + +# For strings +Query.not_contains("name", "Tom") +``` +```server-ruby +# For arrays +Query.not_contains("ingredients", ['apple', 'banana']) + +# For strings +Query.not_contains("name", "Tom") +``` +```server-deno +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-nodejs +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-php +// For arrays +Query::notContains("ingredients", ['apple', 'banana']) + +// For strings +Query::notContains("name", "Tom") +``` +```server-dotnet +// For arrays +Query.NotContains("ingredients", new List { "apple", "banana" }) + +// For strings +Query.NotContains("name", "Tom") +``` +```server-go +// For arrays +query.NotContains("ingredients", []string{"apple", "banana"}) + +// For strings +query.NotContains("name", "Tom") +```server-dart +// For arrays +Query.notContains("ingredients", ['apple', 'banana']) + +// For strings +Query.notContains("name", "Tom") +``` +```server-swift +// For arrays +Query.notContains("ingredients", value: ['apple', 'banana']) + +// For strings +Query.notContains("name", value: "Tom") +``` +```server-kotlin +// For arrays +Query.notContains("ingredients", listOf("apple", "banana")) + +// For strings +Query.notContains("name", "Tom") +``` +```server-rust +// For arrays +Query::not_contains("ingredients", Value::Array(vec![ + Value::String("apple".to_string()), + Value::String("banana".to_string()), +])).to_string() + +// For strings +Query::not_contains("name", "Tom").to_string() +``` +```http +# For arrays +{"method":"notContains","attribute":"ingredients","values":["apple","banana"]} + +# For strings +{"method":"notContains","attribute":"name","values":["Tom"]} +``` +{% /multicode %} + +### Search {% #search %} + +Searches string fields for provided keywords. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes) on queried fields. + +{% multicode %} +```client-web +Query.search("text", "key words") +``` +```client-flutter +Query.search("text", "key words") +``` +```server-python +Query.search("text", "key words") +``` +```server-ruby +Query.search("text", "key words") +``` +```server-deno +Query.search("text", "key words") +``` +```server-php +Query::search("text", "key words") +``` +```client-apple +Query.search("text", value: "key words") +``` +```server-go +query.Search("text", "key words") +``` +```server-rust +Query::search("text", "key words").to_string() +``` +```http +{"method":"search","attribute":"text","values":["key words"]} +``` +{% /multicode %} + +### Not search {% #not-search %} + +Returns documents if a string field does not match the full-text search +query. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes) +on queried fields. + +{% multicode %} +```client-web +Query.notSearch("text", "key words") +``` +```client-flutter +Query.notSearch("text", "key words") +``` +```client-apple +Query.notSearch("text", value: "key words") +``` +```client-android-kotlin +Query.notSearch("text", "key words") +``` +```client-android-java +Query.notSearch("text", "key words") +``` +```server-python +Query.not_search("text", "key words") +``` +```server-ruby +Query.not_search("text", "key words") +``` +```server-deno +Query.notSearch("text", "key words") +``` +```server-nodejs +Query.notSearch("text", "key words") +``` +```server-php +Query::notSearch("text", "key words") +``` +```server-swift +Query.notSearch("text", value: "key words") +``` +```server-rust +Query::not_search("text", "key words").to_string() +``` +```http +{"method":"notSearch","attribute":"text","values":["key words"]} +``` +{% /multicode %} + +## Logical operators {% #logical-operators %} + +### AND {% #and %} + +Returns document if it matches all of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```client-flutter +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```server-python +Query.and_queries([ + Query.less_than("size", 10), + Query.greater_than("size", 5) +]) +``` +```server-ruby +Query.and([ + Query.less_than("size", 10), + Query.greater_than("size", 5) +]) +``` +```server-deno +Query.and([ + Query.lessThan("size", 10), + Query.greaterThan("size", 5) +]) +``` +```server-php +Query::and([ + Query::lessThan("size", 10), + Query::greaterThan("size", 5) +]) +``` +```client-apple +Query.and([ + Query.lessThan("size", value: 10), + Query.greaterThan("size", value: 5) +]) +``` +```server-go +query.And([]string{ + query.LessThan("size", 10), + query.GreaterThan("size", 5), +}) +``` +```server-rust +Query::and(vec![ + Query::less_than("size", 10).to_string(), + Query::greater_than("size", 5).to_string(), +]).to_string() +``` +```http +{"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]} +``` +{% /multicode %} + +### OR {% #or %} + +Returns document if it matches any of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```client-flutter +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```server-python +Query.or_queries([ + Query.less_than("size", 5), + Query.greater_than("size", 10) +]) +``` +```server-ruby +Query.or([ + Query.less_than("size", 5), + Query.greater_than("size", 10) +]) +``` +```server-deno +Query.or([ + Query.lessThan("size", 5), + Query.greaterThan("size", 10) +]) +``` +```server-php +Query::or([ + Query::lessThan("size", 5), + Query::greaterThan("size", 10) +]) +``` +```client-apple +Query.or([ + Query.lessThan("size", value: 5), + Query.greaterThan("size", value: 10) +]) +``` +```server-go +query.Or([]string{ + query.LessThan("size", 5), + query.GreaterThan("size", 10), +}) +``` +```server-rust +Query::or(vec![ + Query::less_than("size", 5).to_string(), + Query::greater_than("size", 10).to_string(), +]).to_string() +``` +```http +{"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]} +``` +{% /multicode %} + +## Ordering {% #ordering %} + +### Order descending {% #order-desc %} + +Orders results in descending order by field. + +{% multicode %} +```client-web +Query.orderDesc("field") +``` +```client-flutter +Query.orderDesc("field") +``` +```server-python +Query.order_desc("field") +``` +```server-ruby +Query.order_desc("field") +``` +```server-nodejs +Query.orderDesc("field") +``` +```server-php +Query::orderDesc("field") +``` +```client-apple +Query.orderDesc("field") +``` +```server-go +query.OrderDesc("attribute") +``` +```server-rust +Query::order_desc("field").to_string() +``` +```http +{"method":"orderDesc","attribute":"field"} +``` +{% /multicode %} + +### Order ascending {% #order-asc %} + +Orders results in ascending order by field. + +{% multicode %} +```client-web +Query.orderAsc("field") +``` +```client-flutter +Query.orderAsc("field") +``` +```server-python +Query.order_asc("field") +``` +```server-ruby +Query.order_asc("field") +``` +```server-nodejs +Query.orderAsc("field") +``` +```server-php +Query::orderAsc("field") +``` +```client-apple +Query.orderAsc("field") +``` +```server-go +query.OrderAsc("attribute") +``` +```server-rust +Query::order_asc("field").to_string() +``` +```http +{"method":"orderAsc","attribute":"field"} +``` +{% /multicode %} + +## Pagination {% #pagination %} + +### Limit {% #limit %} + +Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.limit(25) +``` +```client-flutter +Query.limit(25) +``` +```server-python +Query.limit(25) +``` +```server-ruby +Query.limit(25) +``` +```server-deno +Query.limit(25) +``` +```server-php +Query::limit(25) +``` +```client-apple +Query.limit(25) +``` +```server-go +query.Limit(25) +``` +```server-rust +Query::limit(25).to_string() +``` +```http +{"method":"limit","values":[25]} +``` +{% /multicode %} + +### Offset {% #offset %} + +Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.offset(0) +``` +```client-flutter +Query.offset(0) +``` +```server-python +Query.offset(0) +``` +```server-ruby +Query.offset(0) +``` +```server-deno +Query.offset(0) +``` +```server-php +Query::offset(0) +``` +```client-apple +Query.offset(0) +``` +```server-go +query.Offset(0) +``` +```server-rust +Query::offset(0).to_string() +``` +```http +{"method":"offset","values":[0]} +``` +{% /multicode %} + +### Cursor after {% #cursor-after %} + +Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.cursorAfter("62a7...f620") +``` +```client-flutter +Query.cursorAfter("62a7...f620") +``` +```server-python +Query.cursor_after("62a7...f620") +``` +```server-ruby +Query.cursor_after("62a7...f620") +``` +```server-deno +Query.cursorAfter("62a7...f620") +``` +```server-php +Query::cursorAfter("62a7...f620") +``` +```client-apple +Query.cursorAfter("62a7...f620") +``` +```server-go +query.CursorAfter("62a7...f620") +``` +```server-rust +Query::cursor_after("62a7...f620").to_string() +``` +```http +{"method":"cursorAfter","values":["62a7...f620"]} +``` +{% /multicode %} + +### Cursor before {% #cursor-before %} + +Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination). + +{% multicode %} +```client-web +Query.cursorBefore("62a7...a600") +``` +```client-flutter +Query.cursorBefore("62a7...a600") +``` +```server-python +Query.cursor_before("62a7...a600") +``` +```server-ruby +Query.cursor_before("62a7...a600") +``` +```server-deno +Query.cursorBefore("62a7...a600") +``` +```server-php +Query::cursorBefore("62a7...a600") +``` +```client-apple +Query.cursorBefore("62a7...a600") +``` +```server-go +query.CursorBefore("62a7...a600") +``` +```server-rust +Query::cursor_before("62a7...a600").to_string() +``` +```http +{"method":"cursorBefore","values":["62a7...a600"]} +``` +{% /multicode %} + +# Time helpers {% #time-helpers %} + +Built-in helpers for filtering by creation and update timestamps using +ISO 8601 date-time strings (for example, "2025-01-01T00:00:00Z"). + +### Created before {% #created-before %} + +Returns documents created before the given date. + +{% multicode %} +```client-web +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-apple +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-python +Query.created_before("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.created_before("2025-01-01T00:00:00Z") +``` +```server-deno +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-php +Query::createdBefore("2025-01-01T00:00:00Z") +``` +```server-swift +Query.createdBefore("2025-01-01T00:00:00Z") +``` +```server-rust +Query::created_before("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"createdBefore","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Created after {% #created-after %} + +Returns documents created after the given date. + +{% multicode %} +```client-web +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-apple +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-python +Query.created_after("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.created_after("2025-01-01T00:00:00Z") +``` +```server-deno +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-php +Query::createdAfter("2025-01-01T00:00:00Z") +``` +```server-swift +Query.createdAfter("2025-01-01T00:00:00Z") +``` +```server-rust +Query::created_after("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"createdAfter","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Updated before {% #updated-before %} + +Returns documents updated before the given date. + +{% multicode %} +```client-web +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-apple +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-python +Query.updated_before("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.updated_before("2025-01-01T00:00:00Z") +``` +```server-deno +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-php +Query::updatedBefore("2025-01-01T00:00:00Z") +``` +```server-swift +Query.updatedBefore("2025-01-01T00:00:00Z") +``` +```server-rust +Query::updated_before("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} + +### Updated after {% #updated-after %} + +Returns documents updated after the given date. + +{% multicode %} +```client-web +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-flutter +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-apple +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-android-kotlin +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```client-android-java +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-python +Query.updated_after("2025-01-01T00:00:00Z") +``` +```server-ruby +Query.updated_after("2025-01-01T00:00:00Z") +``` +```server-deno +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-nodejs +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-php +Query::updatedAfter("2025-01-01T00:00:00Z") +``` +```server-swift +Query.updatedAfter("2025-01-01T00:00:00Z") +``` +```server-rust +Query::updated_after("2025-01-01T00:00:00Z").to_string() +``` +```http +{"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]} +``` +{% /multicode %} +# Complex queries {% #complex-queries %} + +You can create complex queries by combining AND and OR operations. For example, to find items that are either books under $20 or magazines under $10: + +{% multicode %} +```client-web +const results = await documentsDB.listDocuments({ + databaseId: '', + collectionId: '', + queries: [ + Query.or([ + Query.and([ + Query.equal('category', ['books']), + Query.lessThan('price', 20) + ]), + Query.and([ + Query.equal('category', ['magazines']), + Query.lessThan('price', 10) + ]) + ]) + ] +}); +``` +```client-flutter +final results = await documentsDB.listDocuments( + '', + '', + [ + Query.or([ + Query.and([ + Query.equal('category', ['books']), + Query.lessThan('price', 20) + ]), + Query.and([ + Query.equal('category', ['magazines']), + Query.lessThan('price', 10) + ]) + ]) + ] +); +``` +```server-python +results = documentsDB.list_documents( + database_id='', + collection_id='', + queries=[ + Query.or_queries([ + Query.and_queries([ + Query.equal('category', ['books']), + Query.less_than('price', 20) + ]), + Query.and_queries([ + Query.equal('category', ['magazines']), + Query.less_than('price', 10) + ]) + ]) + ] +) +``` +```server-go +documents, err := documentsDB.ListDocuments( + "", + "", + documentsDB.WithListDocumentsQueries([]string{ + query.Or([]string{ + query.And([]string{ + query.Equal("category", []string{"books"}), + query.LessThan("price", 20), + }), + query.And([]string{ + query.Equal("category", []string{"magazines"}), + query.LessThan("price", 10), + }), + }), + }), +) +if err != nil { + log.Fatal(err) +} +``` +```server-rust +let documents = documents_db.list_documents( + "", + "", + Some(vec![ + Query::or(vec![ + Query::and(vec![ + Query::equal("category", Value::Array(vec![Value::String("books".to_string())])).to_string(), + Query::less_than("price", 20).to_string(), + ]).to_string(), + Query::and(vec![ + Query::equal("category", Value::Array(vec![Value::String("magazines".to_string())])).to_string(), + Query::less_than("price", 10).to_string(), + ]).to_string(), + ]).to_string(), + ]), + None, + None, + None, +).await?; +``` +```http +{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"category","values":["books"]},{"method":"lessThan","attribute":"price","values":[20]}]},{"method":"and","values":[{"method":"equal","attribute":"category","values":["magazines"]},{"method":"lessThan","attribute":"price","values":[10]}]}]} +``` +{% /multicode %} + +This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10. +Each condition within the OR is composed of two AND conditions - one for the category and one for the price threshold. The database will return documents that match either of these combined conditions. + diff --git a/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc new file mode 100644 index 00000000000..92c5f30cf8b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/quick-start/+page.markdoc @@ -0,0 +1,243 @@ +--- +layout: article +title: Start with DocumentsDB +description: Get started with Appwrite DocumentsDB. Follow a step-by-step guide to create your first database, add a collection, and perform basic document operations. +--- + + +{% section #create-database step=1 title="Create database" %} +Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and click **Create database**. Name it `Oscar` and choose **DocumentsDB** as the database type. +Optionally, add a custom database ID. Select your preferred tier, then click **Create database**. +{% /section %} + +{% section #create-collection step=2 title="Create collection" %} +In the `Oscar` database, click **Create collection** and name it `My books`. Optionally, add a custom collection ID. + +Collections are schemaless, so there are no columns to define. Each document holds its own fields as flexible JSON. + +Open the collection's **Security** tab. Under **Permissions**, add a new role **Any** and check **Create** and **Read**, so anyone can create and read documents. Click **Update** to save. +{% /section %} + + +{% section #create-documents step=3 title="Create documents" %} +To create a document use the `createDocument` method. + +In the **Settings** menu, find your project ID and replace `` in the example. + +Navigate to the `Oscar` database, copy the database ID, and replace ``. +Then, in the `My books` collection, copy the collection ID, and replace ``. + +{% multicode %} +```client-web +import { Client, ID, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { title: "Hamlet" } +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject(''); + + final documentsDB = DocumentsDB(client); + + try { + final document = documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { "title": "Hamlet" } + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let document = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: ID.unique(), + data: ["title": "Hamlet"] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val document = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = ID.unique(), + data = mapOf("title" to "Hamlet"), + ) + } catch (e: Exception) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` +{% /multicode %} + +The response should look similar to this. + +```json +{ + "title": "Hamlet", + "$id": "6a423aa70000c39da0be", + "$sequence": "019f12b5-1c89-7094-8d12-5b7e7a09f025", + "$permissions": [], + "$createdAt": "2026-06-29T09:28:07.047+00:00", + "$updatedAt": "2026-06-29T09:28:07.047+00:00", + "$databaseId": "650125c64b3c25ce4bc4", + "$collectionId": "650125cff227cf9f95ad" +} +``` + +{% /section %} + +{% section #list-documents step=4 title="List documents" %} +To read and query data from your collection, use the `listDocuments` endpoint. + +Like the previous step, replace ``, ``, and `` with their respective IDs. +{% multicode %} +```client-web +import { Client, Query, DocumentsDB } from "appwrite"; + +const client = new Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + +const documentsDB = new DocumentsDB(client); + +const promise = documentsDB.listDocuments({ + databaseId: "", + collectionId: "", + queries: [ + Query.equal('title', 'Hamlet') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + final documentsDB = DocumentsDB(client); + + try { + final documents = await documentsDB.listDocuments( + databaseId: '', + collectionId: '', + queries: [ + Query.equal('title', 'Hamlet') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws{ + let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + let documentsDB = DocumentsDB(client) + + do { + let documents = try await documentsDB.listDocuments( + databaseId: "", + collectionId: "", + queries: [ + Query.equal("title", value: "Hamlet") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.DocumentsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + + val documentsDB = DocumentsDB(client) + + try { + val documents = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf( + Query.equal("title", "Hamlet") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", "Error: " + e.message) + } +} +``` + +{% /multicode %} +{% /section %} diff --git a/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc new file mode 100644 index 00000000000..0549b732ab4 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/timestamp-overrides/+page.markdoc @@ -0,0 +1,1266 @@ +--- +layout: article +title: Timestamp overrides +description: Set custom $createdAt and $updatedAt timestamps for your documents when using server SDKs. +--- + +When creating or updating documents, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. + +{% info title="Server SDKs required" %} +To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These attributes can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). +{% /info %} + +# Setting custom timestamps {% #setting-custom-timestamps %} + +You can override a document's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these attributes are not provided, Appwrite will set them automatically. + +Custom timestamps work with all document operations: create, update, upsert, and their bulk variants. + +## Single document operations {% #single-document-operations %} + +When working with individual documents, you can set custom timestamps during create, update, and upsert operations. + +### Create with custom timestamps {% #create-custom %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const documentsDB = new sdk.DocumentsDB(client); + +await documentsDB.createDocument({ + databaseId: '', + collectionId: '', + documentId: sdk.ID.unique(), + data: { + '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your attributes + } +}); +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->createDocument( + databaseId: '', + collectionId: '', + documentId: ID::unique(), + data: [ + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let customDate = isoFormatter.date(from: "") ?? Date() +let createdAt = isoFormatter.string(from: customDate) +let updatedAt = isoFormatter.string(from: customDate) + +do { + let created = try await documentsDB.createDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your attributes + ] + ) + print("Created:", created) +} catch { + print("Create error:", error) +} +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB +from appwrite.id import ID +from datetime import datetime, timezone + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +documents_db = DocumentsDB(client) + +iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() + +documents_db.create_document( + database_id='', + collection_id='', + document_id=ID.unique(), + data={ + '$createdAt': iso, + '$updatedAt': iso, + # ...your attributes + } +) +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 + +documents_db.create_document( + database_id: '', + collection_id: '', + document_id: ID.unique(), + data: { + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your attributes + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); + +await documentsDB.CreateDocument( + databaseId: "", + collectionId: "", + documentId: ID.Unique(), + data: new Dictionary + { + ["$createdAt"] = customDate, + ["$updatedAt"] = customDate, + // ...your attributes + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); + +await documentsDB.createDocument( + databaseId: '', + collectionId: '', + documentId: ID.unique(), + data: { + '\$createdAt': customDate, + '\$updatedAt': customDate, + // ...your attributes + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "", + "", + &ID::unique(), + json!({ + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z" + // ...your attributes + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Update with custom timestamps {% #update-custom %} + +When updating documents, you can also set a custom `$updatedAt` timestamp: + +{% multicode %} +```server-nodejs +await documentsDB.updateDocument({ + databaseId: '', + collectionId: '', + documentId: '', + data: { + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + // ...your attributes + } +}); +``` +```server-php +$documentsDB->updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: [ + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ] +); +``` +```server-python +from datetime import datetime, timezone + +documents_db.update_document( + database_id='', + collection_id='', + document_id='', + data={ + '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } +) +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let updated = try await documentsDB.updateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: [ + "$updatedAt": updatedAt, + // ...your attributes + ] + ) + print("Updated:", updated) +} catch { + print("Update error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('').iso8601 + +documents_db.update_document( + database_id: '', + collection_id: '', + document_id: '', + data: { + '$updatedAt' => custom_date, + # ...your attributes + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string customDate = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.UpdateDocument( + databaseId: "", + collectionId: "", + documentId: "", + data: new Dictionary + { + ["$updatedAt"] = customDate, + // ...your attributes + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String customDate = DateTime.parse('').toIso8601String(); + +await documentsDB.updateDocument( + databaseId: '', + collectionId: '', + documentId: '', + data: { + '\$updatedAt': customDate, + // ...your attributes + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.update_document( + "", + "", + "", + Some(json!({ + "$updatedAt": "2025-08-10T12:34:56.000Z" + // ...your attributes + })), + None, + None, + ).await?; + + println!("Updated: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Bulk operations {% #bulk-operations %} + +Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch: + +### Bulk create {% #bulk-create %} + +{% multicode %} +```server-nodejs +await documentsDB.createDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + // ...your attributes + }, + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + // ...your attributes + } + ] +}); +``` +```server-python +documents_db.create_documents( + database_id='', + collection_id='', + documents=[ + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + }, + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->createDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + +let first = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) +let second = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let bulkCreated = try await documentsDB.createDocuments( + databaseId: "", + collectionId: "", + documents: [ + [ + "$id": ID.unique(), + "$createdAt": first, + "$updatedAt": first, + // ...your attributes + ], + [ + "$id": ID.unique(), + "$createdAt": second, + "$updatedAt": second, + // ...your attributes + ] + ] + ) + print("Bulk create:", bulkCreated) +} catch { + print("Bulk create error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +first = Time.parse('').iso8601 +second = Time.parse('').iso8601 + +documents_db.create_documents( + database_id: '', + collection_id: '', + documents: [ + { + '$id' => ID.unique(), + '$createdAt' => first, + '$updatedAt' => first, + # ...your attributes + }, + { + '$id' => ID.unique(), + '$createdAt' => second, + '$updatedAt' => second, + # ...your attributes + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string first = DateTimeOffset.Parse("").ToString("O"); +string second = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.CreateDocuments( + databaseId: "", + collectionId: "", + documents: new List + { + new Dictionary + { + ["$id"] = ID.Unique(), + ["$createdAt"] = first, + ["$updatedAt"] = first, + // ...your attributes + }, + new Dictionary + { + ["$id"] = ID.Unique(), + ["$createdAt"] = second, + ["$updatedAt"] = second, + // ...your attributes + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String first = DateTime.parse('').toIso8601String(); +String second = DateTime.parse('').toIso8601String(); + +await documentsDB.createDocuments( + databaseId: '', + collectionId: '', + documents: [ + { + '\$id': ID.unique(), + '\$createdAt': first, + '\$updatedAt': first, + // ...your attributes + }, + { + '\$id': ID.unique(), + '\$createdAt': second, + '\$updatedAt': second, + // ...your attributes + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_documents( + "", + "", + vec![ + json!({ + "$id": ID::unique(), + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2024-01-01T00:00:00.000Z" + // ...your attributes + }), + json!({ + "$id": ID::unique(), + "$createdAt": "2024-02-01T00:00:00.000Z", + "$updatedAt": "2024-02-01T00:00:00.000Z" + // ...your attributes + }), + ], + None, + ).await?; + + println!("Bulk create: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Bulk upsert {% #bulk-upsert %} + +{% multicode %} +```server-nodejs +await documentsDB.upsertDocuments({ + databaseId: '', + collectionId: '', + documents: [ + { + '$id': '', + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), + // ...your attributes + } + ] +}); +``` +```server-python +documents_db.upsert_documents( + database_id='', + collection_id='', + documents=[ + { + '$id': '', + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), + # ...your attributes + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\DocumentsDB; + +$client = (new Client()) + ->setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$documentsDB = new DocumentsDB($client); + +$documentsDB->upsertDocuments( + databaseId: '', + collectionId: '', + documents: [ + [ + '$id' => '', + '$createdAt' => (new DateTime(''))->format(DATE_ATOM), + '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), + // ...your attributes + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let documentsDB = DocumentsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let createdAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) + +do { + let bulkUpserted = try await documentsDB.upsertDocuments( + databaseId: "", + collectionId: "", + documents: [ + [ + "$id": "", + "$createdAt": createdAt, + "$updatedAt": updatedAt, + // ...your attributes + ] + ] + ) + print("Bulk upsert:", bulkUpserted) +} catch { + print("Bulk upsert error:", error) +} +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +documents_db = DocumentsDB.new(client) + +custom_date = Time.parse('').iso8601 + +documents_db.upsert_documents( + database_id: '', + collection_id: '', + documents: [ + { + '$id' => '', + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + # ...your attributes + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +DocumentsDB documentsDB = new DocumentsDB(client); + +string createdAt = DateTimeOffset.Parse("").ToString("O"); +string updatedAt = DateTimeOffset.Parse("").ToString("O"); + +await documentsDB.UpsertDocuments( + databaseId: "", + collectionId: "", + documents: new List + { + new Dictionary + { + ["$id"] = "", + ["$createdAt"] = createdAt, + ["$updatedAt"] = updatedAt, + // ...your attributes + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +DocumentsDB documentsDB = DocumentsDB(client); + +String createdAt = DateTime.parse('').toIso8601String(); +String updatedAt = DateTime.parse('').toIso8601String(); + +await documentsDB.upsertDocuments( + databaseId: '', + collectionId: '', + documents: [ + { + '\$id': '', + '\$createdAt': createdAt, + '\$updatedAt': updatedAt, + // ...your attributes + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_documents( + "", + "", + vec![ + json!({ + "$id": "", + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2025-01-01T00:00:00.000Z" + // ...your attributes + }), + ], + None, + ).await?; + + println!("Bulk upsert: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +# Common use cases {% #use-cases %} + +Custom timestamps are particularly useful in several scenarios: + +## Data migration {% #data-migration %} +When migrating existing data from another system, you can preserve the original +creation and modification times: + +{% multicode %} +```server-nodejs +await documentsDB.createDocument({ + databaseId: '', + collectionId: 'blog_posts', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '', + '$updatedAt': '', + title: '', + content: '<CONTENT>' + } +}); +``` +```server-php +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'blog_posts', + documentId: ID::unique(), + data: [ + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + ] +); +``` +```server-swift +let _ = try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "blog_posts", + documentId: ID.unique(), + data: [ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "title": "<TITLE>", + "content": "<CONTENT>" + ] +) +``` +```server-python +documents_db.create_document( + database_id='<DATABASE_ID>', + collection_id='blog_posts', + document_id=ID.unique(), + data={ + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + } +) +``` +```server-ruby +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: 'blog_posts', + document_id: ID.unique(), + data: { + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'title' => '<TITLE>', + 'content' => '<CONTENT>' + } +) +``` +```server-dotnet +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "blog_posts", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", + ["$updatedAt"] = "<LAST_MODIFIED_ISO>", + ["title"] = "<TITLE>", + ["content"] = "<CONTENT>" + } +); +``` +```server-dart +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'blog_posts', + documentId: ID.unique(), + data: { + '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '\$updatedAt': '<LAST_MODIFIED_ISO>', + 'title': '<TITLE>', + 'content': '<CONTENT>' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "<DATABASE_ID>", + "blog_posts", + &ID::unique(), + json!({ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "title": "<TITLE>", + "content": "<CONTENT>" + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Backdating records {% #backdating %} +For historical data entry or when creating records that represent past events: + +{% multicode %} +```server-nodejs +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + amount: 1000, + type: 'year-end-bonus' + } +}); +``` +```server-php +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: ID::unique(), + data: [ + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + ] +); +``` +```server-swift +let _ = try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "transactions", + documentId: ID.unique(), + data: [ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "amount": 1000, + "type": "year-end-bonus" + ] +) +``` +```server-python +documents_db.create_document( + database_id='<DATABASE_ID>', + collection_id='transactions', + document_id=ID.unique(), + data={ + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + } +) +``` +```server-ruby +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: 'transactions', + document_id: ID.unique(), + data: { + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'amount' => 1000, + 'type' => 'year-end-bonus' + } +) +``` +```server-dotnet +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "transactions", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "2023-12-31T23:59:59.000Z", + ["$updatedAt"] = "2023-12-31T23:59:59.000Z", + ["amount"] = 1000, + ["type"] = "year-end-bonus" + } +); +``` +```server-dart +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'transactions', + documentId: ID.unique(), + data: { + '\$createdAt': '2023-12-31T23:59:59.000Z', + '\$updatedAt': '2023-12-31T23:59:59.000Z', + 'amount': 1000, + 'type': 'year-end-bonus' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.create_document( + "<DATABASE_ID>", + "transactions", + &ID::unique(), + json!({ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "amount": 1000, + "type": "year-end-bonus" + }), + None, + None, + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Synchronization {% #synchronization %} +When synchronizing data between systems while maintaining timestamp consistency: + +{% multicode %} +```server-nodejs +await documentsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + profile: '<PROFILE_DATA>' + } +}); +``` +```server-php +$documentsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: [ + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + ] +); +``` +```server-swift +let _ = try await documentsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "users", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: [ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "profile": "<PROFILE_DATA>" + ] +) +``` +```server-python +documents_db.upsert_document( + database_id='<DATABASE_ID>', + collection_id='users', + document_id='<DOCUMENT_ID_OR_NEW_ID>', + data={ + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + } +) +``` +```server-ruby +documents_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: 'users', + document_id: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile' => '<PROFILE_DATA>' + } +) +``` +```server-dotnet +await documentsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "users", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", + ["profile"] = "<PROFILE_DATA>" + } +); +``` +```server-dart +await documentsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: 'users', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'profile': '<PROFILE_DATA>' + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let result = documents_db.upsert_document( + "<DATABASE_ID>", + "users", + "<DOCUMENT_ID_OR_NEW_ID>", + Some(json!({ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "profile": "<PROFILE_DATA>" + })), + None, + None, + ).await?; + + println!("Upserted: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +{% info title="Timestamp format and usage" %} +- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. +- You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. +{% /info %} diff --git a/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc new file mode 100644 index 00000000000..be64977d0ad --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/transactions/+page.markdoc @@ -0,0 +1,1274 @@ +--- +layout: article +title: Transactions +description: Stage multiple database operations and commit them atomically. Group changes across databases and collections with ordering, isolation, and conflict detection. +--- + +Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and collections. + +# How transactions work {% #how-transactions-work %} + +1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. +2. Stage operations by passing the `transactionId` parameter to supported document, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. +3. Call the [updateTransaction](#update-transaction) method to commit or roll back. + +On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected document changed outside your transaction, the commit fails with a conflict. + +{% info title="Scope and limitations" %} +You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions. +{% /info %} + +# Limits {% #limits %} + +The maximum number of operations you can stage per transaction depends on your plan: + +| Plan | Max operations per transaction | +|------|-------------------------------| +| Free | 100 | +| Pro | 1,000 | + +# Create a transaction {% #create-a-transaction %} + +Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. + +{% multicode %} +```client-web +import { Client, DocumentsDB } from 'appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-react-native +import { Client, DocumentsDB } from 'react-native-appwrite'; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const documentsDB = new DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +final client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +final documentsDB = DocumentsDB(client); + +final tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client(applicationContext) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```client-android-java +import io.appwrite.Client; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +// Create a transaction (asynchronous) +documentsDB.createTransaction(new CoroutineCallback<>((tx, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(tx); +})); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-deno +import * as sdk from 'npm:node-appwrite'; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const documentsDB = new sdk.DocumentsDB(client); + +const tx = await documentsDB.createTransaction(); +// tx.$id is your transactionId +``` +```server-python +from appwrite.client import Client +from appwrite.services.documents_db import DocumentsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +documents_db = DocumentsDB(client) + +tx = documents_db.create_transaction() +# tx.$id is your transactionId +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Services\DocumentsDB; + +$client = new Client(); + +$client + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<API_KEY>') +; + +$documentsDB = new DocumentsDB($client); + +$tx = $documentsDB->createTransaction(); +// $tx->\$id is your transactionId +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<API_KEY>') + +documents_db = DocumentsDB.new(client) + +tx = documents_db.create_transaction +# tx['$id'] is your transactionId +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +var client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<PROJECT_ID>") + .SetKey("<API_KEY>"); + +var documentsDB = new DocumentsDB(client); + +var tx = await documentsDB.CreateTransaction(); +// tx.$id is your transactionId +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +void main() async { + Client client = Client(); + DocumentsDB documentsDB = DocumentsDB(client); + + client + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + + final tx = await documentsDB.createTransaction(); + // tx contains the transaction ID +} +``` +```server-go +package main + +import ( + "log" + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<API_KEY>"), + ) + + documentsDB := appwrite.NewDocumentsDB(client) + + tx, err := documentsDB.CreateTransaction() + if err != nil { log.Fatal(err) } + _ = tx +} +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +let documentsDB = DocumentsDB(client) + +let tx = try await documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.DocumentsDB + +val client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>") + +val documentsDB = DocumentsDB(client) + +val tx = documentsDB.createTransaction() +// tx.$id is your transactionId +``` +```server-java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + .setKey("<API_KEY>"); + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createTransaction(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + System.out.println(result); +})); +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + // tx.id is your transaction_id + + Ok(()) +} +``` +{% /multicode %} + +# Stage operations {% #stage-operations %} + +Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. + +When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target collection is not modified until you commit the transaction. + +## Stage single operations {% #stage-single-operations %} + +Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createDocuments, updateDocuments, upsertDocuments, deleteDocuments). + +{% multicode %} +```client-web +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```client-flutter +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.$id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +); +``` +```client-apple +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +```server-kotlin +// Create inside a transaction +documentsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf("name" to "Walter"), + transactionId = tx.$id +) + +// Increment inside a transaction +documentsDB.incrementDocumentAttribute( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + attribute = "credits", + value = 1, + transactionId = tx.$id +) +``` +```server-java +// Create inside a transaction (asynchronous) +documentsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of("name", "Walter"), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); + +// Increment inside a transaction (asynchronous) +documentsDB.incrementDocumentAttribute( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + "credits", + 1, + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(document); + return null; + }) +); +``` +```client-react-native +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-nodejs +// Update inside a transaction +await documentsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { plan: 'pro' }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await documentsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```server-python +# Upsert inside a transaction +documents_db.upsert_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { 'name': 'Walter' }, + transaction_id = tx.id +) + +# Decrement inside a transaction +documents_db.decrement_document_attribute( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + attribute = 'credits', + value = 1, + transaction_id = tx.id +) +``` +```server-php +// Create inside a transaction +$documentsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: ['name' => 'Walter'], + transactionId: $tx['$id'] +); + +// Increment inside a transaction +$documentsDB->incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: $tx['$id'] +); +``` +```server-ruby +# Create inside a transaction +documents_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { 'name' => 'Walter' }, + transaction_id: tx['$id'] +) + +# Increment inside a transaction +documents_db.increment_document_attribute( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transaction_id: tx['$id'] +) +``` +```server-dotnet +// Create inside a transaction +await documentsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { ["name"] = "Walter" }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.IncrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.Id +); +``` +```server-dart +// Create inside a transaction +await documentsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { 'name': 'Walter' }, + transactionId: tx.Id +); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.Id +); +``` +```server-deno +// Create inside a transaction +await documentsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { name: 'Walter' }, + transactionId: tx.$id +}); + +// Increment inside a transaction +await documentsDB.incrementDocumentAttribute({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + attribute: 'credits', + value: 1, + transactionId: tx.$id +}); +``` +```server-swift +// Create inside a transaction +try await documentsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["name": "Walter"], + transactionId: tx.$id +) + +// Increment inside a transaction +try await documentsDB.incrementDocumentAttribute( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + attribute: "credits", + value: 1, + transactionId: tx.$id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + + // Update inside a transaction + documents_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ "plan": "pro" })), + None, + Some(&tx.id), + ).await?; + + // Delete inside a transaction + documents_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +## Stage many with createOperations {% #create-operations %} + +Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects: + +```json +[ + { + "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID>", + "data": {} + } +] +``` + +### Provide data for each action (createOperations) {% #provide-data-for-each-action %} + +### Create, update, and upsert {% #create-update-upsert %} +Pass a raw data object. +```json +{ "name": "Walter" } +``` + +### Increment and decrement {% #increment-decrement %} +Pass a value and optionally `min`/`max` bounds. +```json +{ "value": 1, "min": 0, "max": 1000, "attribute": "<ATTRIBUTE_NAME>" } +``` + +### Bulk create and bulk upsert {% #bulk-create-upsert %} +Pass an array of raw data objects. +```json +[{ "$id": "123", "name": "Walter" }] +``` + +### Bulk update {% #bulk-update %} +Pass queries and the data to apply. +```json +{ "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } } +``` + +### Bulk delete {% #bulk-delete %} +Pass queries to select documents to delete. +```json +{ "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] } +``` + +{% multicode %} +```server-nodejs +// Stage multiple operations at once +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-python +documents_db.create_operations( + transaction_id = tx.id, + operations = [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +) +``` +```client-web +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```client-flutter +await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ], +); +``` +```client-apple +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +```server-kotlin +documentsDB.createOperations( + transactionId = tx.$id, + operations = listOf( + mapOf( + "action" to "create", + "databaseId" to "<DB_A>", + "collectionId" to "<COLLECTION_1>", + "documentId" to "u1", + "data" to mapOf("name" to "Walter") + ), + mapOf( + "action" to "increment", + "databaseId" to "<DB_B>", + "collectionId" to "<COLLECTION_2>", + "documentId" to "u2", + "data" to mapOf("value" to 1, "min" to 0, "attribute" to "credits") + ) + ) +) +``` +```server-java +// Stage multiple operations at once (asynchronous) +List<Map<String, Object>> operations = Arrays.asList( + Map.of( + "action", "create", + "databaseId", "<DB_A>", + "collectionId", "<COLLECTION_1>", + "documentId", "u1", + "data", Map.of("name", "Walter") + ), + Map.of( + "action", "increment", + "databaseId", "<DB_B>", + "collectionId", "<COLLECTION_2>", + "documentId", "u2", + "data", Map.of("value", 1, "min", 0, "attribute", "credits") + ) +); + +documentsDB.createOperations( + "<TRANSACTION_ID>", + operations, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-deno +await documentsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'create', + databaseId: '<DB_A>', + collectionId: '<COLLECTION_1>', + documentId: 'u1', + data: { name: 'Walter' } + }, + { + action: 'increment', + databaseId: '<DB_B>', + collectionId: '<COLLECTION_2>', + documentId: 'u2', + data: { value: 1, min: 0, attribute: 'credits' } + } + ] +}); +``` +```server-php +$documentsDB->createOperations( + transactionId: $tx['$id'], + operations: [ + [ + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => [ 'name' => 'Walter' ] + ], + [ + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => [ 'value' => 1, 'min' => 0, 'attribute' => 'credits' ] + ] + ] +); +``` +```server-ruby +documents_db.create_operations( + transaction_id: tx['$id'], + operations: [ + { + 'action' => 'create', + 'databaseId' => '<DB_A>', + 'collectionId' => '<COLLECTION_1>', + 'documentId' => 'u1', + 'data' => { 'name' => 'Walter' } + }, + { + 'action' => 'increment', + 'databaseId' => '<DB_B>', + 'collectionId' => '<COLLECTION_2>', + 'documentId' => 'u2', + 'data' => { 'value' => 1, 'min' => 0, 'attribute' => 'credits' } + } + ] +) +``` +```server-dotnet +await documentsDB.CreateOperations( + transactionId: tx.Id, + operations: new List<Dictionary<string, object>> + { + new Dictionary<string, object> + { + ["action"] = "create", + ["databaseId"] = "<DB_A>", + ["collectionId"] = "<COLLECTION_1>", + ["documentId"] = "u1", + ["data"] = new Dictionary<string, object> { ["name"] = "Walter" } + }, + new Dictionary<string, object> + { + ["action"] = "increment", + ["databaseId"] = "<DB_B>", + ["collectionId"] = "<COLLECTION_2>", + ["documentId"] = "u2", + ["data"] = new Dictionary<string, object> { ["value"] = 1, ["min"] = 0, ["attribute"] = "credits" } + } + } +); +``` +```server-dart +await documentsDB.createOperations( + transactionId: tx.Id, + operations: [ + { + 'action': 'create', + 'databaseId': '<DB_A>', + 'collectionId': '<COLLECTION_1>', + 'documentId': 'u1', + 'data': { 'name': 'Walter' } + }, + { + 'action': 'increment', + 'databaseId': '<DB_B>', + 'collectionId': '<COLLECTION_2>', + 'documentId': 'u2', + 'data': { 'value': 1, 'min': 0, 'attribute': 'credits' } + } + ] +); +``` +```server-swift +try await documentsDB.createOperations( + transactionId: tx.$id, + operations: [ + [ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": ["name": "Walter"] + ], + [ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": ["value": 1, "min": 0, "attribute": "credits"] + ] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + let tx = documents_db.create_transaction(None).await?; + + // Stage multiple operations at once + documents_db.create_operations( + &tx.id, + Some(vec![ + json!({ + "action": "create", + "databaseId": "<DB_A>", + "collectionId": "<COLLECTION_1>", + "documentId": "u1", + "data": { "name": "Walter" } + }), + json!({ + "action": "increment", + "databaseId": "<DB_B>", + "collectionId": "<COLLECTION_2>", + "documentId": "u2", + "data": { "value": 1, "min": 0, "attribute": "credits" } + }), + ]), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Commit or roll back {% #commit-or-rollback %} + +When you are done staging operations, call the `updateTransaction` method to finalize the transaction. + +{% multicode %} +```client-web +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```client-flutter +// Commit +await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +); +``` +```client-apple +// Commit +try await documentsDB.updateTransaction( + transactionId: tx.$id, + commit: true +) + +// Roll back +try await documentsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +) +``` +```server-kotlin +// Commit +documentsDB.updateTransaction( + transactionId = tx.$id, + commit = true +) + +// Roll back +documentsDB.updateTransaction( + transactionId = tx.$id, + rollback = true +) +``` +```server-java +// Commit (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + true, + false, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); + +// Roll back (asynchronous) +documentsDB.updateTransaction( + "<TRANSACTION_ID>", + false, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return null; + } + System.out.println(result); + return null; + }) +); +``` +```client-react-native +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-nodejs +// Commit +await documentsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Roll back +await documentsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```server-python +# Commit +documents_db.update_transaction( + transaction_id = tx.id, + commit = True +) + +# Roll back +documents_db.update_transaction( + transaction_id = tx.id, + rollback = True +) +``` +```server-php +// Commit +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + commit: true +); + +// Roll back +$documentsDB->updateTransaction( + transactionId: $tx['$id'], + rollback: true +); +``` +```server-ruby +# Commit +documents_db.update_transaction( + transaction_id: tx['$id'], + commit: true +) + +# Roll back +documents_db.update_transaction( + transaction_id: tx['$id'], + rollback: true +) +``` +```server-dotnet +// Commit +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.UpdateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```server-dart +// Commit +await documentsDB.updateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await documentsDB.updateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```server-rust +use appwrite::Client; +use appwrite::services::documents_db::DocumentsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<API_KEY>"); + + let documents_db = DocumentsDB::new(&client); + + // Commit + documents_db.update_transaction( + "<TRANSACTION_ID>", + Some(true), + None, + ).await?; + + // Roll back + documents_db.update_transaction( + "<TRANSACTION_ID>", + None, + Some(true), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Handle conflicts {% #handle-conflicts %} + +On commit, Appwrite verifies that documents affected by your transaction haven't changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. + +{% info title="Best practices" %} +Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple collections. +{% /info %} + +{% arrow_link href="/docs/references" %} +Explore the API references +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc b/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc index 202133529f6..6dca288f054 100644 --- a/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/backups/+page.markdoc @@ -8,14 +8,14 @@ Appwrite Backups enable seamless, **encrypted** database backups on Cloud. All backups are **hot** backups, ensuring zero downtime and fast recovery. Learn how to efficiently back up your databases to ensure data security and smooth recovery. -{% info title="Backups are available on Appwrite Cloud for all Pro, Scale, and Enterprise customers." %} +{% info title="Backups are available on Appwrite Cloud for all Pro and Enterprise customers." %} {% /info %} Appwrite Backups allow you to automate database backups using backup policies, supporting pre-defined, custom retention & other options. You can also create manual backups whenever necessary. # Backup policies {% #backup-policies %} -Backup policies allow you to automate your backup process. The Scale and Enterprise plans allow for more customization and offer options like how often backups should occur, how long they should be retained, and when they should run. +Backup policies allow you to automate your backup process. The Enterprise plan allows for more customization and offers options like how often backups should occur, how long they should be retained, and when they should run. ## Creating a backup policy {% #creating-backup-policy %} @@ -40,21 +40,21 @@ To automate your database backups, you need to create backup policies that run a ![Pro plan policy](/images/docs/databases/pro-policy.avif) {% /only_light %} - * On **Scale** and **Enterprise** plans, you get access to more & custom policies\ + * On the **Enterprise** plan, you get access to more & custom policies\   * Select a pre-defined policy {% only_dark %} - ![Scale plan policies](/images/docs/databases/dark/scale-policies.avif) + ![Backup policies](/images/docs/databases/dark/scale-policies.avif) {% /only_dark %} {% only_light %} - ![Scale plan policies](/images/docs/databases/scale-policies.avif) + ![Backup policies](/images/docs/databases/scale-policies.avif) {% /only_light %} * Or create a custom policy and adjust the settings as you like {% only_dark %} - ![Custom policies for Scale plan](/images/docs/databases/dark/scale-custom-policies.avif) + ![Custom backup policies](/images/docs/databases/dark/scale-custom-policies.avif) {% /only_dark %} {% only_light %} - ![Custom policies for Scale plan](/images/docs/databases/scale-custom-policies.avif) + ![Custom backup policies](/images/docs/databases/scale-custom-policies.avif) {% /only_light %} 4. Click on **Create** diff --git a/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc index 07a93db88c0..d1574894303 100644 --- a/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc +++ b/src/routes/docs/products/databases/tablesdb/bulk-operations/+page.markdoc @@ -56,10 +56,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.createRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.createRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'Row 1' @@ -69,7 +69,7 @@ const result = await tablesDB.createRows( name: 'Row 2' } ] -); +}); ``` ```server-python @@ -155,16 +155,16 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.updateRows( - '<DATABASE_ID>', - '<TABLE_ID>', - { +const result = await tablesDB.updateRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + data: { status: 'published' }, - [ + queries: [ sdk.Query.equal('status', 'draft') ] -); +}); ``` ```server-python @@ -242,10 +242,10 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.upsertRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.upsertRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + rows: [ { $id: sdk.ID.unique(), name: 'New Row 1' @@ -255,7 +255,7 @@ const result = await tablesDB.upsertRows( name: 'New Row 2' } ] -); +}); ``` ```server-python @@ -342,13 +342,13 @@ const client = new sdk.Client() const tablesDB = new sdk.TablesDB(client); -const result = await tablesDB.deleteRows( - '<DATABASE_ID>', - '<TABLE_ID>', - [ +const result = await tablesDB.deleteRows({ + databaseId: '<DATABASE_ID>', + tableId: '<TABLE_ID>', + queries: [ sdk.Query.equal('status', 'archived') ] -); +}); ``` ```server-python diff --git a/static/images/docs/databases/documentsdb/backup-policy.avif b/static/images/docs/databases/documentsdb/backup-policy.avif new file mode 100644 index 0000000000000000000000000000000000000000..b44ed9afa6b3e8dbd4a45a3d1314fe7a17e6a405 GIT binary patch literal 10913 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 literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/backups-tab.avif b/static/images/docs/databases/documentsdb/backups-tab.avif new file mode 100644 index 0000000000000000000000000000000000000000..29aec532bc7a20143dbfba8f340e88218acaea61 GIT binary patch literal 26847 zcmXu}V~{Y+&NYmVZQHhO+qP|cuCZ;~wr$(CZGZQE&Uw?-Sy`!6CBM>LsQ>@~;F!61 zIvBcHngRSLww7j$|Fdl^4gbeiZB1Pa|L6Zt2rW!(oc@0%0Dyy~vCIFn{~y9RSi0E$ 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 J4&(`B2i)Q|INSgL literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/create-database.avif b/static/images/docs/databases/documentsdb/create-database.avif new file mode 100644 index 0000000000000000000000000000000000000000..4a201bf95547debc4f5f8d931de7205d21a73584 GIT binary patch literal 31320 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%0D85z4Fh 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 Bkput$ literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/dark/backup-policy.avif b/static/images/docs/databases/documentsdb/dark/backup-policy.avif new file mode 100644 index 0000000000000000000000000000000000000000..3e152f6732d9433fda4ff81a128f36f369f710df GIT binary patch literal 11350 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%0D4Hoc}@2Pe9?K-Q|_V?$R8!{Lq3@H_8eG2 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 F{|_n$*Mk56 literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/dark/backups-tab.avif b/static/images/docs/databases/documentsdb/dark/backups-tab.avif new file mode 100644 index 0000000000000000000000000000000000000000..b975ab60a2a1cc63ae0e2471459a75445a4ccb14 GIT binary patch literal 28535 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 literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/dark/create-database.avif b/static/images/docs/databases/documentsdb/dark/create-database.avif new file mode 100644 index 0000000000000000000000000000000000000000..8cfe52aa5ed6d505ef2ba868a2d912a96254e201 GIT binary patch literal 34142 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 BF*pDK literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/dark/manual-backup.avif b/static/images/docs/databases/documentsdb/dark/manual-backup.avif new file mode 100644 index 0000000000000000000000000000000000000000..091f73911ed07848eba43d1d18e6857783484a53 GIT binary patch literal 10490 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 literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb/manual-backup.avif b/static/images/docs/databases/documentsdb/manual-backup.avif new file mode 100644 index 0000000000000000000000000000000000000000..0dc1a93e6dbfa5870d407fe42bb3f109530c0d60 GIT binary patch literal 10120 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 Vl+cu#7+ZGm)Cah&Z{k7w|3AL_cIyBD literal 0 HcmV?d00001 From 424e023486b54d5513616b58f4d6a0dedaf332eb Mon Sep 17 00:00:00 2001 From: Atharva Deosthale <atharva.deosthale17@gmail.com> Date: Wed, 1 Jul 2026 19:19:53 +0530 Subject: [PATCH 3/5] Add VectorsDB documentation section --- .../databases/vectorsdb/+layout.svelte | 76 +- .../databases/vectorsdb/+page.markdoc | 24 +- .../databases/vectorsdb/backups/+page.markdoc | 89 + .../vectorsdb/bulk-operations/+page.markdoc | 475 +++++ .../vectorsdb/collections/+page.markdoc | 1144 ++++++++++ .../vectorsdb/csv-exports/+page.markdoc | 102 + .../vectorsdb/csv-imports/+page.markdoc | 118 ++ .../vectorsdb/databases/+page.markdoc | 975 +++++++++ .../vectorsdb/documents/+page.markdoc | 1541 ++++++++++++++ .../vectorsdb/embeddings/+page.markdoc | 612 ++++++ .../databases/vectorsdb/order/+page.markdoc | 1006 +++++++++ .../vectorsdb/pagination/+page.markdoc | 1041 +++++++++ .../vectorsdb/permissions/+page.markdoc | 1241 +++++++++++ .../databases/vectorsdb/queries/+page.markdoc | 1879 +++++++++++++++++ .../vectorsdb/quick-start/+page.markdoc | 1034 +++++++++ .../timestamp-overrides/+page.markdoc | 1302 ++++++++++++ .../vectorsdb/transactions/+page.markdoc | 1240 +++++++++++ .../vectorsdb/vector-search/+page.markdoc | 556 +++++ 18 files changed, 14451 insertions(+), 4 deletions(-) create mode 100644 src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/order/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc create mode 100644 src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc diff --git a/src/routes/docs/products/databases/vectorsdb/+layout.svelte b/src/routes/docs/products/databases/vectorsdb/+layout.svelte index bd502da28b6..174edc4ed4e 100644 --- a/src/routes/docs/products/databases/vectorsdb/+layout.svelte +++ b/src/routes/docs/products/databases/vectorsdb/+layout.svelte @@ -6,7 +6,7 @@ const parent: NavParent = { href: '/docs/products/databases', - label: 'Databases' + label: 'VectorsDB' }; const navigation: NavTree = [ @@ -16,6 +16,80 @@ { label: 'Overview', href: '/docs/products/databases/vectorsdb' + }, + { + label: 'Quick start', + href: '/docs/products/databases/vectorsdb/quick-start' + } + ] + }, + { + label: 'Concepts', + items: [ + { + label: 'Databases', + href: '/docs/products/databases/vectorsdb/databases' + }, + { + label: 'Collections', + href: '/docs/products/databases/vectorsdb/collections' + }, + { + label: 'Documents', + href: '/docs/products/databases/vectorsdb/documents' + }, + { + label: 'Embeddings', + href: '/docs/products/databases/vectorsdb/embeddings' + }, + { + label: 'Permissions', + href: '/docs/products/databases/vectorsdb/permissions' + }, + { + label: 'Queries', + href: '/docs/products/databases/vectorsdb/queries' + }, + { + label: 'Order', + href: '/docs/products/databases/vectorsdb/order' + }, + { + label: 'Backups', + href: '/docs/products/databases/vectorsdb/backups' + } + ] + }, + { + label: 'Journeys', + items: [ + { + label: 'Pagination', + href: '/docs/products/databases/vectorsdb/pagination' + }, + { + label: 'Vector search', + href: '/docs/products/databases/vectorsdb/vector-search' + }, + { + label: 'Transactions', + href: '/docs/products/databases/vectorsdb/transactions' + }, + { + label: 'Bulk operations', + href: '/docs/products/databases/vectorsdb/bulk-operations' + }, + { + label: 'Timestamp overrides', + href: '/docs/products/databases/vectorsdb/timestamp-overrides' + }, + { + label: 'CSV imports', + href: '/docs/products/databases/vectorsdb/csv-imports' + }, + { + label: 'CSV exports', + href: '/docs/products/databases/vectorsdb/csv-exports' } ] } diff --git a/src/routes/docs/products/databases/vectorsdb/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/+page.markdoc index f0391bc33d3..dcc7f83401f 100644 --- a/src/routes/docs/products/databases/vectorsdb/+page.markdoc +++ b/src/routes/docs/products/databases/vectorsdb/+page.markdoc @@ -4,8 +4,26 @@ title: VectorsDB description: Store vector embeddings and run similarity search with Appwrite VectorsDB to power semantic search, recommendations, and other AI features. --- -Appwrite VectorsDB stores vector embeddings and runs similarity search, so you can build semantic search, recommendations, and other AI features. It also exposes an endpoint to generate text embeddings, and shares the permissions, indexes, transactions, and realtime concepts used across Appwrite Databases. +Appwrite VectorsDB lets you store vector embeddings and run similarity search over them. +A collection is created with a fixed `dimension`, every document holds an `embeddings` vector of that length plus optional `metadata`, and an HNSW index keeps similarity search fast as your data grows. -{% info title="Documentation in progress" %} -Detailed VectorsDB guides are on the way. For now, the concepts in [TablesDB](/docs/products/databases/tablesdb) are the closest reference. +{% info title="Looking for file storage?" %} +Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). {% /info %} + +You organize data into databases, collections, and documents, the same way you do across Appwrite Databases. What sets VectorsDB apart is the fixed schema built for vectors and the ability to generate text embeddings and search by similarity. + +# Key concepts {% #key-concepts %} +[Collections](/docs/products/databases/vectorsdb/collections) are created with a required `dimension`, the length of the vectors they hold. Instead of a typed-attribute schema, every collection is provisioned with a fixed shape: a required `embeddings` vector and an optional `metadata` object. + +[Documents](/docs/products/databases/vectorsdb/documents) store a single embedding under `embeddings` and any associated JSON under `metadata`. The embedding length must match the collection's `dimension`. + +[Embeddings](/docs/products/databases/vectorsdb/embeddings) can be generated from text with built-in models, so you can turn strings into vectors and store them without running a separate embedding service. + +[Vector search](/docs/products/databases/vectorsdb/vector-search) ranks documents by how close their `embeddings` are to a query vector. You create an HNSW index on the `embeddings` field, then pass a vector query to list documents by cosine, dot product, or Euclidean distance. + +VectorsDB also shares the [permissions](/docs/products/databases/vectorsdb/permissions), [queries](/docs/products/databases/vectorsdb/queries), [pagination](/docs/products/databases/vectorsdb/pagination), [transactions](/docs/products/databases/vectorsdb/transactions), and [bulk operations](/docs/products/databases/vectorsdb/bulk-operations) used across Appwrite Databases. + +{% arrow_link href="/docs/products/databases/vectorsdb/quick-start" %} +Quick start +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc new file mode 100644 index 00000000000..745a382dc09 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/backups/+page.markdoc @@ -0,0 +1,89 @@ +--- +layout: article +title: Backups +description: Learn how to back up and restore your VectorsDB databases, ensuring data security and seamless recovery. +--- + +Appwrite Backups enable seamless, **encrypted** database backups. +All backups are **hot** backups, ensuring zero downtime and fast recovery. + +{% info title="Backups are available for all Pro and Enterprise customers." %} +{% /info %} + +You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. A backup captures the database along with its collections, documents, and embeddings. + +{% only_dark %} +![Backups tab](/images/docs/databases/documentsdb/dark/backups-tab.avif) +{% /only_dark %} +{% only_light %} +![Backups tab](/images/docs/databases/documentsdb/backups-tab.avif) +{% /only_light %} + +# Backup policies {% #backup-policies %} + +Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one. + +{% only_dark %} +![Create backup policy](/images/docs/databases/documentsdb/dark/backup-policy.avif) +{% /only_dark %} +{% only_light %} +![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif) +{% /only_light %} + +The available options depend on your plan: + +- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days. +- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained. + +Click **Create** to save the policy. Your database is now set up for automated backups. + +# Manual backups {% #manual-backups %} + +You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**. + +{% only_dark %} +![Manual backup](/images/docs/databases/documentsdb/dark/manual-backup.avif) +{% /only_dark %} +{% only_light %} +![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif) +{% /only_light %} + +Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list. + +# Restoring backups {% #restoring-backups %} + +To restore a database, you need an existing backup. + +1. Open your database's **Backups** tab. +2. In the backups list, open the **Actions** menu for the backup you want to restore. +3. Click **Restore**. +4. Enter a name for the new database and an optional database ID. +5. Click **Restore**. + +Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched. + +# Backup security & performance {% #backup-security-and-performance %} + +All backups created with Appwrite are: + +1. **Encrypted**: + All backups are securely encrypted to ensure your data remains protected at all times. + +2. **Remotely stored**: + Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable. + +3. **Hot backups**: + Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services. + +# Best practices {% #best-practices %} + +To ensure your backups are robust and effective, consider the following best practices: + +1. **Schedule regular backups**: + Add backup policies based on the frequency of database changes. Daily backups are often sufficient for most use cases. + +2. **Retain critical backups longer**: + Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed. + +3. **Optimize backup policies based on data sensitivity**: + Tailor your backup frequency and retention settings according to the sensitivity and importance of the data. diff --git a/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc new file mode 100644 index 00000000000..d7c7e15950e --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/bulk-operations/+page.markdoc @@ -0,0 +1,475 @@ +--- +layout: article +title: Bulk operations +description: Perform bulk operations on documents within your collections for efficient data handling in Appwrite VectorsDB. +--- + +Appwrite VectorsDB supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. + +Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. + +For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. + +Each document's data follows the fixed schema provisioned by its collection: an `embeddings` vector whose length must equal the collection's `dimension`, and an optional free-form `metadata` object. The examples on this page use a collection with `dimension: 4` to keep the arrays readable. + +{% info title="Important notes" %} +Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type. +{% /info %} + +# Atomic behavior {% #atomic-behavior %} + +Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. + +This atomicity ensures: +- **Data consistency**: Your database remains in a consistent state even if some operations would fail. +- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. +- **Simplified error handling**: You only need to handle complete success or complete failure scenarios. + +For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. + +# Plan limits {% #plan-limits %} + +Bulk operations have different limits based on your Appwrite plan: + +| Plan | Documents per request | +|------|----------------------| +| Free | 100 | +| Pro | 1,000 | + +These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan. + +# Create documents {% #create-documents %} + +You can create multiple documents in a single request using the `createDocuments` method. + +{% info title="Custom timestamps" %} +When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. +{% /info %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + $id: sdk.ID.unique(), + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', genre: 'tragedy' } + }, + { + $id: sdk.ID.unique(), + embeddings: [0.91, 0.22, 0.14, 0.65], + metadata: { title: 'Macbeth', genre: 'tragedy' } + } + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.create_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { + '$id': ID.unique(), + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' } + }, + { + '$id': ID.unique(), + 'embeddings': [0.91, 0.22, 0.14, 0.65], + 'metadata': { 'title': 'Macbeth', 'genre': 'tragedy' } + } + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "genre": "tragedy" } + }), + json!({ + "$id": ID::unique(), + "embeddings": [0.91, 0.22, 0.14, 0.65], + "metadata": { "title": "Macbeth", "genre": "tragedy" } + }), + ], + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Update documents {% #update-documents %} + +{% info title="Permissions required" %} +You must grant **update** permissions to users at the **collection level** before users can update documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can update multiple documents in a single request using the `updateDocuments` method. Pass the fields to change in `data`, and use `queries` to select which documents are affected. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + data: { + metadata: { genre: 'drama' } + }, + queries: [ + sdk.Query.equal('metadata.genre', 'tragedy') + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.update_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + data = { + 'metadata': { 'genre': 'drama' } + }, + queries = [ + Query.equal('metadata.genre', 'tragedy') + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(json!({ + "metadata": { "genre": "drama" } + })), + Some(vec![ + Query::equal("metadata.genre", "tragedy").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Upsert documents {% #upsert-documents %} + +{% info title="Permissions required" %} +You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can upsert multiple documents in a single request using the `upsertDocuments` method. Documents with a new `$id` are created, while documents with an existing `$id` are updated. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + $id: sdk.ID.unique(), + embeddings: [0.40, 0.40, 0.40, 0.40], + metadata: { title: 'Othello', genre: 'tragedy' } + }, + { + $id: 'document-id-2', // Existing document ID + embeddings: [0.10, 0.10, 0.10, 0.10], + metadata: { title: 'Hamlet', genre: 'tragedy' } + } + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.upsert_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { + '$id': ID.unique(), + 'embeddings': [0.40, 0.40, 0.40, 0.40], + 'metadata': { 'title': 'Othello', 'genre': 'tragedy' } + }, + { + '$id': 'document-id-2', # Existing document ID + 'embeddings': [0.10, 0.10, 0.10, 0.10], + 'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' } + } + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.40, 0.40, 0.40, 0.40], + "metadata": { "title": "Othello", "genre": "tragedy" } + }), + json!({ + "$id": "document-id-2", // Existing document ID + "embeddings": [0.10, 0.10, 0.10, 0.10], + "metadata": { "title": "Hamlet", "genre": "tragedy" } + }), + ], + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +# Delete documents {% #delete-documents %} + +{% info title="Permissions required" %} +You must grant **delete** permissions to users at the **collection level** before users can delete documents. +[Learn more about permissions](/docs/products/databases/vectorsdb/permissions) +{% /info %} + +You can delete multiple documents in a single request using the `deleteDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.equal('metadata.genre', 'drama') + ] +}); +``` + +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.equal('metadata.genre', 'drama') + ] +) +``` + +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.delete_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::equal("metadata.genre", "drama").to_string(), + ]), + None, + ).await?; + + Ok(()) +} +``` + +{% /multicode %} + +{% info title="Queries for deletion" %} + +When deleting documents, you must specify queries to filter which documents to delete. +If no queries are provided, all documents in the collection will be deleted. +[Learn more about queries](/docs/products/databases/vectorsdb/queries). + +{% /info %} + +# Use transactions {% #use-transactions %} + +`updateDocuments`, `upsertDocuments`, and `deleteDocuments` accept a `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/vectorsdb/transactions). + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { $id: sdk.ID.unique(), embeddings: [0.12, 0.84, 0.33, 0.57], metadata: { title: 'One' } }, + { $id: sdk.ID.unique(), embeddings: [0.91, 0.22, 0.14, 0.65], metadata: { title: 'Two' } } + ], + transactionId: '<TRANSACTION_ID>' +}); +``` +```python +vectors_db.upsert_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + documents = [ + { '$id': ID.unique(), 'embeddings': [0.12, 0.84, 0.33, 0.57], 'metadata': { 'title': 'One' } }, + { '$id': ID.unique(), 'embeddings': [0.91, 0.22, 0.14, 0.65], 'metadata': { 'title': 'Two' } } + ], + transaction_id = '<TRANSACTION_ID>' +) +``` +```server-rust +let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "One" } + }), + json!({ + "$id": ID::unique(), + "embeddings": [0.91, 0.22, 0.14, 0.65], + "metadata": { "title": "Two" } + }), + ], + Some("<TRANSACTION_ID>"), +).await?; +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc new file mode 100644 index 00000000000..bf246bde573 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/collections/+page.markdoc @@ -0,0 +1,1144 @@ +--- +layout: article +title: Collections +description: Organize embeddings with Appwrite VectorsDB collections. Learn how to create collections with a fixed dimension, manage them, and configure permissions. +--- +Appwrite uses collections as containers of documents. +A VectorsDB collection stores embeddings, so every collection is created with a fixed **`dimension`**, the length of the embedding vectors it holds. All documents in the collection must use vectors of that exact length. + +Unlike TablesDB, you don't define a schema for a VectorsDB collection. The schema is fixed and provisioned for you when the collection is created: + +| Attribute | Type | Description | +|--------------|----------|-----------------------------------------------------------------------------| +| `embeddings` | `vector` | The embedding vector. Required, and sized to the collection's `dimension`. | +| `metadata` | `object` | Arbitrary JSON stored alongside the vector. Optional. | + +Because the schema is fixed, VectorsDB has no typed-attribute endpoints like TablesDB columns. You cannot add, update, or delete attributes on a collection. You store your vector under `embeddings` and any associated data under `metadata`. + +# Create collection {% #create-collection %} +You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). + +Head to the **Databases** page, open a [database](/docs/products/databases/vectorsdb/databases), and click **Create collection**. Set a **dimension** that matches the embedding model you plan to use. + +You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +The `dimension` is required and must match the length of the vectors you'll store. For example, the default `nomic-embed-text` model produces 768-dimensional vectors, so you would create the collection with `dimension: 768`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [sdk.Permission.read(sdk.Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission::read(Role::any())], // optional + documentSecurity: false, // optional + enabled: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = '<NAME>', + dimension = 768, + permissions = [Permission.read(Role.any())], # optional + document_security = False, # optional + enabled = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission.read(Role.any())], # optional + document_security: false, # optional + enabled: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + dimension: 768, + permissions: new List<string> { Permission.Read(Role.Any()) }, // optional + documentSecurity: false, // optional + enabled: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + dimension: 768, + permissions: [Permission.read(Role.any())], // (optional) + documentSecurity: false, // (optional) + enabled: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "<NAME>", + dimension = 768, + permissions = listOf(Permission.read(Role.any())), // optional + documentSecurity = false, // optional + enabled = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + 768, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + dimension: 768, + permissions: [Permission.read(Role.any())], // optional + documentSecurity: false, // optional + enabled: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + 768, + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + Some(false), // documentSecurity (optional) + Some(true), // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "<NAME>" \ + --dimension 768 +``` +{% /multicode %} + +{% info title="Dimension is fixed" %} +The `dimension` is set when the collection is created and applies to every document in it. Choose a value that matches your embedding model, between 1 and 16000. To store vectors of a different length, create a separate collection. +{% /info %} + +# Schema {% #schema %} +A VectorsDB collection's schema is provisioned automatically and cannot be changed. Every collection has exactly two attributes: + +- `embeddings`, a required `vector` attribute sized to the collection's `dimension`. +- `metadata`, an optional `object` attribute that holds arbitrary JSON. + +A document's `data` therefore looks like this, where the vector length equals the collection's `dimension`: + +```json +{ + "embeddings": [0.12, 0.84, 0.05, 0.63], + "metadata": { "title": "Introduction", "source": "docs" } +} +``` + +An `object` index on `metadata` is also created for you. There are no endpoints to add, modify, or remove attributes. To learn how to write and read this data, see [Documents](/docs/products/databases/vectorsdb/documents). + +# Manage collections {% #manage-collections %} +Use a [Server SDK](/docs/sdks#server) to list, retrieve, update, and delete collections. + +## List collections {% #list-collections %} +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listCollections({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listCollections({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listCollections( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_collections( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_collections( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +CollectionList result = await vectorsDB.ListCollections( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +CollectionList result = await vectorsDB.listCollections( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listCollections( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listCollections( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collectionList = try await vectorsDB.listCollections( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_collections( + "<DATABASE_ID>", + None, // queries (optional) + None, // search (optional) + None, // total (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-collections \ + --database-id "<DATABASE_ID>" +``` +{% /multicode %} + +## Get collection {% #get-collection %} +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->getCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.GetCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.getCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.getCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.getCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" +``` +{% /multicode %} + +## Update collection {% #update-collection %} +You can rename a collection and change its permissions, `documentSecurity`, or `enabled` state. The `name` is required when updating. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = '<NAME>', + document_security = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: '<NAME>', + document_security: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.UpdateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + documentSecurity: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: '<NAME>', + documentSecurity: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "<NAME>", + documentSecurity = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.updateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "<NAME>", + documentSecurity: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<NAME>", + None, // dimension (optional) + None, // permissions (optional) + Some(true), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "<NAME>" +``` +{% /multicode %} + +## Delete collection {% #delete-collection %} +Deleting a collection permanently removes it and all of its documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->deleteCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.DeleteCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.deleteCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.deleteCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.deleteCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" +``` +{% /multicode %} + +# Permissions {% #permissions %} +Appwrite uses permissions to control data access. +For security, only users that are granted permissions can access a resource. + +By default, Appwrite doesn't grant permissions to any users when a new collection is created. +This means users can't create documents or read, update, and delete existing documents until you grant access. + +Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document. + +[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions). diff --git a/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc new file mode 100644 index 00000000000..c80841bc888 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/csv-exports/+page.markdoc @@ -0,0 +1,102 @@ +--- +layout: article +title: CSV exports +description: Export VectorsDB documents to a CSV file. Share embeddings and metadata as a portable dataset without writing custom scripts. +--- + +Appwrite's CSV export feature lets you export documents from a VectorsDB collection to a CSV file. This is useful for reporting, sharing a dataset with your team, creating custom backups, or handing embeddings and their metadata off to other tools. + +# Exported columns {% #exported-columns %} + +A VectorsDB collection has a fixed schema, so every export has the same shape. Each row carries the document's system fields together with the two collection attributes: + +| Column | Type | Description | +|--------------|--------|--------------------------------------------------------------------------| +| `$id` | string | The document ID. | +| `embeddings` | JSON | The embedding vector, serialized as a JSON array of numbers. | +| `metadata` | JSON | The metadata stored alongside the vector, serialized as a JSON object. | + +System columns like `$id`, `$createdAt`, and `$updatedAt` are included automatically. Because `embeddings` is the full vector for each document, exported files can be large for high dimension collections. + +An example of exported data, where each `embeddings` value is the full vector for that document: + +```text +$id,embeddings,metadata +doc-1,"[0.12,0.84,0.33,0.57]","{""title"":""Hamlet"",""year"":1601}" +doc-2,"[0.2,0.1,0.6,0.1]","{""title"":""Macbeth"",""year"":1606}" +doc-3,"[0.5,0.5,0.5,0.5]","{""title"":""Othello"",""year"":1603}" +``` + +# Export configuration {% #export-configuration %} + +Before exporting, you can configure several options to control the output format and contents. + +## Apply filters {% #apply-filters %} + +You can pass [queries](/docs/products/databases/vectorsdb/queries) to export only the documents you need rather than the whole collection. This is useful when you want a subset of your data for a specific use case. + +## Select columns {% #select-columns %} + +You can choose which columns to include in the export. By default, all columns are exported. Selecting specific columns creates more focused datasets, for example exporting only `metadata` when you don't need the raw vectors. + +## Custom delimiter {% #custom-delimiter %} + +You can set a custom delimiter for the CSV file. While commas are standard, you can use tabs, semicolons, or other delimiters based on the tools you import into. + +Common delimiters: +- **Comma (`,`)**: Standard format, compatible with most tools +- **Tab**: Useful when your data contains many commas +- **Semicolon (`;`)**: Common in European Excel versions +- **Pipe (`|`)**: Useful when your data contains many semicolons + +## Header row {% #header-row %} + +You can choose whether to include a header row with column names. Headers make the data easier to read in spreadsheets, but some import tools work better without them. + +# Timestamps {% #timestamps %} + +The `$createdAt` and `$updatedAt` columns are exported in ISO 8601 format, making them compatible with most spreadsheet and database tools. + +# Permissions {% #permissions %} + +If [document security](/docs/products/databases/vectorsdb/permissions) is enabled for your collection, the `$permissions` column is included in the export. Permission strings are formatted as comma-separated role definitions within quotes. + +```text +$id,embeddings,$permissions +doc-1,"[0.12,0.84,0.33,0.57]","read(""any""),update(""user:user-123"")" +doc-2,"[0.2,0.1,0.6,0.1]","read(""team:team-456""),update(""team:team-456"")" +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/products/databases/vectorsdb/permissions). + +# Background processing {% #background-processing %} + +Large exports run as background tasks so they don't block your workflow. When an export completes, you receive an email with a short-lived download link to retrieve your CSV file. + +This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active. + +# Use cases {% #use-cases %} + +CSV exports are useful for many common workflows: + +- **Reporting**: Generate reports for stakeholders who need data in spreadsheet format +- **Data sharing**: Share an embeddings dataset with teammates +- **Analytics hand-off**: Provide vectors and metadata to analysts using other tools +- **Custom backups**: Archive specific data subsets for record-keeping +- **Migration preparation**: Extract data to move it into another system + +# Best practices {% #best-practices %} + +To get the most out of CSV exports: + +1. **Filter your data**: Export only the documents you need to reduce file size and processing time +2. **Select specific columns**: Export only `metadata` when you don't need the raw vectors, since high dimension `embeddings` columns make files much larger +3. **Choose appropriate delimiters**: Use tabs or semicolons if your data contains many commas +4. **Consider header requirements**: Include headers for human readability, exclude them for automated imports + +# Additional resources {% #additional-resources %} + +- [CSV imports](/docs/products/databases/vectorsdb/csv-imports) - Import data from CSV files +- [Documents](/docs/products/databases/vectorsdb/documents) - Create, read, update, and delete documents +- [Permissions](/docs/products/databases/vectorsdb/permissions) - Configure document-level security +- [Backups](/docs/products/databases/vectorsdb/backups) - Automated backup policies diff --git a/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc new file mode 100644 index 00000000000..6e94bf6e5d4 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/csv-imports/+page.markdoc @@ -0,0 +1,118 @@ +--- +layout: article +title: CSV imports +description: Import embeddings into Appwrite VectorsDB by uploading a CSV file. Learn how to format the embeddings and metadata columns for a bulk import. +--- + +Appwrite's CSV Import feature allows you to create multiple documents in a collection by uploading a single CSV file. This is especially useful for loading precomputed embeddings, seeding test environments, or migrating vectors from another system. + +# Prepare your CSV {% #prepare-csv %} + +A VectorsDB collection has a fixed schema, so every CSV maps to the same two columns: + +| Column | Type | Description | +|--------------|----------|----------------------------------------------------------------------------------| +| `embeddings` | `vector` | The embedding vector, written as a JSON array. Required, and its length must equal the collection's `dimension`. | +| `metadata` | `object` | Arbitrary JSON stored alongside the vector, written as a JSON object. Optional. | + +Each row represents a new document. The `embeddings` value is parsed as a JSON array of numbers, and the `metadata` value is parsed as a JSON object. Each row is validated before being imported. + +{% info title="Good to know" %} +You can optionally include the `$id` column to define custom document IDs. If not provided, Appwrite will generate unique IDs for each document automatically. + +Appwrite imports documents in batches of 100 documents at a time. If a provided ID already exists in the collection, the entire batch containing that document will fail, but documents in other batches will continue to be imported successfully. +{% /info %} + +An example of a valid CSV file for a collection created with `dimension: 4`: + +```text +$id,embeddings,metadata +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector"",""year"":2024}" +vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector"",""year"":2025}" +vec_3,"[0.20,0.20,0.20,0.20]","{""title"":""Third vector"",""year"":2025}" +``` + +The double quotes around each value let you include the commas inside the JSON array and object. The inner double quotes of the JSON keys and string values are escaped by doubling them (`""`). See [Special characters](#special-characters) for the escaping rules. + +{% info title="Vector length" %} +Every `embeddings` array must contain exactly as many numbers as the collection's `dimension`. A row whose vector length does not match the dimension is rejected. +{% /info %} + +# Metadata values {% #metadata-values %} + +The `metadata` column is an object, so each value must be a valid JSON object or the literal `null`: + +- **A JSON object**, for example `"{""key"":""value""}"`. An empty object `"{}"` is also valid. +- **`null`** (the unquoted literal) stores no metadata for that document. + +A blank `metadata` value is not accepted. To omit metadata for a row, use `null` rather than leaving the field empty: + +```text +$id,embeddings,metadata +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""With metadata""}" +vec_2,"[0.55,0.61,0.07,0.42]",null +``` + +# Create and update timestamps {% #created-at-and-updated-at %} + +You can also optionally include `$createdAt` and `$updatedAt` columns to set custom timestamps for imported documents. If omitted, Appwrite sets these automatically during import. + +An example of a valid CSV file with `$createdAt` and `$updatedAt` timestamps: + +```text +$id,$createdAt,$updatedAt,embeddings,metadata +vec_1,2025-08-10T12:34:56.000Z,2025-08-10T12:34:56.000Z,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}" +vec_2,2025-08-11T09:15:00.000Z,2025-08-11T10:00:00.000Z,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}" +``` + +{% info title="Timestamps format" %} +`$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`. +{% /info %} + +# Permissions {% #permissions %} + +You can set permissions for documents in your CSV file by adding data for the `$permissions` column. Make sure document level security is enabled for your collection. + +An example of a valid permissions string: + +```text +"read(""any""),update(""users""),delete(""user:user_id"")" +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +A full example of a valid CSV file with document permissions: + +```text +$id,embeddings,metadata,$permissions +vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}","read(""any""),update(""user:user_id""),delete(""user:user_id"")" +vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}","read(""users""),update(""user:user_id"")" +``` + +# Special characters {% #special-characters %} + +The `embeddings` and `metadata` values are JSON written inside CSV fields, so you need to escape the characters that CSV treats specially. + +## Comma + +The JSON array and object both contain commas, so wrap each value in double quotes (`"[0.1,0.2,0.3,0.4]"`). Without the surrounding quotes, the commas would be read as column separators. + +## Double quotes + +JSON keys and string values use double quotes, and a double quote inside a quoted CSV field must be escaped by doubling it (`""`). For example, the object `{"title":"hello"}` is written in the CSV as `"{""title"":""hello""}"`. + +# Run the import {% #run-import %} + +CSV imports run as a background migration. First upload your CSV to a [storage bucket](/docs/products/storage/buckets), then start the import against the target collection. The import is triggered through the migrations endpoint at `POST /v1/migrations/csv/imports` with an [API key](/docs/advanced/platform/api-keys) that has the `migrations.write` scope. The request takes the storage `bucketId` and `fileId` that hold your CSV, and the target collection as a composite `resourceId` in the form `databaseId:collectionId`. + +The `onDuplicate` option controls what happens when a document with an existing `$id` is encountered: `fail` (the default) aborts on the first conflict, `skip` ignores it, and `overwrite` replaces the existing document. + +{% info title="Console import" %} +The VectorsDB collection view in the Appwrite Console offers JSON import and export. To import a CSV into a VectorsDB collection, use the migrations endpoint described above. +{% /info %} + +# Additional resources {% #additional-resources %} + +- [Appwrite CLI](/docs/command-line) +- [Database permissions](/docs/products/databases/vectorsdb/permissions) +- [Embeddings](/docs/products/databases/vectorsdb/embeddings) diff --git a/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc new file mode 100644 index 00000000000..7802e672768 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/databases/+page.markdoc @@ -0,0 +1,975 @@ +--- +layout: article +title: Databases +description: Dive deeper into Appwrite VectorsDB and database configuration. Learn how to create and manage multiple vector databases for your application. +--- +Databases are the largest organizational unit in Appwrite. +Each database contains a group of [collections](/docs/products/databases/vectorsdb/collections). + +# Shared and dedicated databases {% #shared-and-dedicated %} +VectorsDB databases run on either shared or dedicated infrastructure. + +Shared databases run on infrastructure that Appwrite manages and scales for you. They are the fastest way to get started and you can create them from the Console or programmatically with a [Server SDK](/docs/sdks#server). + +Dedicated databases run on infrastructure provisioned for your project alone. They can only be created from the Appwrite Console. Once created, you manage their collections and documents with the same SDK methods as a shared database. + +# Create in Console {% #create-in-console %} +The easiest way to create a database is using the Appwrite Console. +Navigate to the **Databases** page and click **Create database**, choose **VectorsDB** as the database type, and select your preferred tier. + +# Create using Server SDKs {% #create-using-server-sdks %} +You can programmatically create a shared database using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->create( + databaseId: '<DATABASE_ID>', + name: '<NAME>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create( + database_id = '<DATABASE_ID>', + name = '<NAME>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create( + database_id: '<DATABASE_ID>', + name: '<NAME>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Create( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.create( + databaseId: '<DATABASE_ID>', + name: '<NAME>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.create( + databaseId = "<DATABASE_ID>", + name = "<NAME>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.create( + "<DATABASE_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.create( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create( + "<DATABASE_ID>", + "<NAME>", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create \ + --database-id <DATABASE_ID> \ + --name <NAME> +``` +{% /multicode %} + +# List databases {% #list-databases %} +Use the `list` method to retrieve all databases in your project. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.list({}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.list({}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->list(); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list() +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list() +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DatabaseList result = await vectorsDB.List(); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DatabaseList result = await vectorsDB.list(); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.list() +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.list( + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let databaseList = try await vectorsDB.list() +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list( + None, // queries (optional) + None, // search (optional) + None, // total (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list +``` +{% /multicode %} + +# Get a database {% #get-database %} +Use the `get` method to retrieve a single database by its ID. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.get({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.get({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->get( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Get( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.get( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.get( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.get( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.get( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get( + "<DATABASE_ID>", + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get \ + --database-id <DATABASE_ID> +``` +{% /multicode %} + +# Update a database {% #update-database %} +Use the `update` method to change a database's name or enabled state. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.update({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.update({ + databaseId: '<DATABASE_ID>', + name: '<NAME>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->update( + databaseId: '<DATABASE_ID>', + name: '<NAME>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update( + database_id = '<DATABASE_ID>', + name = '<NAME>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update( + database_id: '<DATABASE_ID>', + name: '<NAME>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Update( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.update( + databaseId: '<DATABASE_ID>', + name: '<NAME>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.update( + databaseId = "<DATABASE_ID>", + name = "<NAME>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.update( + "<DATABASE_ID>", + "<NAME>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.update( + databaseId: "<DATABASE_ID>", + name: "<NAME>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update( + "<DATABASE_ID>", + "<NAME>", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update \ + --database-id <DATABASE_ID> \ + --name <NAME> +``` +{% /multicode %} + +# Delete a database {% #delete-database %} +Use the `delete` method to permanently remove a database and all of its collections and documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.delete({ + databaseId: '<DATABASE_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.delete({ + databaseId: '<DATABASE_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->delete( + databaseId: '<DATABASE_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete( + database_id = '<DATABASE_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete( + database_id: '<DATABASE_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.Delete( + databaseId: "<DATABASE_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.delete( + databaseId: '<DATABASE_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.delete( + databaseId = "<DATABASE_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.delete( + "<DATABASE_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.delete( + databaseId: "<DATABASE_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete( + "<DATABASE_ID>", + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete \ + --database-id <DATABASE_ID> +``` +{% /multicode %} diff --git a/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc new file mode 100644 index 00000000000..ea820edce39 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/documents/+page.markdoc @@ -0,0 +1,1541 @@ +--- +layout: article +title: Documents +description: Create, read, update, and delete documents in Appwrite VectorsDB. Learn how to store embedding vectors and metadata in your collections. +--- +Each piece of data in Appwrite VectorsDB is a document. +A document's data follows the fixed schema provisioned by its collection: an `embeddings` vector and an optional `metadata` object. + +The `embeddings` array is required, and its length must equal the `dimension` you set when [creating the collection](/docs/products/databases/vectorsdb/collections). +The `metadata` field is free-form JSON, so you can attach any data you want to keep alongside each vector. + +{% info title="Embedding length must match the dimension" %} +If the `embeddings` array is longer or shorter than the collection's `dimension`, the request is rejected. +The examples on this page use a collection with `dimension: 4` to keep the arrays readable. +{% /info %} + +# Create documents {% #create-documents %} +{% info title="Permissions required" %} +You must grant _create_ permissions to users at the _collection level_ before users can create documents. +[Learn more about permissions](#permissions) +{% /info %} + +Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', year: 1601 } + }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet', year: 1601 } + }, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet', 'year' => 1601] + ], + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Hamlet", "year" => 1601 } + }, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1601 } } } + }, + permissions: new List<string> { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }, + permissions: [Permission.read(Role.any())], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Hamlet", "year" to 1601) + ), + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Hamlet", "year", 1601) + ), + List.of(Permission.read(Role.any())), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet", "year": 1601] + ], + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet", "year": 1601 } + }), + Some(vec![Permission::read(Role::any()).to_string()]), // optional + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet", "year": 1601 } }' +``` +{% /multicode %} + +To insert many documents in a single request, use [bulk operations](/docs/products/databases/vectorsdb/bulk-operations) instead of calling `createDocument` in a loop. + +# Get document {% #get-document %} +Use the `getDocument` method to read a single document by its ID. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.get_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.get_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.GetDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.getDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.getDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.get_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // queries (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db get-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> +``` +{% /multicode %} + +# List documents {% #list-documents %} +Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/vectorsdb/queries) to filter, order, and paginate the results. + +To rank documents by similarity to a query vector, use a vector search query instead. See [Vector search](/docs/products/databases/vectorsdb/vector-search). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(10) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(10) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(10) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(10) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(10) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(10) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(10).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[10]}' +``` +{% /multicode %} + +# Update document {% #update-document %} +Use the `updateDocument` method to update a document. The update is a patch, so you only pass the fields you want to change. To change the vector, pass a new `embeddings` array of the same length as the collection's `dimension`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.20, 0.10, 0.60, 0.10], + metadata: { title: 'Hamlet', year: 1602 } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.20, 0.10, 0.60, 0.10], + metadata: { title: 'Hamlet', year: 1602 } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + 'embeddings' => [0.20, 0.10, 0.60, 0.10], + 'metadata' => ['title' => 'Hamlet', 'year' => 1602] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + "embeddings" => [0.20, 0.10, 0.60, 0.10], + "metadata" => { "title" => "Hamlet", "year" => 1602 } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.20, 0.10, 0.60, 0.10 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf( + "embeddings" to listOf(0.20, 0.10, 0.60, 0.10), + "metadata" to mapOf("title" to "Hamlet", "year" to 1602) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of( + "embeddings", List.of(0.20, 0.10, 0.60, 0.10), + "metadata", Map.of("title", "Hamlet", "year", 1602) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": ["title": "Hamlet", "year": 1602] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "embeddings": [0.20, 0.10, 0.60, 0.10], + "metadata": { "title": "Hamlet", "year": 1602 } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "embeddings": [0.20, 0.10, 0.60, 0.10], "metadata": { "title": "Hamlet", "year": 1602 } }' +``` +{% /multicode %} + +# Upsert document {% #upsert-document %} +Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.50, 0.50, 0.50, 0.50], + metadata: { title: 'Hamlet', year: 1603 } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + embeddings: [0.50, 0.50, 0.50, 0.50], + metadata: { title: 'Hamlet', year: 1603 } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + 'embeddings' => [0.50, 0.50, 0.50, 0.50], + 'metadata' => ['title' => 'Hamlet', 'year' => 1603] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.upsert_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + "embeddings" => [0.50, 0.50, 0.50, 0.50], + "metadata" => { "title" => "Hamlet", "year" => 1603 } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.50, 0.50, 0.50, 0.50 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1603 } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.upsertDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf( + "embeddings" to listOf(0.50, 0.50, 0.50, 0.50), + "metadata" to mapOf("title" to "Hamlet", "year" to 1603) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.upsertDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of( + "embeddings", List.of(0.50, 0.50, 0.50, 0.50), + "metadata", Map.of("title", "Hamlet", "year", 1603) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": ["title": "Hamlet", "year": 1603] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "embeddings": [0.50, 0.50, 0.50, 0.50], + "metadata": { "title": "Hamlet", "year": 1603 } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db upsert-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "embeddings": [0.50, 0.50, 0.50, 0.50], "metadata": { "title": "Hamlet", "year": 1603 } }' +``` +{% /multicode %} + +# Delete document {% #delete-document %} +Use the `deleteDocument` method to remove a document from a collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.delete_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.delete_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +await vectorsDB.DeleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +await vectorsDB.deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +vectorsDB.deleteDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println("Deleted"); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +try await vectorsDB.deleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + vectors_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // transactionId (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db delete-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> +``` +{% /multicode %} + +# Permissions {% #permissions %} +To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions. +By default, Appwrite doesn't grant any user permissions when a new collection is created. + +You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents. + +[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions). + +# Next steps {% #next-steps %} + +Continue learning with these related guides: + +{% cards %} +{% cards_item href="/docs/products/databases/vectorsdb/vector-search" title="Vector search" %} +Rank documents by similarity to a query vector using cosine, dot product, or Euclidean distance. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/vectorsdb/bulk-operations" title="Bulk operations" %} +Create, update, upsert, and delete many documents in a single request. +{% /cards_item %} + +{% cards_item href="/docs/products/databases/vectorsdb/collections" title="Collections" %} +Configure the dimension and indexes that define how your vectors are stored and searched. +{% /cards_item %} +{% /cards %} diff --git a/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc new file mode 100644 index 00000000000..5730c4365fc --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/embeddings/+page.markdoc @@ -0,0 +1,612 @@ +--- +layout: article +title: Embeddings +description: Generate text embeddings with Appwrite VectorsDB. Turn text into vector embeddings with built-in models and store them in your documents for vector search. +--- +An embedding is a list of numbers that represents the meaning of a piece of text. +VectorsDB can generate embeddings for you with built-in models, so you can turn text into vectors and store them in a collection without running a separate embedding service. + +The typical flow is two steps: generate an embedding from your text, then store that embedding in a document's `embeddings` field. Once stored, you can run [vector search](/docs/products/databases/vectorsdb/vector-search) over your documents. + +# Generate embeddings {% #generate-embeddings %} +Use the `createTextEmbeddings` method to turn one or more strings into vector embeddings. Pass an array of `texts` and, optionally, a `model`. When you omit `model`, VectorsDB uses the default `nomic-embed-text` model. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createTextEmbeddings({ + texts: ['The quick brown fox jumps over the lazy dog'], + model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Enums\EmbeddingModel; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMICEMBEDTEXT() // optional, defaults to nomic-embed-text +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.enums import EmbeddingModel + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_text_embeddings( + texts = ['The quick brown fox jumps over the lazy dog'], + model = EmbeddingModel.NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_text_embeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel::NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +EmbeddingList result = await vectorsDB.CreateTextEmbeddings( + texts: new List<string> { "The quick brown fox jumps over the lazy dog" }, + model: EmbeddingModel.NomicEmbedText // optional, defaults to nomic-embed-text +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +EmbeddingList result = await vectorsDB.createTextEmbeddings( + texts: ['The quick brown fox jumps over the lazy dog'], + model: EmbeddingModel.nomicEmbedText, // optional, defaults to nomic-embed-text +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.EmbeddingModel +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createTextEmbeddings( + texts = listOf("The quick brown fox jumps over the lazy dog"), + model = EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.EmbeddingModel; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTextEmbeddings( + List.of("The quick brown fox jumps over the lazy dog"), + EmbeddingModel.NOMIC_EMBED_TEXT, // optional, defaults to nomic-embed-text + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let embeddingList = try await vectorsDB.createTextEmbeddings( + texts: ["The quick brown fox jumps over the lazy dog"], + model: .nomicEmbedText // optional, defaults to nomic-embed-text +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::enums::EmbeddingModel; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_text_embeddings( + vec!["The quick brown fox jumps over the lazy dog"], + Some(EmbeddingModel::NomicEmbedText), // optional, defaults to nomic-embed-text + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-text-embeddings \ + --texts 'The quick brown fox jumps over the lazy dog' \ + --model 'nomic-embed-text' +``` +{% /multicode %} + +The response is an embedding list. The `embeddings` array holds one entry per input text, in the same order you passed them. + +```json +{ + "total": 1, + "embeddings": [ + { + "model": "nomic-embed-text", + "dimension": 768, + "embedding": [-0.012246467, 0.02621112, -0.15247375, ...], + "error": "" + } + ] +} +``` + +Each entry contains: + +| Field | Description | +| --- | --- | +| `model` | The model that generated this embedding. | +| `dimension` | The number of values in the embedding vector. | +| `embedding` | The embedding vector as an array of floats. If generation fails, this is an empty array. | +| `error` | An error message if this text could not be embedded. An empty string means there was no error. | + +# Available models {% #available-models %} +VectorsDB ships with the following text embedding models. The `dimension` of a model is the length of the vector it produces, and it must match the `dimension` you set on the [collection](/docs/products/databases/vectorsdb/collections) where you store the embeddings. + +| Model | Dimension | Notes | +| --- | --- | --- | +| `nomic-embed-text` | 768 | Default model, used when you omit `model`. | +| `embedding-gemma` | 768 | | +| `all-minilm` | 384 | | +| `bge-small` | 384 | | + +{% info title="Match the model to your collection dimension" %} +Pick a model before you create your collection, then set the collection's `dimension` to that model's dimension. All documents in a collection must use vectors of the same dimension, so a single collection works with models of one dimension only. +{% /info %} + +# Store embeddings {% #store-embeddings %} +Once you have an embedding, store it in a document's `embeddings` field. The collection's `dimension` must match the embedding's `dimension`. You can store any related data alongside the vector in the document's `metadata` field. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const text = 'The quick brown fox jumps over the lazy dog'; + +const embeddings = await vectorsDB.createTextEmbeddings({ + texts: [text] +}); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: embeddings.embeddings[0].embedding, + metadata: { text } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const text = 'The quick brown fox jumps over the lazy dog'; + +const embeddings = await vectorsDB.createTextEmbeddings({ + texts: [text] +}); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: embeddings.embeddings[0].embedding, + metadata: { text } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$text = 'The quick brown fox jumps over the lazy dog'; + +$embeddings = $vectorsDB->createTextEmbeddings( + texts: [$text] +); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => $embeddings['embeddings'][0]['embedding'], + 'metadata' => ['text' => $text] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +text = 'The quick brown fox jumps over the lazy dog' + +embeddings = vectors_db.create_text_embeddings( + texts = [text] +) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": embeddings["embeddings"][0]["embedding"], + "metadata": { "text": text } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +text = 'The quick brown fox jumps over the lazy dog' + +embeddings = vectors_db.create_text_embeddings( + texts: [text] +) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => embeddings.embeddings[0].embedding, + "metadata" => { "text" => text } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +var text = "The quick brown fox jumps over the lazy dog"; + +EmbeddingList embeddings = await vectorsDB.CreateTextEmbeddings( + texts: new List<string> { text } +); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new { + embeddings = embeddings.Embeddings[0].Embedding, + metadata = new { text } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +final text = 'The quick brown fox jumps over the lazy dog'; + +EmbeddingList embeddings = await vectorsDB.createTextEmbeddings( + texts: [text], +); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": embeddings.embeddings[0].embedding, + "metadata": { "text": text } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val text = "The quick brown fox jumps over the lazy dog" + +val embeddings = vectorsDB.createTextEmbeddings( + texts = listOf(text), +) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to embeddings.embeddings[0].embedding, + "metadata" to mapOf("text" to text) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +String text = "The quick brown fox jumps over the lazy dog"; + +vectorsDB.createTextEmbeddings( + List.of(text), + new CoroutineCallback<>((embeddings, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", embeddings.getEmbeddings().get(0).getEmbedding(), + "metadata", Map.of("text", text) + ), + new CoroutineCallback<>((result, err) -> { + if (err != null) { + err.printStackTrace(); + return; + } + + System.out.println(result); + }) + ); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let text = "The quick brown fox jumps over the lazy dog" + +let embeddings = try await vectorsDB.createTextEmbeddings( + texts: [text] +) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": embeddings.embeddings[0].embedding, + "metadata": ["text": text] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let text = "The quick brown fox jumps over the lazy dog"; + + let embeddings = vectors_db.create_text_embeddings( + vec![text], + None, // model (optional) + ).await?; + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": embeddings.embeddings[0].embedding, + "metadata": { "text": text } + }), + None, // permissions (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [-0.012246467, 0.02621112, -0.15247375], "metadata": { "text": "The quick brown fox jumps over the lazy dog" } }' +``` +{% /multicode %} + +{% info title="Embed in batches" %} +You can pass several strings in one `createTextEmbeddings` call to embed them together. The response returns one entry per input text, in order, so you can map each embedding back to its source text before storing. +{% /info %} + +# Next steps {% #next-steps %} +With embeddings stored in your documents, you can find the most similar documents to a query vector with vector search. + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Learn about vector search +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc new file mode 100644 index 00000000000..160513898fa --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/order/+page.markdoc @@ -0,0 +1,1006 @@ +--- +layout: article +title: Order +description: Order documents returned by Appwrite VectorsDB. Learn how to sort by system fields like $createdAt and $sequence, and why metadata sub-fields can't be ordered. +--- +You can order the documents returned by [listDocuments](/docs/products/databases/vectorsdb/documents#list-documents) using the `Query.orderAsc()` and `Query.orderDesc()` query methods. + +VectorsDB orders on the system fields that Appwrite maintains on every document, such as `$createdAt`, `$updatedAt`, `$sequence`, and `$id`. + +{% info title="Metadata sub-fields can't be ordered" %} +A VectorsDB collection has a [fixed schema](/docs/products/databases/vectorsdb/collections#schema): an `embeddings` vector and a `metadata` object. Because `metadata` is stored as a single JSON object rather than typed columns, you can't order by a value inside it. Ordering by a nested path like `metadata.title` is rejected with `Invalid query: Cannot order by nested attribute: metadata`. Order by the system fields below instead, or keep an orderable value in a system field such as `$createdAt`. +{% /info %} + +# Order by a system field {% #one-field %} +Pass `Query.orderAsc()` or `Query.orderDesc()` with a system field to sort the documents returned. The example below orders documents from newest to oldest by `$createdAt`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt') + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderDesc('$createdAt') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_desc('$createdAt') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_desc('$createdAt') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderDesc("$createdAt") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderDesc('\$createdAt') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderDesc("\$createdAt") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderDesc("$createdAt") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderDesc("$createdAt") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_desc("$createdAt").to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","values":["$createdAt"]}' +``` +{% /multicode %} + +# Order by multiple fields {% #multiple-fields %} +To sort by more than one field, pass multiple order queries. They are applied in order, so the first query is the primary sort and each later query breaks ties. + +In the example below, documents are sorted first by `$createdAt` in descending order, then by `$id` in ascending order to break ties. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first + sdk.Query.orderAsc('$id') // Then break ties by document ID + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first + sdk.Query.orderAsc('$id') // Then break ties by document ID + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderDesc('$createdAt'), + Query::orderAsc('$id') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_desc('$createdAt'), + Query.order_asc('$id') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_desc('$createdAt'), + Query.order_asc('$id') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderDesc("$createdAt"), + Query.OrderAsc("$id") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderDesc('\$createdAt'), + Query.orderAsc('\$id') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderDesc("\$createdAt"), + Query.orderAsc("\$id") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderDesc("$createdAt"), + Query.orderAsc("$id") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderDesc("$createdAt"), + Query.orderAsc("$id") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::order_desc("$createdAt").to_string(), + Query::order_asc("$id").to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","values":["$createdAt"]}' '{"method":"orderAsc","values":["$id"]}' +``` +{% /multicode %} + +# Order by sequence {% #sequence-ordering %} +For ordering based on insertion order, use the `$sequence` field, which Appwrite adds to every document. Sorting by `$sequence` returns documents in the order they were created. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderAsc('$sequence') + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderAsc('$sequence') + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderAsc('$sequence') + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_asc('$sequence') + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_asc('$sequence') + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderAsc("$sequence") + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderAsc('\$sequence') + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderAsc("\$sequence") + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderAsc("$sequence") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderAsc("$sequence") + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_asc("$sequence").to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderAsc","values":["$sequence"]}' +``` +{% /multicode %} + +The `$sequence` field is useful when you need: +- Consistent ordering for pagination, especially with high-frequency inserts +- Reliable insertion order tracking when timestamps might not be precise enough +- Simple insertion-order sorting without managing custom counter fields + +# Order randomly {% #random-ordering %} +Use `Query.orderRandom()` to return documents in a random order. This is useful for sampling documents or surfacing varied results on each request. It takes no field. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderRandom() + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.orderRandom() + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::orderRandom() + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.order_random() + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.order_random() + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.OrderRandom() + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.orderRandom() + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.orderRandom() + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.orderRandom() + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documentList = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.orderRandom() + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::order_random().to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderRandom","values":[]}' +``` +{% /multicode %} + +To rank documents by similarity to a query vector instead of ordering by a field, use a vector search query. See [Vector search](/docs/products/databases/vectorsdb/vector-search). diff --git a/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc new file mode 100644 index 00000000000..c12e0d4cddc --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/pagination/+page.markdoc @@ -0,0 +1,1041 @@ +--- +layout: article +title: Pagination +description: Implement pagination for large data sets in Appwrite VectorsDB. Explore techniques for splitting and displaying documents across multiple pages. +--- + +As your collection grows in size, you'll need to paginate the documents returned. +Pagination improves performance by returning a subset of documents that match a query at a time, called a page. + +By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method. +There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**. + +# Offset pagination {% #offset-pagination %} + +Offset pagination divides documents into pages of `N` documents each. +To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`. + +Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. +With `Query.limit()` you define how many documents can be returned from one request. +The `Query.offset()` is the number of documents you wish to skip before selecting documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(0) + ] +}); + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(25) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(0) + ] +}); + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.offset(25) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +// Page 1 +$page1 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::offset(0) + ] +); + +// Page 2 +$page2 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::offset(25) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.offset(0) + ] +) + +# Page 2 +page2 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(0) + ] +) + +# Page 2 +page2 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.Offset(0) + } +); + +// Page 2 +DocumentList page2 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.Offset(25) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(0) + ], +); + +// Page 2 +DocumentList page2 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.offset(25) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +// Page 1 +val page1 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.offset(0) + ), +) + +// Page 2 +val page2 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.offset(25) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25), + Query.offset(0) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +// Page 1 +let page1 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.offset(0) + ] +) + +// Page 2 +let page2 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.offset(25) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Page 1 + let page1 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::offset(0).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + // Page 2 + let page2 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::offset(25).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' '{"method":"offset","values":[25]}' +``` +{% /multicode %} + +{% info title="Drawbacks" %} +While traditional offset pagination is familiar, it comes with some drawbacks. +The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data. +If the data changes frequently, offset pagination will also produce **missing and duplicate** results. +{% /info %} + +# Cursor pagination {% #cursor-pagination %} + +The cursor is a unique identifier for a document that points to where the next page should start. +After reading a page of documents, pass the last document's ID into the `Query.cursorAfter(lastId)` query method to get the next page of documents. +Pass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.cursorAfter(lastId) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +// Page 1 +const page1 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ] +}); + +const lastId = page1.documents[page1.documents.length - 1].$id; + +// Page 2 +const page2 = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25), + sdk.Query.cursorAfter(lastId) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +// Page 1 +$page1 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25) + ] +); + +$lastId = $page1['documents'][count($page1['documents']) - 1]['$id']; + +// Page 2 +$page2 = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25), + Query::cursorAfter($lastId) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25) + ] +) + +last_id = page1['documents'][-1]['$id'] + +# Page 2 +page2 = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25), + Query.cursor_after(last_id) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +# Page 1 +page1 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ] +) + +last_id = page1.documents.last.id + +# Page 2 +page2 = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.cursor_after(last_id) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25) + } +); + +string lastId = page1.Documents.Last().Id; + +// Page 2 +DocumentList page2 = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25), + Query.CursorAfter(lastId) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +// Page 1 +DocumentList page1 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], +); + +final lastId = page1.documents.last.$id; + +// Page 2 +DocumentList page2 = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +// Page 1 +val page1 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25) + ), +) + +val lastId = page1.documents.last().id + +// Page 2 +val page2 = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25), + Query.cursorAfter(lastId) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25), + Query.cursorAfter("<LAST_DOCUMENT_ID>") + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +// Page 1 +let page1 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25) + ] +) + +let lastId = page1.documents.last!.id + +// Page 2 +let page2 = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25), + Query.cursorAfter(lastId) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Page 1 + let page1 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(25).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + let last_id = page1.documents.last().unwrap().id.clone(); + + // Page 2 + let page2 = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::limit(25).to_string(), + Query::cursor_after(last_id).to_string(), + ]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' '{"method":"cursorAfter","values":["<LAST_DOCUMENT_ID>"]}' +``` +{% /multicode %} + +# When to use what? {% #when-to-use %} +Offset pagination should be used for collections that rarely change. +Offset pagination lets you build an indicator of the current page number and the total page count. +For example, a list with up to 20 pages or static data like a list of countries or currencies. +Using offset pagination on large and frequently updated collections may result in slow performance and **missing and duplicate** results. + +Cursor pagination should be used for frequently updated collections. +It is best suited for lazy-loaded pages with infinite scrolling. +For example, a feed, comment section, chat history, or high volume datasets. + +# Cache list responses {% #cache-list-responses %} + +You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data. + +Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const page = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const page = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$page = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(25) + ], + ttl: 60 // Cache for 60 seconds +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +page = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(25) + ], + ttl = 60 # Cache for 60 seconds +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +page = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], + ttl: 60 # Cache for 60 seconds +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList page = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(25) + }, + ttl: 60 // Cache for 60 seconds +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList page = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(25) + ], + ttl: 60, // Cache for 60 seconds +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val page = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(25) + ), + ttl = 60 // Cache for 60 seconds +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(25) + ), + null, // transactionId + null, // total + 60, // ttl - Cache for 60 seconds + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let page = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(25) + ], + ttl: 60 // Cache for 60 seconds +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let page = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(25).to_string()]), + None, // transactionId (optional) + None, // total (optional) + Some(60), // ttl - Cache for 60 seconds + ).await?; + + println!("{:?}", page); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' \ + --ttl 60 +``` +{% /multicode %} + +Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents. diff --git a/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc new file mode 100644 index 00000000000..2b3adc1c581 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/permissions/+page.markdoc @@ -0,0 +1,1241 @@ +--- +layout: article +title: Database permissions +description: Control access to your VectorsDB data with permissions. Learn how to set collection level and document level access rules. +--- + +Permissions define who can access documents in a collection. By default **no permissions** are granted to any users, so no user can access any documents. +Permissions exist at two levels, collection level and document level permissions. + +In Appwrite, permissions are **granted**, meaning a user has no access by default and receives access when granted. +A user with access granted at either collection level or document level will be able to access a document. +Users **don't need access at both levels** to access documents. + +Permissions are evaluated when documents are accessed through a [Client SDK](/docs/sdks#client). [Server SDKs](/docs/sdks#server) authenticated with an [API key](/docs/advanced/platform/api-keys) bypass permissions, so the examples below use a Server SDK to set and read back the permissions that a client would then be evaluated against. + +# Collection level {% #collection-level %} +Collection level permissions apply to every document in the collection. +If a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection. + +Configure collection level permissions by navigating to **Your collection** > **Security** > **Permissions**, or pass a `permissions` array when you create or update the collection. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()), + sdk.Permission.update(sdk.Role.users()), + sdk.Permission.delete(sdk.Role.users()) + ], + documentSecurity: true // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()), + sdk.Permission.update(sdk.Role.users()), + sdk.Permission.delete(sdk.Role.users()) + ], + documentSecurity: true // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID::unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission::read(Role::any()), + Permission::create(Role::users()), + Permission::update(Role::users()), + Permission::delete(Role::users()) + ], + documentSecurity: true // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = ID.unique(), + name = 'documents', + dimension = 4, + permissions = [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + document_security = True # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + document_security: true # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.Unique(), + name: "documents", + dimension: 4, + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Create(Role.Users()), + Permission.Update(Role.Users()), + Permission.Delete(Role.Users()) + }, + documentSecurity: true // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID.unique(), + name: 'documents', + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], // (optional) + documentSecurity: true, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = ID.unique(), + name = "documents", + dimension = 4, + permissions = listOf( + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ), // optional + documentSecurity = true, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + ID.unique(), + "documents", + 4, + List.of( + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ), + true, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.unique(), + name: "documents", + dimension: 4, + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()), + Permission.update(Role.users()), + Permission.delete(Role.users()) + ], + documentSecurity: true // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + ID::unique(), + "documents", + 4, + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::create(Role::users(None)).to_string(), + Permission::update(Role::users(None)).to_string(), + Permission::delete(Role::users(None)).to_string(), + ]), + Some(true), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "documents" \ + --dimension 4 \ + --permissions 'read("any")' 'create("users")' 'update("users")' 'delete("users")' \ + --document-security true +``` +{% /multicode %} + +To change a collection's permissions later, pass a new `permissions` array to `updateCollection`. The `name` is required when updating. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()) + ], + documentSecurity: false // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateCollection({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.create(sdk.Role.users()) + ], + documentSecurity: false // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission::read(Role::any()), + Permission::create(Role::users()) + ], + documentSecurity: false // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_collection( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + name = 'documents', + permissions = [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + document_security = False # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_collection( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + document_security: false # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.UpdateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "documents", + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Create(Role.Users()) + }, + documentSecurity: false // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.updateCollection( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + name: 'documents', + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], // (optional) + documentSecurity: false, // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateCollection( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + name = "documents", + permissions = listOf( + Permission.read(Role.any()), + Permission.create(Role.users()) + ), // optional + documentSecurity = false, // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateCollection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "documents", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.updateCollection( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + name: "documents", + permissions: [ + Permission.read(Role.any()), + Permission.create(Role.users()) + ], + documentSecurity: false // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_collection( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "documents", + None, // dimension (optional) + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::create(Role::users(None)).to_string(), + ]), + Some(false), // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-collection \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --name "documents" \ + --permissions 'read("any")' 'create("users")' \ + --document-security false +``` +{% /multicode %} + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Document level {% #document-level %} +Document level permissions grant access to individual documents. +If a user has read, update, or delete permissions at the document level, the user can access the **individual document**. + +Document level permissions are only applied if `documentSecurity` is enabled on the collection. Enable it in the Console by navigating to **Your collection** > **Security** > **Document security**, or by setting `documentSecurity` to `true` when you create or update the collection, as shown above. + +Set permissions on an individual document by passing a `permissions` array to `createDocument`. Use `Role.user('<USER_ID>')` to scope access to a specific user. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + }, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.update(sdk.Role.user('<USER_ID>')), + sdk.Permission.delete(sdk.Role.user('<USER_ID>')) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + }, + permissions: [ + sdk.Permission.read(sdk.Role.any()), + sdk.Permission.update(sdk.Role.user('<USER_ID>')), + sdk.Permission.delete(sdk.Role.user('<USER_ID>')) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet'] + ], + permissions: [ + Permission::read(Role::any()), + Permission::update(Role::user('<USER_ID>')), + Permission::delete(Role::user('<USER_ID>')) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }, + permissions = [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Hamlet" } + }, + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" } } } + }, + permissions: new List<string> { + Permission.Read(Role.Any()), + Permission.Update(Role.User("<USER_ID>")), + Permission.Delete(Role.User("<USER_ID>")) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }, + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user('<USER_ID>')), + Permission.delete(Role.user('<USER_ID>')) + ], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Hamlet") + ), + permissions = listOf( + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Hamlet") + ), + List.of( + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet"] + ], + permissions: [ + Permission.read(Role.any()), + Permission.update(Role.user("<USER_ID>")), + Permission.delete(Role.user("<USER_ID>")) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }), + Some(vec![ + Permission::read(Role::any()).to_string(), + Permission::update(Role::user("<USER_ID>", None)).to_string(), + Permission::delete(Role::user("<USER_ID>", None)).to_string(), + ]), + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet" } }' \ + --permissions 'read("any")' 'update("user:<USER_ID>")' 'delete("user:<USER_ID>")' +``` +{% /multicode %} + +To change a document's permissions later, pass a new `permissions` array to `updateDocument`. Only the fields you pass are changed, so you can update permissions without touching the vector or metadata. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + sdk.Permission.read(sdk.Role.users()) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + sdk.Permission.read(sdk.Role.users()) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + Permission::read(Role::users()) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + permissions = [ + Permission.read(Role.users()) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + permissions: [ + Permission.read(Role.users()) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + permissions: new List<string> { + Permission.Read(Role.Users()) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/permission.dart'; +import 'package:dart_appwrite/role.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + permissions: [ + Permission.read(Role.users()) + ], // (optional) +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + permissions = listOf( + Permission.read(Role.users()) + ), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + null, + List.of( + Permission.read(Role.users()) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + permissions: [ + Permission.read(Role.users()) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, // data (optional) + Some(vec![ + Permission::read(Role::users(None)).to_string(), + ]), + None, // transactionId (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id "<DATABASE_ID>" \ + --collection-id "<COLLECTION_ID>" \ + --document-id "<DOCUMENT_ID>" \ + --permissions 'read("users")' +``` +{% /multicode %} + +[Learn more about permissions and roles](/docs/advanced/platform/permissions) + +# Common use cases {% #common-use-cases %} + +For examples of how to implement common permission patterns, including creating private documents that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. diff --git a/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc new file mode 100644 index 00000000000..5e36bec2324 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/queries/+page.markdoc @@ -0,0 +1,1879 @@ +--- +layout: article +title: Queries +description: Filter VectorsDB documents by their metadata using the Query class. Discover comparison, string, logical, ordering, and pagination operators. +--- + +Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. + +In VectorsDB, every document stores an `embeddings` vector and an optional `metadata` object. The queries on this page filter documents by the fields inside that `metadata` object. To rank documents by vector similarity instead, see [vector search](/docs/products/databases/vectorsdb/vector-search). + +# Query class {% #query-class %} + +Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. + +# Building queries {% #building-queries %} + +Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. + +Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. +For example `Query.equal('metadata.genre', ['sci-fi', 'drama'])` will fetch documents whose genre is `sci-fi` or `drama`. + +To filter on a field inside the `metadata` object, reference it with dot notation, such as `metadata.genre` or `metadata.year`. + +{% info title="Filter metadata with string values" %} +VectorsDB stores `metadata` as a JSON object, so metadata fields are compared as strings. Pass filter values as strings, even when the stored value is a number. For example, use `Query.greaterThan('metadata.year', '2010')`, not `Query.greaterThan('metadata.year', 2010)`. Passing a numeric value returns a `400` error. +{% /info %} + +{% info title="Default pagination behavior" %} +By default, results are limited to the **first 25 items**. +You can change this through [pagination](/docs/products/databases/vectorsdb/pagination). +{% /info %} + +{% multicode %} + +```client-web +import { Client, Query, VectorsDB } from "appwrite"; + +const client = new Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + +const vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greaterThan('metadata.year', '2010') + ] +}); +``` +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client(); + +const vectorsDB = new sdk.VectorsDB(client); + +client + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<YOUR_API_KEY>') +; + +const promise = vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.equal('metadata.genre', ['sci-fi', 'drama']), + sdk.Query.greaterThan('metadata.year', '2010') + ] +}); + +promise.then(function (response) { + console.log(response); +}, function (error) { + console.log(error); +}); +``` +```client-flutter +import 'package:appwrite/appwrite.dart'; + +void main() async { + final client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + + final vectorsDB = VectorsDB(client); + + try { + final documents = await vectorsDB.listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greaterThan('metadata.year', '2010') + ] + ); + } on AppwriteException catch(e) { + print(e); + } +} +``` +```client-apple +import Appwrite +import AppwriteModels + +func main() async throws { + let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<PROJECT_ID>") + + let vectorsDB = VectorsDB(client) + + do { + let documents = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.equal("metadata.genre", value: ["sci-fi", "drama"]), + Query.greaterThan("metadata.year", value: "2010") + ] + ) + } catch { + print(error.localizedDescription) + } +} +``` +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +suspend fun main() { + val client = Client(applicationContext) + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>'); + + val vectorsDB = VectorsDB(client) + + try { + val documents = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.equal("metadata.genre", listOf("sci-fi", "drama")), + Query.greaterThan("metadata.year", "2010") + ) + ) + } catch (e: AppwriteException) { + Log.e("Appwrite", e.message) + } +} +``` +```server-go +package main + +import ( + "fmt" + "log" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/query" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), + appwrite.WithProject("<PROJECT_ID>"), + appwrite.WithKey("<API_KEY>"), + ) + + vectorsDB := appwrite.NewVectorsDB(client) + + documents, err := vectorsDB.ListDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vectorsDB.WithListDocumentsQueries([]string{ + query.Equal("metadata.genre", []string{"sci-fi", "drama"}), + query.GreaterThan("metadata.year", "2010"), + }), + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Documents: %+v\n", documents) +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::Value; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new() + .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") + .set_project("<PROJECT_ID>") + .set_key("<YOUR_API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let documents = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::equal("metadata.genre", Value::Array(vec![ + Value::String("sci-fi".to_string()), + Value::String("drama".to_string()), + ])).to_string(), + Query::greater_than("metadata.year", "2010").to_string(), + ]), + None, + None, + None, + ).await?; + + println!("{:?}", documents); + Ok(()) +} +``` +```server-php +<?php + +use Appwrite\Client; +use Appwrite\Query; +use Appwrite\Services\VectorsDB; + +$client = new Client(); + +$client + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<PROJECT_ID>') + ->setKey('<YOUR_API_KEY>') +; + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query::equal('metadata.genre', ['sci-fi', 'drama']), + Query::greaterThan('metadata.year', '2010') + ] +); +``` +```server-python +from appwrite.client import Client +from appwrite.query import Query +from appwrite.services.vectors_db import VectorsDB + +client = Client() + +(client + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<PROJECT_ID>') + .set_key('<YOUR_API_KEY>') +) + +vectorsDB = VectorsDB(client) + +result = vectorsDB.list_documents( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.equal('metadata.genre', ['sci-fi', 'drama']), + Query.greater_than('metadata.year', '2010') + ] +) +``` +```graphql +query { + vectorsDBListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>" + queries: [ + "{\"method\":\"equal\",\"attribute\":\"metadata.genre\",\"values\":[\"sci-fi\",\"drama\"]}", + "{\"method\":\"greaterThan\",\"attribute\":\"metadata.year\",\"values\":[\"2010\"]}" + ] + ) { + total + documents { + _id + data + } + } +} +``` +```http +GET /v1/vectorsdb/<DATABASE_ID>/collections/<COLLECTION_ID>/documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22metadata.genre%22%2C%22values%22%3A%5B%22sci-fi%22%2C%22drama%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22metadata.year%22%2C%22values%22%3A%5B%222010%22%5D%7D HTTP/1.1 +Content-Type: application/json +X-Appwrite-Project: <PROJECT_ID> +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi","drama"]}' '{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}' +``` +{% /multicode %} + +# Query operators {% #query-operators %} + +## Select {% #select %} + +The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need. Each VectorsDB document has two data fields, `embeddings` and `metadata`. + +{% multicode %} +```client-web +Query.select(["metadata"]) +``` +```client-flutter +Query.select(["metadata"]) +``` +```server-python +Query.select(["metadata"]) +``` +```server-ruby +Query.select(["metadata"]) +``` +```server-deno +Query.select(["metadata"]) +``` +```server-php +Query::select(["metadata"]) +``` +```client-apple +Query.select(["metadata"]) +``` +```server-go +query.Select([]string{"metadata"}) +``` +```server-nodejs +Query.select(["metadata"]) +``` +```server-rust +Query::select(vec!["metadata"]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"select","values":["metadata"]}' +``` +```http +{"method":"select","values":["metadata"]} +``` +{% /multicode %} + +### Use selection patterns {% #select-patterns %} + +| Pattern | Description | Use case | +|---------|-------------|----------| +| `["metadata"]` | Metadata object only | Return metadata without the embedding vector | +| `["embeddings"]` | Embedding vector only | Return the stored vector | +| `["metadata", "embeddings"]` | Both data fields | Get the complete document data | + +### Optimize performance {% #select-performance %} + +**Optimize response size** - Only select the fields you actually need. Embedding vectors are large, so omitting `embeddings` keeps responses small when you only need metadata. + +**Reduce database load** - Selecting fewer fields reduces database processing time. + +## Comparison operators {% #comparison %} + +Pass metadata filter values as strings. See [Filter metadata with string values](#building-queries). + +### Equal {% #equal %} + +Returns document if a metadata field is equal to any value in the provided array. + +{% multicode %} +```client-web +Query.equal("metadata.genre", ["sci-fi"]) +``` +```client-flutter +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-python +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-ruby +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-deno +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-php +Query::equal("metadata.genre", ["sci-fi"]) +``` +```client-apple +Query.equal("metadata.genre", value: ["sci-fi"]) +``` +```server-go +query.Equal("metadata.genre", []string{"sci-fi"}) +``` +```server-nodejs +Query.equal("metadata.genre", ["sci-fi"]) +``` +```server-rust +Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}' +``` +```http +{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]} +``` +{% /multicode %} + +### Not equal {% #not-equal %} + +Returns document if a metadata field is not equal to the provided value. + +{% multicode %} +```client-web +Query.notEqual("metadata.genre", "sci-fi") +``` +```client-flutter +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-python +Query.not_equal("metadata.genre", "sci-fi") +``` +```server-ruby +Query.not_equal("metadata.genre", "sci-fi") +``` +```server-deno +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-php +Query::notEqual("metadata.genre", "sci-fi") +``` +```client-apple +Query.notEqual("metadata.genre", value: "sci-fi") +``` +```server-go +query.NotEqual("metadata.genre", "sci-fi") +``` +```server-nodejs +Query.notEqual("metadata.genre", "sci-fi") +``` +```server-rust +Query::not_equal("metadata.genre", "sci-fi").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notEqual","attribute":"metadata.genre","values":["sci-fi"]}' +``` +```http +{"method":"notEqual","attribute":"metadata.genre","values":"sci-fi"} +``` +{% /multicode %} + +### Less than {% #less-than %} + +Returns document if a metadata field is less than the provided value. + +{% multicode %} +```client-web +Query.lessThan("metadata.year", "2009") +``` +```client-flutter +Query.lessThan("metadata.year", "2009") +``` +```server-python +Query.less_than("metadata.year", "2009") +``` +```server-ruby +Query.less_than("metadata.year", "2009") +``` +```server-deno +Query.lessThan("metadata.year", "2009") +``` +```server-php +Query::lessThan("metadata.year", "2009") +``` +```client-apple +Query.lessThan("metadata.year", value: "2009") +``` +```server-go +query.LessThan("metadata.year", "2009") +``` +```server-nodejs +Query.lessThan("metadata.year", "2009") +``` +```server-rust +Query::less_than("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"lessThan","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"lessThan","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Less than or equal {% #less-than-equal %} + +Returns document if a metadata field is less than or equal to the provided value. + +{% multicode %} +```client-web +Query.lessThanEqual("metadata.year", "2009") +``` +```client-flutter +Query.lessThanEqual("metadata.year", "2009") +``` +```server-python +Query.less_than_equal("metadata.year", "2009") +``` +```server-ruby +Query.less_than_equal("metadata.year", "2009") +``` +```server-deno +Query.lessThanEqual("metadata.year", "2009") +``` +```server-php +Query::lessThanEqual("metadata.year", "2009") +``` +```client-apple +Query.lessThanEqual("metadata.year", value: "2009") +``` +```server-go +query.LessThanEqual("metadata.year", "2009") +``` +```server-nodejs +Query.lessThanEqual("metadata.year", "2009") +``` +```server-rust +Query::less_than_equal("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Greater than {% #greater-than %} + +Returns document if a metadata field is greater than the provided value. + +{% multicode %} +```client-web +Query.greaterThan("metadata.year", "2009") +``` +```client-flutter +Query.greaterThan("metadata.year", "2009") +``` +```server-python +Query.greater_than("metadata.year", "2009") +``` +```server-ruby +Query.greater_than("metadata.year", "2009") +``` +```server-deno +Query.greaterThan("metadata.year", "2009") +``` +```server-php +Query::greaterThan("metadata.year", "2009") +``` +```client-apple +Query.greaterThan("metadata.year", value: "2009") +``` +```server-go +query.GreaterThan("metadata.year", "2009") +``` +```server-nodejs +Query.greaterThan("metadata.year", "2009") +``` +```server-rust +Query::greater_than("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"greaterThan","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"greaterThan","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Greater than or equal {% #greater-than-equal %} + +Returns document if a metadata field is greater than or equal to the provided value. + +{% multicode %} +```client-web +Query.greaterThanEqual("metadata.year", "2009") +``` +```client-flutter +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-python +Query.greater_than_equal("metadata.year", "2009") +``` +```server-ruby +Query.greater_than_equal("metadata.year", "2009") +``` +```server-deno +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-php +Query::greaterThanEqual("metadata.year", "2009") +``` +```client-apple +Query.greaterThanEqual("metadata.year", value: "2009") +``` +```server-go +query.GreaterThanEqual("metadata.year", "2009") +``` +```server-nodejs +Query.greaterThanEqual("metadata.year", "2009") +``` +```server-rust +Query::greater_than_equal("metadata.year", "2009").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]}' +``` +```http +{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]} +``` +{% /multicode %} + +### Between {% #between %} + +Returns document if a metadata field value falls between the two values. The boundary values are inclusive. + +{% multicode %} +```client-web +Query.between("metadata.year", "2005", "2010") +``` +```client-flutter +Query.between("metadata.year", "2005", "2010") +``` +```server-python +Query.between("metadata.year", "2005", "2010") +``` +```server-ruby +Query.between("metadata.year", "2005", "2010") +``` +```server-deno +Query.between("metadata.year", "2005", "2010") +``` +```server-php +Query::between("metadata.year", "2005", "2010") +``` +```client-apple +Query.between("metadata.year", start: "2005", end: "2010") +``` +```server-go +query.Between("metadata.year", "2005", "2010") +``` +```server-nodejs +Query.between("metadata.year", "2005", "2010") +``` +```server-rust +Query::between("metadata.year", "2005", "2010").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"between","attribute":"metadata.year","values":["2005","2010"]}' +``` +```http +{"method":"between","attribute":"metadata.year","values":["2005","2010"]} +``` +{% /multicode %} + +### Not between {% #not-between %} + +Returns documents if the metadata field value is outside the range defined by the two values. Boundary values are excluded. + +{% multicode %} +```client-web +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-flutter +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-apple +Query.notBetween("metadata.year", start: "2005", end: "2010") +``` +```client-android-kotlin +Query.notBetween("metadata.year", "2005", "2010") +``` +```client-android-java +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-python +Query.not_between("metadata.year", "2005", "2010") +``` +```server-ruby +Query.not_between("metadata.year", "2005", "2010") +``` +```server-deno +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-nodejs +Query.notBetween("metadata.year", "2005", "2010") +``` +```server-php +Query::notBetween("metadata.year", "2005", "2010") +``` +```server-swift +Query.notBetween("metadata.year", start: "2005", end: "2010") +``` +```server-rust +Query::not_between("metadata.year", "2005", "2010").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]}' +``` +```http +{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]} +``` +{% /multicode %} + +## Null checks {% #null-checks %} + +### Is null {% #is-null %} + +Returns documents where the metadata field value is null or absent. + +{% multicode %} +```client-web +Query.isNull("metadata.archived") +``` +```client-flutter +Query.isNull("metadata.archived") +``` +```server-python +Query.is_null("metadata.archived") +``` +```server-ruby +Query.is_null("metadata.archived") +``` +```server-deno +Query.isNull("metadata.archived") +``` +```server-php +Query::isNull("metadata.archived") +``` +```client-apple +Query.isNull("metadata.archived") +``` +```server-go +query.IsNull("metadata.archived") +``` +```server-nodejs +Query.isNull("metadata.archived") +``` +```server-rust +Query::is_null("metadata.archived").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"isNull","attribute":"metadata.archived"}' +``` +```http +{"method":"isNull","attribute":"metadata.archived"} +``` +{% /multicode %} + +### Is not null {% #is-not-null %} + +Returns documents where the metadata field value is **not** null. + +{% multicode %} +```client-web +Query.isNotNull("metadata.archived") +``` +```client-flutter +Query.isNotNull("metadata.archived") +``` +```server-python +Query.is_not_null("metadata.archived") +``` +```server-ruby +Query.is_not_null("metadata.archived") +``` +```server-deno +Query.isNotNull("metadata.archived") +``` +```server-php +Query::isNotNull("metadata.archived") +``` +```client-apple +Query.isNotNull("metadata.archived") +``` +```server-go +query.IsNotNull("metadata.archived") +``` +```server-nodejs +Query.isNotNull("metadata.archived") +``` +```server-rust +Query::is_not_null("metadata.archived").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"isNotNull","attribute":"metadata.archived"}' +``` +```http +{"method":"isNotNull","attribute":"metadata.archived"} +``` +{% /multicode %} + +## String operations {% #string-operations %} + +### Starts with {% #starts-with %} + +Returns documents if a metadata string field starts with a substring. + +{% multicode %} +```client-web +Query.startsWith("metadata.title", "Iron") +``` +```client-flutter +Query.startsWith("metadata.title", "Iron") +``` +```server-python +Query.starts_with("metadata.title", "Iron") +``` +```server-ruby +Query.starts_with("metadata.title", "Iron") +``` +```server-deno +Query.startsWith("metadata.title", "Iron") +``` +```server-php +Query::startsWith("metadata.title", "Iron") +``` +```client-apple +Query.startsWith("metadata.title", value: "Iron") +``` +```server-go +query.StartsWith("metadata.title", "Iron") +``` +```server-nodejs +Query.startsWith("metadata.title", "Iron") +``` +```server-rust +Query::starts_with("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"startsWith","attribute":"metadata.title","values":["Iron"]}' +``` +```http +{"method":"startsWith","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Not starts with {% #not-starts-with %} + +Returns documents if a metadata string field does not start with a substring. + +{% multicode %} +```client-web +Query.notStartsWith("metadata.title", "Iron") +``` +```client-flutter +Query.notStartsWith("metadata.title", "Iron") +``` +```client-apple +Query.notStartsWith("metadata.title", value: "Iron") +``` +```client-android-kotlin +Query.notStartsWith("metadata.title", "Iron") +``` +```client-android-java +Query.notStartsWith("metadata.title", "Iron") +``` +```server-python +Query.not_starts_with("metadata.title", "Iron") +``` +```server-ruby +Query.not_starts_with("metadata.title", "Iron") +``` +```server-deno +Query.notStartsWith("metadata.title", "Iron") +``` +```server-nodejs +Query.notStartsWith("metadata.title", "Iron") +``` +```server-php +Query::notStartsWith("metadata.title", "Iron") +``` +```server-swift +Query.notStartsWith("metadata.title", value: "Iron") +``` +```server-rust +Query::not_starts_with("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]}' +``` +```http +{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Ends with {% #ends-with %} + +Returns documents if a metadata string field ends with a substring. + +{% multicode %} +```client-web +Query.endsWith("metadata.title", "Man") +``` +```client-flutter +Query.endsWith("metadata.title", "Man") +``` +```server-python +Query.ends_with("metadata.title", "Man") +``` +```server-ruby +Query.ends_with("metadata.title", "Man") +``` +```server-deno +Query.endsWith("metadata.title", "Man") +``` +```server-php +Query::endsWith("metadata.title", "Man") +``` +```client-apple +Query.endsWith("metadata.title", value: "Man") +``` +```server-go +query.EndsWith("metadata.title", "Man") +``` +```server-nodejs +Query.endsWith("metadata.title", "Man") +``` +```server-rust +Query::ends_with("metadata.title", "Man").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"endsWith","attribute":"metadata.title","values":["Man"]}' +``` +```http +{"method":"endsWith","attribute":"metadata.title","values":["Man"]} +``` +{% /multicode %} + +### Not ends with {% #not-ends-with %} + +Returns documents if a metadata string field does not end with a substring. + +{% multicode %} +```client-web +Query.notEndsWith("metadata.title", "Man") +``` +```client-flutter +Query.notEndsWith("metadata.title", "Man") +``` +```client-apple +Query.notEndsWith("metadata.title", value: "Man") +``` +```client-android-kotlin +Query.notEndsWith("metadata.title", "Man") +``` +```client-android-java +Query.notEndsWith("metadata.title", "Man") +``` +```server-python +Query.not_ends_with("metadata.title", "Man") +``` +```server-ruby +Query.not_ends_with("metadata.title", "Man") +``` +```server-deno +Query.notEndsWith("metadata.title", "Man") +``` +```server-nodejs +Query.notEndsWith("metadata.title", "Man") +``` +```server-php +Query::notEndsWith("metadata.title", "Man") +``` +```server-swift +Query.notEndsWith("metadata.title", value: "Man") +``` +```server-rust +Query::not_ends_with("metadata.title", "Man").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]}' +``` +```http +{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]} +``` +{% /multicode %} + +### Contains {% #contains %} + +Returns documents if a metadata array field contains the specified elements or if a metadata string field contains the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```client-flutter +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-python +# For arrays +Query.contains("metadata.tags", ["space"]) + +# For strings +Query.contains("metadata.title", "Iron") +``` +```server-ruby +# For arrays +Query.contains("metadata.tags", ["space"]) + +# For strings +Query.contains("metadata.title", "Iron") +``` +```server-deno +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-php +// For arrays +Query::contains("metadata.tags", ["space"]) + +// For strings +Query::contains("metadata.title", "Iron") +``` +```client-apple +// For arrays +Query.contains("metadata.tags", value: ["space"]) + +// For strings +Query.contains("metadata.title", value: "Iron") +``` +```server-go +// For arrays +query.Contains("metadata.tags", []string{"space"}) + +// For strings +query.Contains("metadata.title", "Iron") +``` +```server-nodejs +// For arrays +Query.contains("metadata.tags", ["space"]) + +// For strings +Query.contains("metadata.title", "Iron") +``` +```server-rust +// For arrays +Query::contains("metadata.tags", Value::Array(vec![ + Value::String("space".to_string()), +])).to_string() + +// For strings +Query::contains("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"contains","attribute":"metadata.tags","values":["space"]}' +``` +```http +# For arrays +{"method":"contains","attribute":"metadata.tags","values":["space"]} + +# For strings +{"method":"contains","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +### Not contains {% #not-contains %} + +Returns documents if a metadata array field does not contain the specified elements, or if a metadata string field does not contain the specified substring. + +{% multicode %} +```client-web +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-flutter +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-react-native +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-apple +// For arrays +Query.notContains("metadata.tags", value: ["space"]) + +// For strings +Query.notContains("metadata.title", value: "Iron") +``` +```client-android-kotlin +// For arrays +Query.notContains("metadata.tags", listOf("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```client-android-java +// For arrays +Query.notContains("metadata.tags", Arrays.asList("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-python +# For arrays +Query.not_contains("metadata.tags", ["space"]) + +# For strings +Query.not_contains("metadata.title", "Iron") +``` +```server-ruby +# For arrays +Query.not_contains("metadata.tags", ["space"]) + +# For strings +Query.not_contains("metadata.title", "Iron") +``` +```server-deno +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-nodejs +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-php +// For arrays +Query::notContains("metadata.tags", ["space"]) + +// For strings +Query::notContains("metadata.title", "Iron") +``` +```server-dotnet +// For arrays +Query.NotContains("metadata.tags", new List<string> { "space" }) + +// For strings +Query.NotContains("metadata.title", "Iron") +``` +```server-go +// For arrays +query.NotContains("metadata.tags", []string{"space"}) + +// For strings +query.NotContains("metadata.title", "Iron") +``` +```server-dart +// For arrays +Query.notContains("metadata.tags", ["space"]) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-swift +// For arrays +Query.notContains("metadata.tags", value: ["space"]) + +// For strings +Query.notContains("metadata.title", value: "Iron") +``` +```server-kotlin +// For arrays +Query.notContains("metadata.tags", listOf("space")) + +// For strings +Query.notContains("metadata.title", "Iron") +``` +```server-rust +// For arrays +Query::not_contains("metadata.tags", Value::Array(vec![ + Value::String("space".to_string()), +])).to_string() + +// For strings +Query::not_contains("metadata.title", "Iron").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"notContains","attribute":"metadata.tags","values":["space"]}' +``` +```http +# For arrays +{"method":"notContains","attribute":"metadata.tags","values":["space"]} + +# For strings +{"method":"notContains","attribute":"metadata.title","values":["Iron"]} +``` +{% /multicode %} + +## Logical operators {% #logical-operators %} + +### AND {% #and %} + +Returns document if it matches all of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```client-flutter +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-python +Query.and_queries([ + Query.equal("metadata.genre", "sci-fi"), + Query.starts_with("metadata.title", "Inter") +]) +``` +```server-ruby +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.starts_with("metadata.title", "Inter") +]) +``` +```server-deno +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-php +Query::and([ + Query::equal("metadata.genre", "sci-fi"), + Query::startsWith("metadata.title", "Inter") +]) +``` +```client-apple +Query.and([ + Query.equal("metadata.genre", value: "sci-fi"), + Query.startsWith("metadata.title", value: "Inter") +]) +``` +```server-go +query.And([]string{ + query.Equal("metadata.genre", []string{"sci-fi"}), + query.StartsWith("metadata.title", "Inter"), +}) +``` +```server-nodejs +Query.and([ + Query.equal("metadata.genre", "sci-fi"), + Query.startsWith("metadata.title", "Inter") +]) +``` +```server-rust +Query::and(vec![ + Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(), + Query::starts_with("metadata.title", "Inter").to_string(), +]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]}' +``` +```http +{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]} +``` +{% /multicode %} + +### OR {% #or %} + +Returns document if it matches any of the nested sub-queries in the array passed in. + +{% multicode %} +```client-web +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```client-flutter +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-python +Query.or_queries([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-ruby +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-deno +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-php +Query::or([ + Query::equal("metadata.genre", "romance"), + Query::equal("metadata.genre", "sci-fi") +]) +``` +```client-apple +Query.or([ + Query.equal("metadata.genre", value: "romance"), + Query.equal("metadata.genre", value: "sci-fi") +]) +``` +```server-go +query.Or([]string{ + query.Equal("metadata.genre", []string{"romance"}), + query.Equal("metadata.genre", []string{"sci-fi"}), +}) +``` +```server-nodejs +Query.or([ + Query.equal("metadata.genre", "romance"), + Query.equal("metadata.genre", "sci-fi") +]) +``` +```server-rust +Query::or(vec![ + Query::equal("metadata.genre", Value::String("romance".to_string())).to_string(), + Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(), +]).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]}' +``` +```http +{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]} +``` +{% /multicode %} + +## Ordering {% #ordering %} + +Order results by a top-level document attribute such as `$createdAt`, `$updatedAt`, or `$id`. Ordering by a nested `metadata` field is not supported. + +### Order descending {% #order-desc %} + +Orders results in descending order by attribute. + +{% multicode %} +```client-web +Query.orderDesc("$createdAt") +``` +```client-flutter +Query.orderDesc("$createdAt") +``` +```server-python +Query.order_desc("$createdAt") +``` +```server-ruby +Query.order_desc("$createdAt") +``` +```server-nodejs +Query.orderDesc("$createdAt") +``` +```server-php +Query::orderDesc("$createdAt") +``` +```client-apple +Query.orderDesc("$createdAt") +``` +```server-go +query.OrderDesc("$createdAt") +``` +```server-rust +Query::order_desc("$createdAt").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderDesc","attribute":"$createdAt"}' +``` +```http +{"method":"orderDesc","attribute":"$createdAt"} +``` +{% /multicode %} + +### Order ascending {% #order-asc %} + +Orders results in ascending order by attribute. + +{% multicode %} +```client-web +Query.orderAsc("$createdAt") +``` +```client-flutter +Query.orderAsc("$createdAt") +``` +```server-python +Query.order_asc("$createdAt") +``` +```server-ruby +Query.order_asc("$createdAt") +``` +```server-nodejs +Query.orderAsc("$createdAt") +``` +```server-php +Query::orderAsc("$createdAt") +``` +```client-apple +Query.orderAsc("$createdAt") +``` +```server-go +query.OrderAsc("$createdAt") +``` +```server-rust +Query::order_asc("$createdAt").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderAsc","attribute":"$createdAt"}' +``` +```http +{"method":"orderAsc","attribute":"$createdAt"} +``` +{% /multicode %} + +### Order random {% #order-random %} + +Orders results in random order. + +{% multicode %} +```client-web +Query.orderRandom() +``` +```client-flutter +Query.orderRandom() +``` +```server-python +Query.order_random() +``` +```server-ruby +Query.order_random() +``` +```server-nodejs +Query.orderRandom() +``` +```server-php +Query::orderRandom() +``` +```client-apple +Query.orderRandom() +``` +```server-go +query.OrderRandom() +``` +```server-rust +Query::order_random().to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"orderRandom"}' +``` +```http +{"method":"orderRandom"} +``` +{% /multicode %} + +## Pagination {% #pagination %} + +### Limit {% #limit %} + +Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.limit(25) +``` +```client-flutter +Query.limit(25) +``` +```server-python +Query.limit(25) +``` +```server-ruby +Query.limit(25) +``` +```server-deno +Query.limit(25) +``` +```server-php +Query::limit(25) +``` +```client-apple +Query.limit(25) +``` +```server-go +query.Limit(25) +``` +```server-nodejs +Query.limit(25) +``` +```server-rust +Query::limit(25).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[25]}' +``` +```http +{"method":"limit","values":[25]} +``` +{% /multicode %} + +### Offset {% #offset %} + +Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.offset(0) +``` +```client-flutter +Query.offset(0) +``` +```server-python +Query.offset(0) +``` +```server-ruby +Query.offset(0) +``` +```server-deno +Query.offset(0) +``` +```server-php +Query::offset(0) +``` +```client-apple +Query.offset(0) +``` +```server-go +query.Offset(0) +``` +```server-nodejs +Query.offset(0) +``` +```server-rust +Query::offset(0).to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"offset","values":[0]}' +``` +```http +{"method":"offset","values":[0]} +``` +{% /multicode %} + +### Cursor after {% #cursor-after %} + +Places the cursor after the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.cursorAfter("62a7...f620") +``` +```client-flutter +Query.cursorAfter("62a7...f620") +``` +```server-python +Query.cursor_after("62a7...f620") +``` +```server-ruby +Query.cursor_after("62a7...f620") +``` +```server-deno +Query.cursorAfter("62a7...f620") +``` +```server-php +Query::cursorAfter("62a7...f620") +``` +```client-apple +Query.cursorAfter("62a7...f620") +``` +```server-go +query.CursorAfter("62a7...f620") +``` +```server-nodejs +Query.cursorAfter("62a7...f620") +``` +```server-rust +Query::cursor_after("62a7...f620").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"cursorAfter","values":["62a7...f620"]}' +``` +```http +{"method":"cursorAfter","values":["62a7...f620"]} +``` +{% /multicode %} + +### Cursor before {% #cursor-before %} + +Places the cursor before the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination). + +{% multicode %} +```client-web +Query.cursorBefore("62a7...a600") +``` +```client-flutter +Query.cursorBefore("62a7...a600") +``` +```server-python +Query.cursor_before("62a7...a600") +``` +```server-ruby +Query.cursor_before("62a7...a600") +``` +```server-deno +Query.cursorBefore("62a7...a600") +``` +```server-php +Query::cursorBefore("62a7...a600") +``` +```client-apple +Query.cursorBefore("62a7...a600") +``` +```server-go +query.CursorBefore("62a7...a600") +``` +```server-nodejs +Query.cursorBefore("62a7...a600") +``` +```server-rust +Query::cursor_before("62a7...a600").to_string() +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"cursorBefore","values":["62a7...a600"]}' +``` +```http +{"method":"cursorBefore","values":["62a7...a600"]} +``` +{% /multicode %} + +# Complex queries {% #complex-queries %} + +You can create complex queries by combining AND and OR operations. For example, to find documents that are either sci-fi released after 2010 or romance released before 2005: + +{% multicode %} +```client-web +const results = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +}); +``` +```client-flutter +final results = await vectorsDB.listDocuments( + '<DATABASE_ID>', + '<COLLECTION_ID>', + [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +); +``` +```server-nodejs +const results = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.or([ + Query.and([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greaterThan('metadata.year', '2010') + ]), + Query.and([ + Query.equal('metadata.genre', ['romance']), + Query.lessThan('metadata.year', '2005') + ]) + ]) + ] +}); +``` +```server-python +results = vectorsDB.list_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + queries=[ + Query.or_queries([ + Query.and_queries([ + Query.equal('metadata.genre', ['sci-fi']), + Query.greater_than('metadata.year', '2010') + ]), + Query.and_queries([ + Query.equal('metadata.genre', ['romance']), + Query.less_than('metadata.year', '2005') + ]) + ]) + ] +) +``` +```server-go +documents, err := vectorsDB.ListDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vectorsDB.WithListDocumentsQueries([]string{ + query.Or([]string{ + query.And([]string{ + query.Equal("metadata.genre", []string{"sci-fi"}), + query.GreaterThan("metadata.year", "2010"), + }), + query.And([]string{ + query.Equal("metadata.genre", []string{"romance"}), + query.LessThan("metadata.year", "2005"), + }), + }), + }), +) +if err != nil { + log.Fatal(err) +} +``` +```server-rust +let documents = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::or(vec![ + Query::and(vec![ + Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string(), + Query::greater_than("metadata.year", "2010").to_string(), + ]).to_string(), + Query::and(vec![ + Query::equal("metadata.genre", Value::Array(vec![Value::String("romance".to_string())])).to_string(), + Query::less_than("metadata.year", "2005").to_string(), + ]).to_string(), + ]).to_string(), + ]), + None, + None, + None, +).await?; +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]}' +``` +```http +{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]} +``` +{% /multicode %} + +This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: sci-fi released after 2010 OR romance released before 2005. +Each condition within the OR is composed of two AND conditions, one for the genre and one for the year. The database returns documents that match either of these combined conditions. + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Learn about vector search +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc new file mode 100644 index 00000000000..3586d5bc965 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/quick-start/+page.markdoc @@ -0,0 +1,1034 @@ +--- +layout: article +title: Start with VectorsDB +description: Get started with Appwrite VectorsDB. Follow a step-by-step guide to create your first database, add a collection with a fixed dimension, store embeddings with metadata, and read them back. +--- + + +VectorsDB stores embedding vectors so you can build features like semantic search, recommendations, and retrieval for AI applications. +This guide walks through creating a database, adding a collection with a fixed `dimension`, storing a document with its `embeddings` and `metadata`, and reading it back. + +These steps use a [Server SDK](/docs/sdks#server), which requires an [API key](/docs/advanced/platform/api-keys). + +{% section #create-database step=1 title="Create database" %} +Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and click **Create database**. Name it `Knowledge base` and choose **VectorsDB** as the database type. +Optionally, add a custom database ID. Select your preferred tier, then click **Create database**. + +You can also create a database programmatically with the `create` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: sdk.ID.unique(), + name: 'Knowledge base' +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.create({ + databaseId: sdk.ID.unique(), + name: 'Knowledge base' +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->create( + databaseId: ID::unique(), + name: 'Knowledge base' +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create( + database_id = ID.unique(), + name = 'Knowledge base' +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create( + database_id: ID.unique(), + name: 'Knowledge base' +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Database result = await vectorsDB.Create( + databaseId: ID.Unique(), + name: "Knowledge base" +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Database result = await vectorsDB.create( + databaseId: ID.unique(), + name: 'Knowledge base', +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.create( + databaseId = ID.unique(), + name = "Knowledge base", +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.create( + ID.unique(), + "Knowledge base", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let database = try await vectorsDB.create( + databaseId: ID.unique(), + name: "Knowledge base" +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create( + ID::unique(), + "Knowledge base", + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create \ + --database-id <DATABASE_ID> \ + --name "Knowledge base" +``` +{% /multicode %} +{% /section %} + +{% section #create-collection step=2 title="Create collection" %} +In the `Knowledge base` database, click **Create collection** and name it `Articles`. Optionally, add a custom collection ID. + +A VectorsDB collection has a fixed `dimension`, the length of the embedding vectors it holds. All documents in the collection must use vectors of that exact length, so set the `dimension` to match the embedding model you plan to use. This guide uses `dimension: 4` to keep the vectors short and readable. The default `nomic-embed-text` [embedding model](/docs/products/databases/vectorsdb/embeddings) produces 768-dimensional vectors, so a production collection would use `dimension: 768`. + +The schema is provisioned for you: an `embeddings` vector and an optional `metadata` object. There are no columns to define. + +Set the **dimension**, then add a **Read** permission for the **Any** role so anyone can read documents. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createCollection({ + databaseId: '<DATABASE_ID>', + collectionId: sdk.ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [sdk.Permission.read(sdk.Role.any())] // optional +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; +use Appwrite\Permission; +use Appwrite\Role; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID::unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission::read(Role::any())] // optional +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from appwrite.permission import Permission +from appwrite.role import Role + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_collection( + database_id = '<DATABASE_ID>', + collection_id = ID.unique(), + name = 'Articles', + dimension = 4, + permissions = [Permission.read(Role.any())] # optional +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Permission +include Appwrite::Role + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_collection( + database_id: '<DATABASE_ID>', + collection_id: ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission.read(Role.any())] # optional +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Collection result = await vectorsDB.CreateCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.Unique(), + name: "Articles", + dimension: 4, + permissions: new List<string> { Permission.Read(Role.Any()) } // optional +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Collection result = await vectorsDB.createCollection( + databaseId: '<DATABASE_ID>', + collectionId: ID.unique(), + name: 'Articles', + dimension: 4, + permissions: [Permission.read(Role.any())], // optional +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.Permission +import io.appwrite.Role +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createCollection( + databaseId = "<DATABASE_ID>", + collectionId = ID.unique(), + name = "Articles", + dimension = 4, + permissions = listOf(Permission.read(Role.any())), // optional +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createCollection( + "<DATABASE_ID>", + ID.unique(), + "Articles", + 4, + List.of(Permission.read(Role.any())), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let collection = try await vectorsDB.createCollection( + databaseId: "<DATABASE_ID>", + collectionId: ID.unique(), + name: "Articles", + dimension: 4, + permissions: [Permission.read(Role.any())] // optional +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use appwrite::permission::Permission; +use appwrite::role::Role; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_collection( + "<DATABASE_ID>", + ID::unique(), + "Articles", + 4, + Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional) + None, // documentSecurity (optional) + None, // enabled (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-collection \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --name "Articles" \ + --dimension 4 \ + --permissions 'read("any")' +``` +{% /multicode %} +{% /section %} + +{% section #store-documents step=3 title="Store documents" %} +A document's data is an `embeddings` vector and an optional `metadata` object. +The `embeddings` array length must equal the collection's `dimension`, and `metadata` is free-form JSON you can use to store any data you want to keep alongside the vector. + +The vectors here are hardcoded so the example runs on its own. In an application, you generate them from your text with [embeddings](/docs/products/databases/vectorsdb/embeddings). + +Use the `createDocument` method to store the embedding and its metadata. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Getting started with Appwrite', tags: ['guide'] } + } +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Getting started with Appwrite', tags: ['guide'] } + } +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\ID; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Getting started with Appwrite', 'tags' => ['guide']] + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = ID.unique(), + data = { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + } +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + "embeddings" => [0.12, 0.84, 0.33, 0.57], + "metadata" => { "title" => "Getting started with Appwrite", "tags" => ["guide"] } + } +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Document result = await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> { + { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } }, + { "metadata", new Dictionary<string, object> { { "title", "Getting started with Appwrite" }, { "tags", new List<string> { "guide" } } } } + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Document result = await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + }, +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.ID +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.createDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = ID.unique(), + data = mapOf( + "embeddings" to listOf(0.12, 0.84, 0.33, 0.57), + "metadata" to mapOf("title" to "Getting started with Appwrite", "tags" to listOf("guide")) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.ID; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; +import java.util.Map; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID.unique(), + Map.of( + "embeddings", List.of(0.12, 0.84, 0.33, 0.57), + "metadata", Map.of("title", "Getting started with Appwrite", "tags", List.of("guide")) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let document = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Getting started with Appwrite", "tags": ["guide"]] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } + }), + None, // permissions (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id 'unique()' \ + --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] } }' +``` +{% /multicode %} + +The response includes the stored `embeddings` and `metadata` alongside the document's system fields. + +```json +{ + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Getting started with Appwrite", "tags": ["guide"] }, + "$id": "6a43c07100195469a012", + "$sequence": "1", + "$createdAt": "2026-06-30T13:11:13.441+00:00", + "$updatedAt": "2026-06-30T13:11:13.441+00:00", + "$permissions": [], + "$databaseId": "650125c64b3c25ce4bc4", + "$collectionId": "650125cff227cf9f95ad" +} +``` +{% /section %} + +{% section #read-documents step=4 title="Read documents" %} +To read documents back from your collection, use the `listDocuments` method. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.limit(10) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::limit(10) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.limit(10) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Queries; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.Limit(10) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.limit(10) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val response = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.limit(10) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.limit(10) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let documents = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.limit(10) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![Query::limit(10).to_string()]), + None, // transactionId (optional) + None, // total (optional) + None, // ttl (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"limit","values":[10]}' +``` +{% /multicode %} +{% /section %} + +{% section #next-steps step=5 title="Next steps" %} +You now have a database, a collection, and a document holding an embedding with its metadata. From here: + +- Generate vectors from your text instead of hardcoding them with [embeddings](/docs/products/databases/vectorsdb/embeddings). +- Find the documents closest to a query vector with [vector search](/docs/products/databases/vectorsdb/vector-search). + +{% arrow_link href="/docs/products/databases/vectorsdb/vector-search" %} +Run vector search +{% /arrow_link %} +{% /section %} diff --git a/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc new file mode 100644 index 00000000000..2956294d759 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/timestamp-overrides/+page.markdoc @@ -0,0 +1,1302 @@ +--- +layout: article +title: Timestamp overrides +description: Set custom $createdAt and $updatedAt timestamps for your documents when using server SDKs. +--- + +When creating or updating documents, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. + +{% info title="Server SDKs required" %} +To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These attributes can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). +{% /info %} + +# Setting custom timestamps {% #setting-custom-timestamps %} + +You can override a document's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these attributes are not provided, Appwrite will set them automatically. + +Custom timestamps work with all document operations: create, update, upsert, and their bulk variants. + +## Single document operations {% #single-document-operations %} + +When working with individual documents, you can set custom timestamps during create, update, and upsert operations. + +### Create with custom timestamps {% #create-custom %} + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<PROJECT_ID>') + .setKey('<API_KEY>'); + +const vectorsDB = new sdk.VectorsDB(client); + +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Hamlet' } + } +}); +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Hamlet'] + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date() +let createdAt = isoFormatter.string(from: customDate) +let updatedAt = isoFormatter.string(from: customDate) + +do { + let created = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "$createdAt": createdAt, + "$updatedAt": updatedAt, + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Hamlet"] + ] + ) + print("Created:", created) +} catch { + print("Create error:", error) +} +``` +```server-python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.id import ID +from datetime import datetime, timezone + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') +client.set_project('<PROJECT_ID>') +client.set_key('<API_KEY>') + +vectors_db = VectorsDB(client) + +iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() + +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': iso, + '$updatedAt': iso, + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet' } + } +) +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 + +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => { 'title' => 'Hamlet' } + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); + +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = customDate, + ["$updatedAt"] = customDate, + ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 }, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet" } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); + +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': customDate, + '\$updatedAt': customDate, + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Hamlet' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Hamlet" } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Update with custom timestamps {% #update-custom %} + +When updating documents, you can also set a custom `$updatedAt` timestamp. The existing `$createdAt` is preserved unless you provide a new one: + +{% multicode %} +```server-nodejs +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), + metadata: { title: 'Hamlet, revised' } + } +}); +``` +```server-php +$vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: [ + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'metadata' => ['title' => 'Hamlet, revised'] + ] +); +``` +```server-python +from datetime import datetime, timezone + +vectors_db.update_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id='<DOCUMENT_ID>', + data={ + '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), + 'metadata': { 'title': 'Hamlet, revised' } + } +) +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let updated = try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: [ + "$updatedAt": updatedAt, + "metadata": ["title": "Hamlet, revised"] + ] + ) + print("Updated:", updated) +} catch { + print("Update error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { + '$updatedAt' => custom_date, + 'metadata' => { 'title' => 'Hamlet, revised' } + } +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = customDate, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet, revised" } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { + '\$updatedAt': customDate, + 'metadata': { 'title': 'Hamlet, revised' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ + "$updatedAt": "2025-08-10T12:34:56.000Z", + "metadata": { "title": "Hamlet, revised" } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("Updated: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Bulk operations {% #bulk-operations %} + +Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch: + +### Bulk create {% #bulk-create %} + +{% multicode %} +```server-nodejs +await vectorsDB.createDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + embeddings: [0.1, 0.1, 0.1, 0.1], + metadata: { batch: 1 } + }, + { + '$id': sdk.ID.unique(), + '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), + embeddings: [0.2, 0.2, 0.2, 0.2], + metadata: { batch: 2 } + } + ] +}); +``` +```server-python +vectors_db.create_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + documents=[ + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.1, 0.1, 0.1, 0.1], + 'metadata': { 'batch': 1 } + }, + { + '$id': ID.unique(), + '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.2, 0.2, 0.2, 0.2], + 'metadata': { 'batch': 2 } + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->createDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.1, 0.1, 0.1, 0.1], + 'metadata' => ['batch' => 1] + ], + [ + '$id' => ID::unique(), + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.2, 0.2, 0.2, 0.2], + 'metadata' => ['batch' => 2] + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + +let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkCreated = try await vectorsDB.createDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: [ + [ + "$id": ID.unique(), + "$createdAt": first, + "$updatedAt": first, + "embeddings": [0.1, 0.1, 0.1, 0.1], + "metadata": ["batch": 1] + ], + [ + "$id": ID.unique(), + "$createdAt": second, + "$updatedAt": second, + "embeddings": [0.2, 0.2, 0.2, 0.2], + "metadata": ["batch": 2] + ] + ] + ) + print("Bulk create:", bulkCreated) +} catch { + print("Bulk create error:", error) +} +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +first = Time.parse('<CUSTOM_DATE>').iso8601 +second = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.create_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + documents: [ + { + '$id' => ID.unique(), + '$createdAt' => first, + '$updatedAt' => first, + 'embeddings' => [0.1, 0.1, 0.1, 0.1], + 'metadata' => { 'batch' => 1 } + }, + { + '$id' => ID.unique(), + '$createdAt' => second, + '$updatedAt' => second, + 'embeddings' => [0.2, 0.2, 0.2, 0.2], + 'metadata' => { 'batch' => 2 } + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.CreateDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = first, + ["$updatedAt"] = first, + ["embeddings"] = new List<double> { 0.1, 0.1, 0.1, 0.1 }, + ["metadata"] = new Dictionary<string, object> { ["batch"] = 1 } + }, + new Dictionary<string, object> + { + ["$id"] = ID.Unique(), + ["$createdAt"] = second, + ["$updatedAt"] = second, + ["embeddings"] = new List<double> { 0.2, 0.2, 0.2, 0.2 }, + ["metadata"] = new Dictionary<string, object> { ["batch"] = 2 } + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.createDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '\$id': ID.unique(), + '\$createdAt': first, + '\$updatedAt': first, + 'embeddings': [0.1, 0.1, 0.1, 0.1], + 'metadata': { 'batch': 1 } + }, + { + '\$id': ID.unique(), + '\$createdAt': second, + '\$updatedAt': second, + 'embeddings': [0.2, 0.2, 0.2, 0.2], + 'metadata': { 'batch': 2 } + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": ID::unique(), + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2024-01-01T00:00:00.000Z", + "embeddings": [0.1, 0.1, 0.1, 0.1], + "metadata": { "batch": 1 } + }), + json!({ + "$id": ID::unique(), + "$createdAt": "2024-02-01T00:00:00.000Z", + "$updatedAt": "2024-02-01T00:00:00.000Z", + "embeddings": [0.2, 0.2, 0.2, 0.2], + "metadata": { "batch": 2 } + }), + ], + ).await?; + + println!("Bulk create: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +### Bulk upsert {% #bulk-upsert %} + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '$id': '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), + '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), + embeddings: [0.3, 0.3, 0.3, 0.3], + metadata: { source: 'sync' } + } + ] +}); +``` +```server-python +vectors_db.upsert_documents( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + documents=[ + { + '$id': '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), + '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), + 'embeddings': [0.3, 0.3, 0.3, 0.3], + 'metadata': { 'source': 'sync' } + } + ] +) +``` +```server-php +use Appwrite\Client; +use Appwrite\ID; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + ->setProject('<YOUR_PROJECT_ID>') + ->setKey('<YOUR_API_KEY>'); + +$vectorsDB = new VectorsDB($client); + +$vectorsDB->upsertDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + [ + '$id' => '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM), + 'embeddings' => [0.3, 0.3, 0.3, 0.3], + 'metadata' => ['source' => 'sync'] + ], + ] +); +``` +```server-swift +import Appwrite +import Foundation + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .setProject("<YOUR_PROJECT_ID>") + .setKey("<YOUR_API_KEY>") + +let vectorsDB = VectorsDB(client) + +let isoFormatter = ISO8601DateFormatter() +isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] +let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) +let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()) + +do { + let bulkUpserted = try await vectorsDB.upsertDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: [ + [ + "$id": "<DOCUMENT_ID_OR_NEW_ID>", + "$createdAt": createdAt, + "$updatedAt": updatedAt, + "embeddings": [0.3, 0.3, 0.3, 0.3], + "metadata": ["source": "sync"] + ] + ] + ) + print("Bulk upsert:", bulkUpserted) +} catch { + print("Bulk upsert error:", error) +} +``` +```server-ruby +require 'appwrite' +require 'time' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') + .set_project('<YOUR_PROJECT_ID>') + .set_key('<YOUR_API_KEY>') + +vectors_db = VectorsDB.new(client) + +custom_date = Time.parse('<CUSTOM_DATE>').iso8601 + +vectors_db.upsert_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + documents: [ + { + '$id' => '<DOCUMENT_ID_OR_NEW_ID>', + '$createdAt' => custom_date, + '$updatedAt' => custom_date, + 'embeddings' => [0.3, 0.3, 0.3, 0.3], + 'metadata' => { 'source' => 'sync' } + } + ] +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") + .SetProject("<YOUR_PROJECT_ID>") + .SetKey("<YOUR_API_KEY>"); + +VectorsDB vectorsDB = new VectorsDB(client); + +string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); +string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O"); + +await vectorsDB.UpsertDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documents: new List<object> + { + new Dictionary<string, object> + { + ["$id"] = "<DOCUMENT_ID_OR_NEW_ID>", + ["$createdAt"] = createdAt, + ["$updatedAt"] = updatedAt, + ["embeddings"] = new List<double> { 0.3, 0.3, 0.3, 0.3 }, + ["metadata"] = new Dictionary<string, object> { ["source"] = "sync" } + } + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') + .setProject('<YOUR_PROJECT_ID>') + .setKey('<YOUR_API_KEY>'); + +VectorsDB vectorsDB = VectorsDB(client); + +String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); +String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String(); + +await vectorsDB.upsertDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documents: [ + { + '\$id': '<DOCUMENT_ID_OR_NEW_ID>', + '\$createdAt': createdAt, + '\$updatedAt': updatedAt, + 'embeddings': [0.3, 0.3, 0.3, 0.3], + 'metadata': { 'source': 'sync' } + } + ], +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + vec![ + json!({ + "$id": "<DOCUMENT_ID_OR_NEW_ID>", + "$createdAt": "2024-01-01T00:00:00.000Z", + "$updatedAt": "2025-01-01T00:00:00.000Z", + "embeddings": [0.3, 0.3, 0.3, 0.3], + "metadata": { "source": "sync" } + }), + ], + None, // transactionId (optional) + ).await?; + + println!("Bulk upsert: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +# Common use cases {% #use-cases %} + +Custom timestamps are particularly useful in several scenarios: + +## Data migration {% #data-migration %} +When migrating existing vectors from another system, you can preserve the original +creation and modification times: + +{% multicode %} +```server-nodejs +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + embeddings: [0.12, 0.84, 0.33, 0.57], + metadata: { title: 'Imported post' } + } +}); +``` +```server-php +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => ['title' => 'Imported post'] + ] +); +``` +```server-swift +let _ = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": ["title": "Imported post"] + ] +) +``` +```server-python +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt': '<LAST_MODIFIED_ISO>', + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Imported post' } + } +) +``` +```server-ruby +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', + '$updatedAt' => '<LAST_MODIFIED_ISO>', + 'embeddings' => [0.12, 0.84, 0.33, 0.57], + 'metadata' => { 'title' => 'Imported post' } + } +) +``` +```server-dotnet +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", + ["$updatedAt"] = "<LAST_MODIFIED_ISO>", + ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 }, + ["metadata"] = new Dictionary<string, object> { ["title"] = "Imported post" } + } +); +``` +```server-dart +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', + '\$updatedAt': '<LAST_MODIFIED_ISO>', + 'embeddings': [0.12, 0.84, 0.33, 0.57], + 'metadata': { 'title': 'Imported post' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", + "$updatedAt": "<LAST_MODIFIED_ISO>", + "embeddings": [0.12, 0.84, 0.33, 0.57], + "metadata": { "title": "Imported post" } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Backdating records {% #backdating %} +For historical data entry or when creating records that represent past events: + +{% multicode %} +```server-nodejs +await vectorsDB.createDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: sdk.ID.unique(), + data: { + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + embeddings: [0.5, 0.5, 0.5, 0.5], + metadata: { type: 'year-end-bonus', amount: 1000 } + } +}); +``` +```server-php +$vectorsDB->createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID::unique(), + data: [ + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'embeddings' => [0.5, 0.5, 0.5, 0.5], + 'metadata' => ['type' => 'year-end-bonus', 'amount' => 1000] + ] +); +``` +```server-swift +let _ = try await vectorsDB.createDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.unique(), + data: [ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "embeddings": [0.5, 0.5, 0.5, 0.5], + "metadata": ["type": "year-end-bonus", "amount": 1000] + ] +) +``` +```server-python +vectors_db.create_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id=ID.unique(), + data={ + '$createdAt': '2023-12-31T23:59:59.000Z', + '$updatedAt': '2023-12-31T23:59:59.000Z', + 'embeddings': [0.5, 0.5, 0.5, 0.5], + 'metadata': { 'type': 'year-end-bonus', 'amount': 1000 } + } +) +``` +```server-ruby +vectors_db.create_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: ID.unique(), + data: { + '$createdAt' => '2023-12-31T23:59:59.000Z', + '$updatedAt' => '2023-12-31T23:59:59.000Z', + 'embeddings' => [0.5, 0.5, 0.5, 0.5], + 'metadata' => { 'type' => 'year-end-bonus', 'amount' => 1000 } + } +) +``` +```server-dotnet +await vectorsDB.CreateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: ID.Unique(), + data: new Dictionary<string, object> + { + ["$createdAt"] = "2023-12-31T23:59:59.000Z", + ["$updatedAt"] = "2023-12-31T23:59:59.000Z", + ["embeddings"] = new List<double> { 0.5, 0.5, 0.5, 0.5 }, + ["metadata"] = new Dictionary<string, object> { ["type"] = "year-end-bonus", ["amount"] = 1000 } + } +); +``` +```server-dart +await vectorsDB.createDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: ID.unique(), + data: { + '\$createdAt': '2023-12-31T23:59:59.000Z', + '\$updatedAt': '2023-12-31T23:59:59.000Z', + 'embeddings': [0.5, 0.5, 0.5, 0.5], + 'metadata': { 'type': 'year-end-bonus', 'amount': 1000 } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::id::ID; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + ID::unique(), + json!({ + "$createdAt": "2023-12-31T23:59:59.000Z", + "$updatedAt": "2023-12-31T23:59:59.000Z", + "embeddings": [0.5, 0.5, 0.5, 0.5], + "metadata": { "type": "year-end-bonus", "amount": 1000 } + }), + None, // permissions (optional) + ).await?; + + println!("Created: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +## Synchronization {% #synchronization %} +When synchronizing data between systems while maintaining timestamp consistency: + +{% multicode %} +```server-nodejs +await vectorsDB.upsertDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + embeddings: [0.6, 0.6, 0.6, 0.6], + metadata: { profile: 'external' } + } +}); +``` +```server-php +$vectorsDB->upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: [ + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings' => [0.6, 0.6, 0.6, 0.6], + 'metadata' => ['profile' => 'external'] + ] +); +``` +```server-swift +let _ = try await vectorsDB.upsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: [ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "embeddings": [0.6, 0.6, 0.6, 0.6], + "metadata": ["profile": "external"] + ] +) +``` +```server-python +vectors_db.upsert_document( + database_id='<DATABASE_ID>', + collection_id='<COLLECTION_ID>', + document_id='<DOCUMENT_ID_OR_NEW_ID>', + data={ + '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings': [0.6, 0.6, 0.6, 0.6], + 'metadata': { 'profile': 'external' } + } +) +``` +```server-ruby +vectors_db.upsert_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings' => [0.6, 0.6, 0.6, 0.6], + 'metadata' => { 'profile' => 'external' } + } +) +``` +```server-dotnet +await vectorsDB.UpsertDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID_OR_NEW_ID>", + data: new Dictionary<string, object> + { + ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", + ["embeddings"] = new List<double> { 0.6, 0.6, 0.6, 0.6 }, + ["metadata"] = new Dictionary<string, object> { ["profile"] = "external" } + } +); +``` +```server-dart +await vectorsDB.upsertDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_OR_NEW_ID>', + data: { + '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', + 'embeddings': [0.6, 0.6, 0.6, 0.6], + 'metadata': { 'profile': 'external' } + }, +); +``` +```rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); + client.set_project("<PROJECT_ID>"); + client.set_key("<API_KEY>"); + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.upsert_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID_OR_NEW_ID>", + Some(json!({ + "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", + "embeddings": [0.6, 0.6, 0.6, 0.6], + "metadata": { "profile": "external" } + })), + None, // permissions (optional) + None, // transactionId (optional) + ).await?; + + println!("Upserted: {:?}", result); + Ok(()) +} +``` +{% /multicode %} + +{% info title="Timestamp format and usage" %} +- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. +- You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. +{% /info %} diff --git a/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc new file mode 100644 index 00000000000..00dfdfb011e --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/transactions/+page.markdoc @@ -0,0 +1,1240 @@ +--- +layout: article +title: Transactions +description: Stage multiple VectorsDB operations and commit them atomically. Group changes across databases and collections with ordering, isolation, and conflict detection. +--- + +Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and collections. + +# How transactions work {% #how-transactions-work %} + +1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. +2. Stage operations by passing the `transactionId` parameter to supported document, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. +3. Call the [updateTransaction](#commit-or-rollback) method to commit or roll back. + +On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected document changed outside your transaction, the commit fails with a conflict. + +{% info title="Scope and limitations" %} +You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions. +{% /info %} + +# Limits {% #limits %} + +The maximum number of operations you can stage per transaction depends on your plan: + +| Plan | Max operations per transaction | +|------|-------------------------------| +| Free | 100 | +| Pro | 1,000 | + +# Create a transaction {% #create-a-transaction %} + +Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$tx = $vectorsDB->createTransaction(); +// $tx['$id'] is your transactionId +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +tx = vectors_db.create_transaction() +# tx['$id'] is your transactionId +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +tx = vectors_db.create_transaction +# tx['$id'] is your transactionId +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +var tx = await vectorsDB.CreateTransaction(); +// tx.Id is your transactionId +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +final tx = await vectorsDB.createTransaction(); +// tx.$id is your transactionId +``` +```kotlin +import io.appwrite.Client +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val tx = vectorsDB.createTransaction() +// tx.id is your transactionId +``` +```java +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTransaction(new CoroutineCallback<>((tx, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(tx); +})); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let tx = try await vectorsDB.createTransaction() +// tx.id is your transactionId +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + // tx.id is your transaction_id + + Ok(()) +} +``` +```bash +appwrite vectors-db create-transaction +``` +{% /multicode %} + +# Stage operations {% #stage-operations %} + +Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. + +When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target collection is not modified until you commit the transaction. + +## Stage single operations {% #stage-single-operations %} + +Update, upsert, and delete operations accept `transactionId`, as well as their bulk versions (`updateDocuments`, `upsertDocuments`, `deleteDocuments`). To stage the first write to a brand new document, use `upsertDocument` with `transactionId`. + +{% multicode %} +```server-nodejs +// Update inside a transaction +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { metadata: { title: 'Hamlet', year: 1602 } }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```deno +// Update inside a transaction +await vectorsDB.updateDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { metadata: { title: 'Hamlet', year: 1602 } }, + transactionId: tx.$id +}); + +// Delete inside a transaction +await vectorsDB.deleteDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```php +// Update inside a transaction +$vectorsDB->updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: ['metadata' => ['title' => 'Hamlet', 'year' => 1602]], + transactionId: $tx['$id'] +); + +// Delete inside a transaction +$vectorsDB->deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: $tx['$id'] +); +``` +```python +# Update inside a transaction +vectors_db.update_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + data = { "metadata": { "title": "Hamlet", "year": 1602 } }, + transaction_id = tx['$id'] +) + +# Delete inside a transaction +vectors_db.delete_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + transaction_id = tx['$id'] +) +``` +```ruby +# Update inside a transaction +vectors_db.update_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + data: { "metadata" => { "title" => "Hamlet", "year" => 1602 } }, + transaction_id: tx['$id'] +) + +# Delete inside a transaction +vectors_db.delete_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + transaction_id: tx['$id'] +) +``` +```csharp +// Update inside a transaction +await vectorsDB.UpdateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: new Dictionary<string, object> { + { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } } + }, + transactionId: tx.Id +); + +// Delete inside a transaction +await vectorsDB.DeleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.Id +); +``` +```dart +// Update inside a transaction +await vectorsDB.updateDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + data: { "metadata": { "title": "Hamlet", "year": 1602 } }, + transactionId: tx.$id +); + +// Delete inside a transaction +await vectorsDB.deleteDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +); +``` +```kotlin +// Update inside a transaction +vectorsDB.updateDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + data = mapOf("metadata" to mapOf("title" to "Hamlet", "year" to 1602)), + transactionId = tx.id +) + +// Delete inside a transaction +vectorsDB.deleteDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + transactionId = tx.id +) +``` +```java +// Update inside a transaction (asynchronous) +vectorsDB.updateDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Map.of("metadata", Map.of("title", "Hamlet", "year", 1602)), + listOf(), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(document); + }) +); +``` +```swift +// Update inside a transaction +try await vectorsDB.updateDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + data: ["metadata": ["title": "Hamlet", "year": 1602]], + transactionId: tx.id +) + +// Delete inside a transaction +try await vectorsDB.deleteDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Update inside a transaction + vectors_db.update_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(json!({ "metadata": { "title": "Hamlet", "year": 1602 } })), + None, + Some(&tx.id), + ).await?; + + // Delete inside a transaction + vectors_db.delete_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db update-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --data '{ "metadata": { "title": "Hamlet", "year": 1602 } }' \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +You can read uncommitted changes back inside the same transaction by passing `transactionId` to `getDocument`. A plain read without `transactionId` still returns the committed document. + +{% multicode %} +```server-nodejs +// Read your own staged write +const staged = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```deno +// Read your own staged write +const staged = await vectorsDB.getDocument({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +}); +``` +```php +// Read your own staged write +$staged = $vectorsDB->getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: $tx['$id'] +); +``` +```python +# Read your own staged write +staged = vectors_db.get_document( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + document_id = '<DOCUMENT_ID>', + transaction_id = tx['$id'] +) +``` +```ruby +# Read your own staged write +staged = vectors_db.get_document( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + document_id: '<DOCUMENT_ID>', + transaction_id: tx['$id'] +) +``` +```csharp +// Read your own staged write +var staged = await vectorsDB.GetDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.Id +); +``` +```dart +// Read your own staged write +final staged = await vectorsDB.getDocument( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID>', + transactionId: tx.$id +); +``` +```kotlin +// Read your own staged write +val staged = vectorsDB.getDocument( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + documentId = "<DOCUMENT_ID>", + transactionId = tx.id +) +``` +```java +// Read your own staged write (asynchronous) +vectorsDB.getDocument( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + listOf(), + "<TRANSACTION_ID>", + new CoroutineCallback<>((document, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(document); + }) +); +``` +```swift +// Read your own staged write +let staged = try await vectorsDB.getDocument( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + documentId: "<DOCUMENT_ID>", + transactionId: tx.id +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Read your own staged write + let staged = vectors_db.get_document( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "<DOCUMENT_ID>", + None, + Some(&tx.id), + ).await?; + + Ok(()) +} +``` +```bash +appwrite vectors-db get-document \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --document-id <DOCUMENT_ID> \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +## Stage many with createOperations {% #create-operations %} + +Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects: + +```json +[ + { + "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID>", + "data": {} + } +] +``` + +For `create`, `update`, and `upsert` actions, pass the document `data` (an `embeddings` vector and optional `metadata`). For `delete`, the `documentId` is enough. + +{% multicode %} +```server-nodejs +// Stage multiple operations at once +await vectorsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'update', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_1>', + data: { metadata: { title: 'Macbeth' } } + }, + { + action: 'delete', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_2>' + } + ] +}); +``` +```deno +// Stage multiple operations at once +await vectorsDB.createOperations({ + transactionId: tx.$id, + operations: [ + { + action: 'update', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_1>', + data: { metadata: { title: 'Macbeth' } } + }, + { + action: 'delete', + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + documentId: '<DOCUMENT_ID_2>' + } + ] +}); +``` +```php +$vectorsDB->createOperations( + transactionId: $tx['$id'], + operations: [ + [ + 'action' => 'update', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_1>', + 'data' => [ 'metadata' => [ 'title' => 'Macbeth' ] ] + ], + [ + 'action' => 'delete', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_2>' + ] + ] +); +``` +```python +vectors_db.create_operations( + transaction_id = tx['$id'], + operations = [ + { + 'action': 'update', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_1>', + 'data': { 'metadata': { 'title': 'Macbeth' } } + }, + { + 'action': 'delete', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_2>' + } + ] +) +``` +```ruby +vectors_db.create_operations( + transaction_id: tx['$id'], + operations: [ + { + 'action' => 'update', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_1>', + 'data' => { 'metadata' => { 'title' => 'Macbeth' } } + }, + { + 'action' => 'delete', + 'databaseId' => '<DATABASE_ID>', + 'collectionId' => '<COLLECTION_ID>', + 'documentId' => '<DOCUMENT_ID_2>' + } + ] +) +``` +```csharp +await vectorsDB.CreateOperations( + transactionId: tx.Id, + operations: new List<object> + { + new Dictionary<string, object> + { + ["action"] = "update", + ["databaseId"] = "<DATABASE_ID>", + ["collectionId"] = "<COLLECTION_ID>", + ["documentId"] = "<DOCUMENT_ID_1>", + ["data"] = new Dictionary<string, object> { + ["metadata"] = new Dictionary<string, object> { ["title"] = "Macbeth" } + } + }, + new Dictionary<string, object> + { + ["action"] = "delete", + ["databaseId"] = "<DATABASE_ID>", + ["collectionId"] = "<COLLECTION_ID>", + ["documentId"] = "<DOCUMENT_ID_2>" + } + } +); +``` +```dart +await vectorsDB.createOperations( + transactionId: tx.$id, + operations: [ + { + 'action': 'update', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_1>', + 'data': { 'metadata': { 'title': 'Macbeth' } } + }, + { + 'action': 'delete', + 'databaseId': '<DATABASE_ID>', + 'collectionId': '<COLLECTION_ID>', + 'documentId': '<DOCUMENT_ID_2>' + } + ] +); +``` +```kotlin +vectorsDB.createOperations( + transactionId = tx.id, + operations = listOf( + mapOf( + "action" to "update", + "databaseId" to "<DATABASE_ID>", + "collectionId" to "<COLLECTION_ID>", + "documentId" to "<DOCUMENT_ID_1>", + "data" to mapOf("metadata" to mapOf("title" to "Macbeth")) + ), + mapOf( + "action" to "delete", + "databaseId" to "<DATABASE_ID>", + "collectionId" to "<COLLECTION_ID>", + "documentId" to "<DOCUMENT_ID_2>" + ) + ) +) +``` +```java +// Stage multiple operations at once (asynchronous) +List<Map<String, Object>> operations = Arrays.asList( + Map.of( + "action", "update", + "databaseId", "<DATABASE_ID>", + "collectionId", "<COLLECTION_ID>", + "documentId", "<DOCUMENT_ID_1>", + "data", Map.of("metadata", Map.of("title", "Macbeth")) + ), + Map.of( + "action", "delete", + "databaseId", "<DATABASE_ID>", + "collectionId", "<COLLECTION_ID>", + "documentId", "<DOCUMENT_ID_2>" + ) +); + +vectorsDB.createOperations( + "<TRANSACTION_ID>", + operations, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +try await vectorsDB.createOperations( + transactionId: tx.id, + operations: [ + [ + "action": "update", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_1>", + "data": ["metadata": ["title": "Macbeth"]] + ], + [ + "action": "delete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_2>" + ] + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let tx = vectors_db.create_transaction(None).await?; + + // Stage multiple operations at once + vectors_db.create_operations( + &tx.id, + Some(vec![ + json!({ + "action": "update", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_1>", + "data": { "metadata": { "title": "Macbeth" } } + }), + json!({ + "action": "delete", + "databaseId": "<DATABASE_ID>", + "collectionId": "<COLLECTION_ID>", + "documentId": "<DOCUMENT_ID_2>" + }), + ]), + ).await?; + + Ok(()) +} +``` +{% /multicode %} + +# Commit or roll back {% #commit-or-rollback %} + +When you are done staging operations, call the `updateTransaction` method to finalize the transaction. Set `commit` to `true` to apply the staged operations, or `rollback` to `true` to discard them. + +{% multicode %} +```server-nodejs +// Commit +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```deno +// Commit +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + commit: true +}); + +// Or roll back +await vectorsDB.updateTransaction({ + transactionId: tx.$id, + rollback: true +}); +``` +```php +// Commit +$vectorsDB->updateTransaction( + transactionId: $tx['$id'], + commit: true +); + +// Roll back +$vectorsDB->updateTransaction( + transactionId: $tx['$id'], + rollback: true +); +``` +```python +# Commit +vectors_db.update_transaction( + transaction_id = tx['$id'], + commit = True +) + +# Roll back +vectors_db.update_transaction( + transaction_id = tx['$id'], + rollback = True +) +``` +```ruby +# Commit +vectors_db.update_transaction( + transaction_id: tx['$id'], + commit: true +) + +# Roll back +vectors_db.update_transaction( + transaction_id: tx['$id'], + rollback: true +) +``` +```csharp +// Commit +await vectorsDB.UpdateTransaction( + transactionId: tx.Id, + commit: true +); + +// Roll back +await vectorsDB.UpdateTransaction( + transactionId: tx.Id, + rollback: true +); +``` +```dart +// Commit +await vectorsDB.updateTransaction( + transactionId: tx.$id, + commit: true +); + +// Roll back +await vectorsDB.updateTransaction( + transactionId: tx.$id, + rollback: true +); +``` +```kotlin +// Commit +vectorsDB.updateTransaction( + transactionId = tx.id, + commit = true +) + +// Roll back +vectorsDB.updateTransaction( + transactionId = tx.id, + rollback = true +) +``` +```java +// Commit (asynchronous) +vectorsDB.updateTransaction( + "<TRANSACTION_ID>", + true, + false, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + +// Roll back (asynchronous) +vectorsDB.updateTransaction( + "<TRANSACTION_ID>", + false, + true, + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +// Commit +try await vectorsDB.updateTransaction( + transactionId: tx.id, + commit: true +) + +// Roll back +try await vectorsDB.updateTransaction( + transactionId: tx.id, + rollback: true +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Commit + vectors_db.update_transaction( + "<TRANSACTION_ID>", + Some(true), + None, + ).await?; + + // Roll back + vectors_db.update_transaction( + "<TRANSACTION_ID>", + None, + Some(true), + ).await?; + + Ok(()) +} +``` +```bash +# Commit +appwrite vectors-db update-transaction \ + --transaction-id <TRANSACTION_ID> \ + --commit true + +# Roll back +appwrite vectors-db update-transaction \ + --transaction-id <TRANSACTION_ID> \ + --rollback true +``` +{% /multicode %} + +# Inspect and delete transactions {% #inspect-and-delete %} + +Use `getTransaction` to check a transaction's status and operation count, `listTransactions` to list transactions across all databases, and `deleteTransaction` to discard a transaction without committing it. + +{% multicode %} +```server-nodejs +// Get one transaction +const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' }); + +// List transactions +const list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' }); +``` +```deno +// Get one transaction +const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' }); + +// List transactions +const list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' }); +``` +```php +// Get one transaction +$tx = $vectorsDB->getTransaction(transactionId: '<TRANSACTION_ID>'); + +// List transactions +$list = $vectorsDB->listTransactions(); + +// Delete a transaction +$vectorsDB->deleteTransaction(transactionId: '<TRANSACTION_ID>'); +``` +```python +# Get one transaction +tx = vectors_db.get_transaction(transaction_id = '<TRANSACTION_ID>') + +# List transactions +list = vectors_db.list_transactions() + +# Delete a transaction +vectors_db.delete_transaction(transaction_id = '<TRANSACTION_ID>') +``` +```ruby +# Get one transaction +tx = vectors_db.get_transaction(transaction_id: '<TRANSACTION_ID>') + +# List transactions +list = vectors_db.list_transactions + +# Delete a transaction +vectors_db.delete_transaction(transaction_id: '<TRANSACTION_ID>') +``` +```csharp +// Get one transaction +var tx = await vectorsDB.GetTransaction(transactionId: "<TRANSACTION_ID>"); + +// List transactions +var list = await vectorsDB.ListTransactions(); + +// Delete a transaction +await vectorsDB.DeleteTransaction(transactionId: "<TRANSACTION_ID>"); +``` +```dart +// Get one transaction +final tx = await vectorsDB.getTransaction(transactionId: '<TRANSACTION_ID>'); + +// List transactions +final list = await vectorsDB.listTransactions(); + +// Delete a transaction +await vectorsDB.deleteTransaction(transactionId: '<TRANSACTION_ID>'); +``` +```kotlin +// Get one transaction +val tx = vectorsDB.getTransaction(transactionId = "<TRANSACTION_ID>") + +// List transactions +val list = vectorsDB.listTransactions() + +// Delete a transaction +vectorsDB.deleteTransaction(transactionId = "<TRANSACTION_ID>") +``` +```java +// Get one transaction (asynchronous) +vectorsDB.getTransaction( + "<TRANSACTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); + +// List transactions (asynchronous) +vectorsDB.listTransactions(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); +})); + +// Delete a transaction (asynchronous) +vectorsDB.deleteTransaction( + "<TRANSACTION_ID>", + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +// Get one transaction +let tx = try await vectorsDB.getTransaction(transactionId: "<TRANSACTION_ID>") + +// List transactions +let list = try await vectorsDB.listTransactions() + +// Delete a transaction +try await vectorsDB.deleteTransaction(transactionId: "<TRANSACTION_ID>") +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + // Get one transaction + let tx = vectors_db.get_transaction("<TRANSACTION_ID>").await?; + + // List transactions + let list = vectors_db.list_transactions(None).await?; + + // Delete a transaction + vectors_db.delete_transaction("<TRANSACTION_ID>").await?; + + Ok(()) +} +``` +```bash +# Get one transaction +appwrite vectors-db get-transaction \ + --transaction-id <TRANSACTION_ID> + +# List transactions +appwrite vectors-db list-transactions + +# Delete a transaction +appwrite vectors-db delete-transaction \ + --transaction-id <TRANSACTION_ID> +``` +{% /multicode %} + +# Handle conflicts {% #handle-conflicts %} + +On commit, Appwrite verifies that documents affected by your transaction haven't changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. + +{% info title="Best practices" %} +Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple collections. +{% /info %} + +{% arrow_link href="/docs/references" %} +Explore the API references +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc b/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc new file mode 100644 index 00000000000..a663dcb0458 --- /dev/null +++ b/src/routes/docs/products/databases/vectorsdb/vector-search/+page.markdoc @@ -0,0 +1,556 @@ +--- +layout: article +title: Vector search +description: Run similarity search over your documents with Appwrite VectorsDB. Create an HNSW index on the embeddings field and rank documents by cosine, dot product, or Euclidean distance. +--- +Vector search finds the documents whose `embeddings` are closest to a query vector. +Instead of matching exact values, it ranks documents by similarity, so you can build features like semantic search, recommendations, and retrieval for AI applications. + +There are two steps: create an [index](/docs/products/databases/vectorsdb/collections) on the `embeddings` field so searches are fast, then pass a vector query to `listDocuments` to get documents ranked by similarity. + +# Create an index {% #create-an-index %} +Before you search, create an HNSW index on the `embeddings` field with `createIndex`. HNSW (Hierarchical Navigable Small World) is an approximate nearest neighbor index that keeps similarity search fast as your collection grows. + +The index `type` decides how similarity is measured. Use the `VectorsDBIndexType` enum to pick one: + +| Index type | Enum | Use when | +| --- | --- | --- | +| `hnsw_cosine` | `VectorsDBIndexType.HnswCosine` | You care about the direction of the vectors, not their magnitude. A common default for text embeddings. | +| `hnsw_dot` | `VectorsDBIndexType.HnswDot` | You want the dot product, which factors in both direction and magnitude. | +| `hnsw_euclidean` | `VectorsDBIndexType.HnswEuclidean` | You want the straight-line distance between vectors. | + +Match the index type to the search query you plan to run. A `hnsw_cosine` index serves `Query.vectorCosine` searches, `hnsw_dot` serves `Query.vectorDot`, and `hnsw_euclidean` serves `Query.vectorEuclidean`. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createIndex({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: sdk.VectorsDBIndexType.HnswCosine, + attributes: ['embeddings'] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.createIndex({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: sdk.VectorsDBIndexType.HnswCosine, + attributes: ['embeddings'] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Enums\VectorsDBIndexType; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->createIndex( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType::HNSWCOSINE(), + attributes: ['embeddings'] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.enums import VectorsDBIndexType + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.create_index( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + key = 'embeddings_index', + type = VectorsDBIndexType.HNSW_COSINE, + attributes = ['embeddings'] +) +``` +```ruby +require 'appwrite' + +include Appwrite +include Appwrite::Enums + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.create_index( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType::HNSW_COSINE, + attributes: ['embeddings'] +) +``` +```csharp +using Appwrite; +using Appwrite.Enums; +using Appwrite.Models; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +Index result = await vectorsDB.CreateIndex( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + key: "embeddings_index", + type: VectorsDBIndexType.HnswCosine, + attributes: new List<string> { "embeddings" } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; +import 'package:dart_appwrite/enums.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +Index result = await vectorsDB.createIndex( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + key: 'embeddings_index', + type: VectorsDBIndexType.hnswCosine, + attributes: ['embeddings'], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.enums.VectorsDBIndexType +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createIndex( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + key = "embeddings_index", + type = VectorsDBIndexType.HNSW_COSINE, + attributes = listOf("embeddings"), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.enums.VectorsDBIndexType; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createIndex( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "embeddings_index", + VectorsDBIndexType.HNSW_COSINE, + List.of("embeddings"), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite +import AppwriteEnums + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let index = try await vectorsDB.createIndex( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + key: "embeddings_index", + type: .hnswCosine, + attributes: ["embeddings"] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::enums::VectorsDBIndexType; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.create_index( + "<DATABASE_ID>", + "<COLLECTION_ID>", + "embeddings_index", + VectorsDBIndexType::HnswCosine, + vec!["embeddings"], + None, // orders (optional) + None, // lengths (optional) + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db create-index \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --key 'embeddings_index' \ + --type 'hnsw_cosine' \ + --attributes 'embeddings' +``` +{% /multicode %} + +The index is built in the background. New documents are added to the index as you create them, so you can keep writing while it builds. + +# Run a similarity search {% #run-a-similarity-search %} +To search, pass a vector query to `listDocuments`. Build the query with one of the `Query` vector methods, passing the field name `embeddings` and the query vector. The response returns documents ranked from most to least similar. + +| Query method | Use with index type | +| --- | --- | +| `Query.vectorCosine('embeddings', vector)` | `hnsw_cosine` | +| `Query.vectorDot('embeddings', vector)` | `hnsw_dot` | +| `Query.vectorEuclidean('embeddings', vector)` | `hnsw_euclidean` | + +The query vector must have the same `dimension` as the collection. You can combine the vector query with other queries, such as `Query.limit()` to cap how many results you get back. + +{% multicode %} +```server-nodejs +const sdk = require('node-appwrite'); + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```deno +import * as sdk from "npm:node-appwrite"; + +const client = new sdk.Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +const vectorsDB = new sdk.VectorsDB(client); + +const result = await vectorsDB.listDocuments({ + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + sdk.Query.limit(3) + ] +}); +``` +```php +<?php + +use Appwrite\Client; +use Appwrite\Services\VectorsDB; +use Appwrite\Query; + +$client = (new Client()) + ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('<YOUR_PROJECT_ID>') // Your project ID + ->setKey('<YOUR_API_KEY>'); // Your secret API key + +$vectorsDB = new VectorsDB($client); + +$result = $vectorsDB->listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query::vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query::limit(3) + ] +); +``` +```python +from appwrite.client import Client +from appwrite.services.vectors_db import VectorsDB +from appwrite.query import Query + +client = Client() +client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('<YOUR_PROJECT_ID>') # Your project ID +client.set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB(client) + +result = vectors_db.list_documents( + database_id = '<DATABASE_ID>', + collection_id = '<COLLECTION_ID>', + queries = [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint + .set_project('<YOUR_PROJECT_ID>') # Your project ID + .set_key('<YOUR_API_KEY>') # Your secret API key + +vectors_db = VectorsDB.new(client) + +result = vectors_db.list_documents( + database_id: '<DATABASE_ID>', + collection_id: '<COLLECTION_ID>', + queries: [ + Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```csharp +using Appwrite; +using Appwrite.Models; +using Appwrite.Services; +using Appwrite.Queries; + +Client client = new Client() + .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .SetProject("<YOUR_PROJECT_ID>") // Your project ID + .SetKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +DocumentList result = await vectorsDB.ListDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: new List<string> { + Query.VectorCosine("embeddings", new List<object> { 0.11, 0.21, 0.30, 0.40 }), + Query.Limit(3) + } +); +``` +```dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint + .setProject('<YOUR_PROJECT_ID>') // Your project ID + .setKey('<YOUR_API_KEY>'); // Your secret API key + +VectorsDB vectorsDB = VectorsDB(client); + +DocumentList result = await vectorsDB.listDocuments( + databaseId: '<DATABASE_ID>', + collectionId: '<COLLECTION_ID>', + queries: [ + Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ], +); +``` +```kotlin +import io.appwrite.Client +import io.appwrite.Query +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.listDocuments( + databaseId = "<DATABASE_ID>", + collectionId = "<COLLECTION_ID>", + queries = listOf( + Query.vectorCosine("embeddings", listOf(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), +) +``` +```java +import io.appwrite.Client; +import io.appwrite.Query; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; +import java.util.List; + +Client client = new Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>"); // Your secret API key + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "<DATABASE_ID>", + "<COLLECTION_ID>", + List.of( + Query.vectorCosine("embeddings", List.of(0.11, 0.21, 0.30, 0.40)), + Query.limit(3) + ), + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) +); +``` +```swift +import Appwrite + +let client = Client() + .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("<YOUR_PROJECT_ID>") // Your project ID + .setKey("<YOUR_API_KEY>") // Your secret API key + +let vectorsDB = VectorsDB(client) + +let result = try await vectorsDB.listDocuments( + databaseId: "<DATABASE_ID>", + collectionId: "<COLLECTION_ID>", + queries: [ + Query.vectorCosine("embeddings", [0.11, 0.21, 0.30, 0.40]), + Query.limit(3) + ] +) +``` +```server-rust +use appwrite::Client; +use appwrite::services::vectors_db::VectorsDB; +use appwrite::query::Query; +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = Client::new(); + client.set_endpoint("https://<REGION>.cloud.appwrite.io/v1"); // Your API Endpoint + client.set_project("<YOUR_PROJECT_ID>"); // Your project ID + client.set_key("<YOUR_API_KEY>"); // Your secret API key + + let vectors_db = VectorsDB::new(&client); + + let result = vectors_db.list_documents( + "<DATABASE_ID>", + "<COLLECTION_ID>", + Some(vec![ + Query::vector_cosine("embeddings", json!([0.11, 0.21, 0.30, 0.40])).to_string(), + Query::limit(3).to_string(), + ]), + None, // transaction_id + None, // total + None, // ttl + ).await?; + + println!("{:?}", result); + Ok(()) +} +``` +```bash +appwrite vectors-db list-documents \ + --database-id <DATABASE_ID> \ + --collection-id <COLLECTION_ID> \ + --queries '{"method":"vectorCosine","attribute":"embeddings","values":[[0.11,0.21,0.30,0.40]]}' '{"method":"limit","values":[3]}' +``` +{% /multicode %} + +To rank by dot product or Euclidean distance instead, swap `vectorCosine` for `vectorDot` or `vectorEuclidean`, and make sure your index uses the matching type. + +{% info title="One vector query per request" %} +A single `listDocuments` call accepts at most one vector query. You can still add non-vector queries such as `Query.limit()` to the same request, but combining two vector queries in one call is not supported. +{% /info %} + +{% info title="Vector search currently works with low dimensions only" %} +Vector queries are sent on a `GET` request, and each query string is capped at 4096 characters. A serialized query vector grows with its dimension, so once a vector reaches a few hundred dimensions it pushes the query past that limit and the request is rejected with a `400 general_argument_invalid` error: *"Value must be a valid string and at least 1 chars and no longer than 4096 chars"*. The exact cutoff depends on the precision of the numbers in your vector, but in practice it falls somewhere around 200 to 270 dimensions. + +This means vector search through the SDK currently works for low-dimensional vectors only. The built-in [embedding models](/docs/products/databases/vectorsdb/embeddings) produce 384- and 768-dimensional vectors, which exceed this limit, so you cannot yet search over those embeddings directly. Keep your search vectors small enough to stay under the 4096-character limit. +{% /info %} + +# Next steps {% #next-steps %} +Vector search ranks documents by similarity. To filter those results by the data stored alongside each vector, combine search with metadata queries. + +{% arrow_link href="/docs/products/databases/vectorsdb/queries" %} +Learn about querying metadata +{% /arrow_link %} From 1739db52271574e5692fc52016045ebcbcc3bce8 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale <atharva.deosthale17@gmail.com> Date: Wed, 1 Jul 2026 19:19:53 +0530 Subject: [PATCH 4/5] Add JSON import and export docs for DocumentsDB --- .optimize-cache.json | 2 + .../databases/documentsdb/+layout.svelte | 8 ++ .../documentsdb/json-exports/+page.markdoc | 96 ++++++++++++++++++ .../documentsdb/json-imports/+page.markdoc | 94 +++++++++++++++++ .../dark/documentsdb-import-export.avif | Bin 0 -> 35245 bytes .../databases/documentsdb-import-export.avif | Bin 0 -> 33739 bytes 6 files changed, 200 insertions(+) create mode 100644 src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc create mode 100644 src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc create mode 100644 static/images/docs/databases/dark/documentsdb-import-export.avif create mode 100644 static/images/docs/databases/documentsdb-import-export.avif diff --git a/.optimize-cache.json b/.optimize-cache.json index babde58ee40..1d753d17ce1 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -1449,12 +1449,14 @@ "static/images/docs/databases/dark/csv-export.png": "8d2fbffbdc4b1e6e443d61a93fe4a331ecd91c66ae1457270f51f296c406aeb9", "static/images/docs/databases/dark/csv-import.png": "bdd1e700c747e703ac75b744a8e1caa7e0704ac3439e4ed1077ee0a8e76389d3", "static/images/docs/databases/dark/databases.png": "2cc14bcda3d289c3fb77f8ee4f432d93b46bd0b9755cbf26a53fcadbca3cf32b", + "static/images/docs/databases/dark/documentsdb-import-export.png": "8e87428ef2f5698bd041d398de165ac5063aa7db951901913ec4b7b1674073bc", "static/images/docs/databases/dark/manual-backup.png": "539bdf15bf654a1a696951f4447465b286566460b311ce3db82eb010502a7e03", "static/images/docs/databases/dark/pro-policy.png": "7f74b6eae525187faa9f34a12a0804b227ba9cb2bbd9300f5e8337e9512a6f14", "static/images/docs/databases/dark/restore.png": "f2605303eba4c528bf0041b0e37bb64e61c66503b2d753716b224f66b8f62ecf", "static/images/docs/databases/dark/scale-custom-policies.png": "0013e987e9b8b917cb9be4c28048f851f2d188f3bfa5ff17a11a7ac7cf9c3ade", "static/images/docs/databases/dark/scale-policies.png": "9ca9523f2e20e9aa993f0ad933cdf1dcd12adbaa35ecb2a0b8b3d2fd65877e1f", "static/images/docs/databases/databases.png": "0278a6bc5672684653f74bcf3c0d022fdd82a08d7a7fd438b28e21bd81b5e5d5", + "static/images/docs/databases/documentsdb-import-export.png": "4456efce983bfe6aebaafde0b119801cf35173a036bd0ddd06be4a74afd14778", "static/images/docs/databases/documentsdb/backup-policy.png": "913cacbe957847f8a71efa2d2e704754e52cf9c276dce8978f6c473a9ba6595b", "static/images/docs/databases/documentsdb/backups-tab.png": "231163729496618d26863c6fa7ad88069875f78a3c320033d4bddc3c2f8da376", "static/images/docs/databases/documentsdb/create-database.png": "49ba017dcec96631a87f924b13b6b93478add7b516cc62947e87230b5a0d5de1", diff --git a/src/routes/docs/products/databases/documentsdb/+layout.svelte b/src/routes/docs/products/databases/documentsdb/+layout.svelte index 3f63cab9b0a..6d43eae7041 100644 --- a/src/routes/docs/products/databases/documentsdb/+layout.svelte +++ b/src/routes/docs/products/databases/documentsdb/+layout.svelte @@ -78,6 +78,14 @@ { label: 'Timestamp overrides', href: '/docs/products/databases/documentsdb/timestamp-overrides' + }, + { + label: 'JSON imports', + href: '/docs/products/databases/documentsdb/json-imports' + }, + { + label: 'JSON exports', + href: '/docs/products/databases/documentsdb/json-exports' } ] } diff --git a/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc b/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc new file mode 100644 index 00000000000..05d23aae415 --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/json-exports/+page.markdoc @@ -0,0 +1,96 @@ +--- +layout: article +title: JSON exports +description: Export documents from a DocumentsDB collection to a JSON file. Share datasets, create custom backups, or move data to another system without writing custom scripts. +--- + +Appwrite's JSON Export feature allows you to export documents from a collection to a JSON file. This is especially useful for creating custom backups, sharing data with other teams, or moving datasets to another system. + +# Export configuration {% #export-configuration %} + +Before exporting, you can configure a few options to control the contents of the output. These settings let you export exactly the data you need. + +## Select fields {% #select-fields %} + +You can choose which fields to include in your export. By default, every field is exported, but selecting specific fields creates cleaner, more focused datasets that are easier to work with. + +{% info title="Good to know" %} +System fields like `$id`, `$permissions`, `$createdAt`, and `$updatedAt` are always included in the export. +{% /info %} + +## Filter documents {% #filter-documents %} + +You can apply queries to export only the documents you need. This is especially useful when you want to export a subset of a collection for a specific use case. + +# Export format {% #export-format %} + +Each document is exported as a JSON object that includes its system fields and data fields. The output file is an array of these objects. + +An example of exported data: + +```json +[ + { + "$id": "book-1", + "$permissions": ["read(\"any\")"], + "$createdAt": "2025-08-10T12:34:56.000Z", + "$updatedAt": "2025-08-10T12:34:56.000Z", + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling", + "year": 1997, + "available": true + } +] +``` + +# Timestamps {% #timestamps %} + +The `$createdAt` and `$updatedAt` fields are exported in ISO 8601 format, making them compatible with most tools that consume JSON. + +# Permissions {% #permissions %} + +If document level security is enabled for your collection, the `$permissions` field contains the permission strings for each document. Permission strings are formatted as an array of role definitions. + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +# Background processing {% #background-processing %} + +Large exports run as background tasks to avoid blocking your workflow. When an export completes, you'll receive an email with a short-lived download link to retrieve your JSON file. + +This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active. + +# Export documents from the Console {% #export-console %} + +{% only_dark %} +![JSON export from the collection toolbar](/images/docs/databases/dark/documentsdb-import-export.avif) +{% /only_dark %} +{% only_light %} +![JSON export from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif) +{% /only_light %} + +To export documents using the Appwrite Console: + +1. Go to your project and navigate to **Databases** +2. Select your database and open the target collection +3. Click the export (download) icon in the collection toolbar +4. Configure your export options: + - Choose which fields to include (optional) + - Apply queries to filter documents (optional) +5. Start the export + +The export begins processing in the background. You'll see a progress indicator and receive an email when the export is ready to download. + +# Use cases {% #use-cases %} + +JSON exports are useful for many common workflows: + +- **Custom backups**: Archive specific data subsets for record-keeping +- **Data sharing**: Hand off datasets to other teams +- **Migration preparation**: Extract data for migration to other systems +- **Reporting**: Feed data into tools that consume JSON + +# Additional resources {% #additional-resources %} + +- [JSON imports](/docs/products/databases/documentsdb/json-imports) +- [Documents](/docs/products/databases/documentsdb/documents) +- [Database permissions](/docs/products/databases/documentsdb/permissions) diff --git a/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc b/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc new file mode 100644 index 00000000000..5749ebf282b --- /dev/null +++ b/src/routes/docs/products/databases/documentsdb/json-imports/+page.markdoc @@ -0,0 +1,94 @@ +--- +layout: article +title: JSON imports +description: Create documents in a DocumentsDB collection by uploading a JSON file. Seed test data, restore a dataset, or migrate from another system without writing custom scripts. +--- + +Appwrite's JSON Import feature allows you to create multiple documents in a collection by uploading a single JSON file. This is especially useful for importing existing data, seeding test environments, or migrating from other systems. + +# Prepare your JSON file {% #prepare-file %} + +Your JSON file is an array of objects, where each object becomes one document in the collection. DocumentsDB is schemaless, so each object can hold its own set of fields. + +An example of a valid JSON file: + +```json +[ + { + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling", + "year": 1997, + "available": true + }, + { + "title": "The Fellowship of the Ring", + "author": "J.R.R. Tolkien", + "year": 1954, + "available": true + } +] +``` + +Each object is validated before being imported. + +# Custom document IDs {% #custom-ids %} + +You can include a `$id` field on any object to set a custom document ID. If you omit it, Appwrite generates a unique ID for the document automatically. + +```json +[ + { + "$id": "book-1", + "title": "Harry Potter and the Sorcerer's Stone", + "author": "J.K. Rowling" + } +] +``` + +# Handle duplicate IDs {% #duplicate-ids %} + +When an imported document uses a `$id` that already exists in the collection, Appwrite decides what to do based on the duplicate handling you choose: + +- **Fail** (default): the import stops at the first conflicting ID. +- **Skip**: conflicting documents are ignored, and the rest are imported. +- **Overwrite**: existing documents are replaced with the imported version. + +# Permissions {% #permissions %} + +You can set document level permissions by including a `$permissions` field with an array of permission strings. Make sure document level security is enabled for your collection. + +```json +[ + { + "$id": "book-1", + "$permissions": ["read(\"any\")", "update(\"users\")", "delete(\"user:user_id\")"], + "title": "Harry Potter and the Sorcerer's Stone" + } +] +``` + +The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). + +# Import documents from the Console {% #import-console %} + +{% only_dark %} +![JSON import from the collection toolbar](/images/docs/databases/dark/documentsdb-import-export.avif) +{% /only_dark %} +{% only_light %} +![JSON import from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif) +{% /only_light %} + +To import documents using the Appwrite Console: + +1. Go to your project and navigate to **Databases** +2. Select your database and open the target collection +3. Click the import (upload) icon in the collection toolbar +4. Upload a new JSON file or choose an existing file from your Storage bucket + +JSON imports run as background tasks. The Console displays a floating progress bar while the import is active. + +# Additional resources {% #additional-resources %} + +- [JSON exports](/docs/products/databases/documentsdb/json-exports) +- [Documents](/docs/products/databases/documentsdb/documents) +- [Database permissions](/docs/products/databases/documentsdb/permissions) diff --git a/static/images/docs/databases/dark/documentsdb-import-export.avif b/static/images/docs/databases/dark/documentsdb-import-export.avif new file mode 100644 index 0000000000000000000000000000000000000000..de937ff4c6a8ae6695545ea784c286fc39de878c GIT binary patch literal 35245 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 literal 0 HcmV?d00001 diff --git a/static/images/docs/databases/documentsdb-import-export.avif b/static/images/docs/databases/documentsdb-import-export.avif new file mode 100644 index 0000000000000000000000000000000000000000..9bdce2ba3bb7ea964fca78b1b16994b4961b561f GIT binary patch literal 33739 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 literal 0 HcmV?d00001 From faff2981ee64afa80baf482af45820de08eae9c3 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale <atharva.deosthale17@gmail.com> Date: Wed, 1 Jul 2026 19:19:53 +0530 Subject: [PATCH 5/5] Address review feedback on DocumentsDB docs --- .../products/databases/documentsdb/backups/+page.markdoc | 7 +++---- .../databases/documentsdb/bulk-operations/+page.markdoc | 2 +- .../products/databases/documentsdb/order/+page.markdoc | 4 ++-- .../databases/documentsdb/pagination/+page.markdoc | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc index a5930313c71..629bab646ce 100644 --- a/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/backups/+page.markdoc @@ -1,13 +1,12 @@ --- layout: article title: Backups -description: Learn how to back up and restore your DocumentsDB databases on Appwrite Cloud, ensuring data security and seamless recovery. +description: Learn how to back up and restore your DocumentsDB databases, ensuring data security and seamless recovery. --- -Appwrite Backups enable seamless, **encrypted** database backups on Cloud. -All backups are **hot** backups, ensuring zero downtime and fast recovery. +Backups protect your DocumentsDB data by capturing a full copy of a database that you can restore later. Every backup is **encrypted** and taken as a **hot** backup, so your database keeps serving traffic with zero downtime and recovery stays fast. -{% info title="Backups are available on Appwrite Cloud for all Pro and Enterprise customers." %} +{% info title="Backups are available for all Pro and Enterprise customers." %} {% /info %} You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. diff --git a/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc index 6522e3bde64..e83d69316b4 100644 --- a/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/bulk-operations/+page.markdoc @@ -4,7 +4,7 @@ title: Bulk operations description: Perform bulk operations on documents within your collections for efficient data handling. --- -Appwrite Databases supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. +Appwrite DocumentsDB supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. diff --git a/src/routes/docs/products/databases/documentsdb/order/+page.markdoc b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc index c8dfeba16c6..cf7bb7a1f73 100644 --- a/src/routes/docs/products/databases/documentsdb/order/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/order/+page.markdoc @@ -1,10 +1,10 @@ --- layout: article title: Order -description: Understand how to do data ordering in Appwrite Databases. Learn how to order and sort your database records for efficient data retrieval. +description: Understand how to do data ordering in Appwrite DocumentsDB. Learn how to order and sort your database records for efficient data retrieval. --- -You can order results returned by Appwrite Databases by using an order query. +You can order results returned by Appwrite DocumentsDB by using an order query. For best performance, create an [index](/docs/products/databases/documentsdb/collections#indexes) on the field you plan to order by. # Ordering one field {% #one-field %} diff --git a/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc index 1f310e8ffeb..918642e682f 100644 --- a/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc +++ b/src/routes/docs/products/databases/documentsdb/pagination/+page.markdoc @@ -1,7 +1,7 @@ --- layout: article title: Pagination -description: Implement pagination for large data sets in Appwrite Databases. Explore techniques for splitting and displaying data across multiple pages. +description: Implement pagination for large data sets in Appwrite DocumentsDB. Explore techniques for splitting and displaying data across multiple pages. --- As your collection grows in size, you'll need to paginate the documents returned.