Skip to content

Rows

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

Create rows

Permissions required

You must grant create permissions to users at the table level before users can create rows. Learn more about permissions

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.

List rows

Permissions required

You must grant read permissions to users at the table level before users can read rows. Learn more about permissions

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.

Speed up lists by skipping totals

If your UI doesn't need an exact total, set the total flag to false on list calls. The response keeps the same shape and sets total to 0. This reduces latency for large tables and filtered queries. Learn more in Pagination: Skip totals.

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

Permissions required

You must grant update permissions to users at the table level or row level before users can update rows. Learn more about permissions

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(
    '<DATABASE_ID>',
    '<TABLE_ID>',
    '<ROW_ID>',
    { 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.

Permissions required

You must grant create permissions to users at the table level, and update permissions to users at the table or row level before users can upsert rows. Learn more about permissions

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(
    '<DATABASE_ID>',
    '<TABLE_ID>',
    ID.unique(),
    {}
);

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:

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}")
}
const tablesDB = new TablesDB(client);

try {
    // Create with generics
    const newBook = await tablesDB.createRow<Book>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        rowId: ID.unique(),
        data: {
            title: "The Great Gatsby",
            author: "F. Scott Fitzgerald",
            isAvailable: true
        }
    });

    // List with generics
    const books = await tablesDB.listRows<Book>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>'
    });

    // TypeScript provides full type safety
    books.rows.forEach(book => {
        console.log(`Book: ${book.title} by ${book.author}`);
        if (book.isAvailable) {
            console.log("Available for checkout");
        }
    });
} catch (error) {
    console.log(error);
}

Model methods

Models returned by native SDKs include helpful utility methods:

Generate types automatically

You can automatically generate model definitions for your tables using the Appwrite CLI. Run appwrite types collection to generate types based on your database schema.

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: