Skip to content

Databases

Rows_

Master row management with Appwrite Databases. Learn how to create, update, upsert, and query rows within your tables for dynamic data storage.

4 min read

Raw

Each piece of data or information in Appwrite Databases is a row. Rows have a structure defined by the parent table.

Create rows

In most use cases, you will create rows programmatically.

import { Client, ID, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const tablesDB = new TablesDB(client);
const promise = tablesDB.createRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: ID.unique(),
data: {}
});
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});

During testing, you might prefer to create rows in the Appwrite Console. To do so, navigate to the Rows tab of your table and click the Add row button.

Get row

A single row can be retrieved by its ID using the Get row endpoint.

Pass queries to shape the response, for example Query.select() to return only the columns you need or to load relationship data.

import { Client, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const tablesDB = new TablesDB(client);
const promise = tablesDB.getRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>'
});
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});

List rows

Rows can be retrieved using the List rows endpoint.

Results can be filtered, sorted, and paginated using Appwrite's shared set of query methods. You can find a full guide on querying in the Queries Guide.

By default, results are limited to the first 25 items. You can change this through pagination.

import { Client, Query, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
const tablesDB = new TablesDB(client);
let promise = tablesDB.listRows({
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
queries: [
Query.equal('title', 'Avatar')
]
});
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});

Cache list responses

You can cache list responses by passing a ttl (time-to-live) value in seconds to listRows. 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.

import { Client, Query, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>");
const tablesDB = new TablesDB(client);
const rows = await tablesDB.listRows({
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
queries: [
Query.equal('title', 'Avatar')
],
ttl: 60 // Cache for 60 seconds
});

Purge cache

Row writes do not invalidate the cache, so cached responses may contain stale data until the TTL expires. Schema changes (adding or removing columns and indexes) invalidate cached entries automatically.

To force an immediate cache purge, call updateTable with purge set to true using a Server SDK.

const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<YOUR_API_KEY>');
const tablesDB = new sdk.TablesDB(client);
await tablesDB.updateTable({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
purge: true
});

Update row

In most use cases, you will update rows programmatically.

import { Client, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const tablesDB = new TablesDB(client);
const promise = tablesDB.updateRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: { title: 'Updated Title' }
});
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});

Upsert rows

Upsert is a combination of "update" and "insert" operations. It creates a new row if one doesn't exist with the given ID, or updates an existing row if it does exist.

In most use cases, you will upsert rows programmatically.

import { Client, ID, TablesDB } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const tablesDB = new TablesDB(client);
const promise = tablesDB.upsertRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: ID.unique(),
data: {}
});
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});

Type safety with models

Mobile and native SDKs provide type safety when working with rows through the nestedType parameter. This allows you to specify custom model types for complete auto-completion and type safety.

Define your model

Create a data class or struct that matches your table structure:

Kotlin
data class Book(
val title: String,
val author: String,
val publishedYear: Int? = null,
val genre: List<String>? = null,
val isAvailable: Boolean = true
)

Using type-safe operations

Use the nestedType parameter for full type safety in native SDKs, or generics in web SDKs:

val tablesDB = TablesDB(client)
try {
// Create with type safety
val newBook = tablesDB.createRow(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
rowId = ID.unique(),
data = mapOf(
"title" to "The Great Gatsby",
"author" to "F. Scott Fitzgerald",
"isAvailable" to true
),
nestedType = Book::class.java
)
// List with type safety
val books = tablesDB.listRows(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
nestedType = Book::class.java
)
// Now you have full type safety
for (book in books.rows) {
Log.d("Appwrite", "Book: ${book.title} by ${book.author}")
if (book.isAvailable) {
Log.d("Appwrite", "Available for checkout")
}
}
} catch (e: AppwriteException) {
Log.e("Appwrite", "Error: ${e.message}")
}

Model methods

Models returned by native SDKs include helpful utility methods:

Kotlin
val book = books.rows.first()
// Convert model to Map for debugging or manual manipulation
val bookMap = book.toMap()
Log.d("Appwrite", "Book data: ${bookMap}")
// Create model instance from Map data
val bookData = mapOf(
"title" to "1984",
"author" to "George Orwell",
"isAvailable" to false
)
val newBook = Book.from(bookData, Book::class.java)
// JSON serialization using Gson (used internally by SDK)
import com.google.gson.Gson
val gson = Gson()
val jsonString = gson.toJson(book)
val bookFromJson = gson.fromJson(jsonString, Book::class.java)

Permissions

In Appwrite, permissions can be granted at the table level and the row level. Before a user can create a row, you need to grant create permissions to the user.

Read, update, and delete permissions can be granted at both the table and row level. Users only need to be granted access at either the table or row level to access rows.

Learn about configuring permissions.

Use transactions

All row operations support transactionId. When provided, operations are staged to an internal log and not applied until the transaction is committed. Learn more in the Transactions guide.

// Create row inside a transaction
await tablesDB.createRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: { title: 'Draft' },
transactionId: '<TRANSACTION_ID>'
});
// Update row inside a transaction
await tablesDB.updateRow({
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: { title: 'Published' },
transactionId: '<TRANSACTION_ID>'
});

Next steps

Continue learning with these related guides:

Was this page helpful?

Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.