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
Each piece of data or information in Appwrite Databases is a row. Rows have a structure defined by the parent table.
Create rows
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.
Get row
You must grant read permissions to users at the table level or row level before users can read rows. Learn more about permissions
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
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.
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
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({ 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.
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({ 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:
data class Book( val title: String, val author: String, val publishedYear: Int? = null, val genre: List<String>? = null, val isAvailable: Boolean = true)struct Book: Codable { let title: String let author: String let publishedYear: Int? let genre: [String]? let isAvailable: Bool}interface Book { title: string; author: string; publishedYear?: number; genre?: string[]; isAvailable: boolean;}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:
val book = books.rows.first()
// Convert model to Map for debugging or manual manipulationval bookMap = book.toMap()Log.d("Appwrite", "Book data: ${bookMap}")
// Create model instance from Map dataval 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.Gsonval gson = Gson()val jsonString = gson.toJson(book)val bookFromJson = gson.fromJson(jsonString, Book::class.java)let book = books.rows.first!
// Convert model to dictionary for debugginglet bookMap = book.toMap()print("Book data: \(bookMap)")
// Create model instance from dictionarylet bookData: [String: Any] = [ "title": "1984", "author": "George Orwell", "isAvailable": false]let newBook = Book.from(map: bookData)
// JSON encoding using Swift's Codablelet jsonData = try JSONEncoder().encode(book)let jsonString = String(data: jsonData, encoding: .utf8)
// JSON decodingif let jsonString = jsonString, let data = jsonString.data(using: .utf8) { let bookFromJson = try JSONDecoder().decode(Book.self, from: data)}You can automatically generate model definitions for your tables using the Appwrite CLI. Run appwrite types 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 transactionawait tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { title: 'Draft' }, transactionId: '<TRANSACTION_ID>'});
// Update row inside a transactionawait 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:
Queries
Learn how to filter, sort, and search your rows with various query operators.
Pagination
Handle large datasets by implementing pagination in your row queries.
Bulk operations
Perform create, update, and delete operations on multiple rows simultaneously.
Timestamp overrides
Set custom creation and update timestamps when migrating data or backdating records.
Was this page helpful?
Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.