appwrite-typescript

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Appwrite TypeScript SDK

Appwrite TypeScript SDK

Installation

安装

bash
undefined
bash
undefined

Web

Web

npm install appwrite
npm install appwrite

React Native

React Native

npm install react-native-appwrite
npm install react-native-appwrite

Node.js / Deno

Node.js / Deno

npm install node-appwrite
undefined
npm install node-appwrite
undefined

Setting Up the Client

客户端设置

Client-side (Web / React Native)

客户端(Web / React Native)

typescript
// Web
import { Client, Account, TablesDB, Storage, ID, Query } from 'appwrite';

// React Native
import { Client, Account, TablesDB, Storage, ID, Query } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');
typescript
// Web
import { Client, Account, TablesDB, Storage, ID, Query } from 'appwrite';

// React Native
import { Client, Account, TablesDB, Storage, ID, Query } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

Server-side (Node.js / Deno)

服务器端(Node.js / Deno)

typescript
import { Client, Users, TablesDB, Storage, Functions, ID, Query } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject(process.env.APPWRITE_PROJECT_ID)
    .setKey(process.env.APPWRITE_API_KEY);
typescript
import { Client, Users, TablesDB, Storage, Functions, ID, Query } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject(process.env.APPWRITE_PROJECT_ID)
    .setKey(process.env.APPWRITE_API_KEY);

Code Examples

代码示例

Authentication (client-side)

客户端认证

typescript
const account = new Account(client);

// Email signup
await account.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// Email login
const session = await account.createEmailPasswordSession({
    email: 'user@example.com',
    password: 'password123'
});

// OAuth login (Web)
account.createOAuth2Session({
    provider: OAuthProvider.Github,
    success: 'https://example.com/success',
    failure: 'https://example.com/fail',
    scopes: ['repo', 'user'] // optional — provider-specific scopes
});

// Get current user
const user = await account.get();

// Logout
await account.deleteSession({ sessionId: 'current' });
typescript
const account = new Account(client);

// Email signup
await account.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// Email login
const session = await account.createEmailPasswordSession({
    email: 'user@example.com',
    password: 'password123'
});

// OAuth login (Web)
account.createOAuth2Session({
    provider: OAuthProvider.Github,
    success: 'https://example.com/success',
    failure: 'https://example.com/fail',
    scopes: ['repo', 'user'] // optional — provider-specific scopes
});

// Get current user
const user = await account.get();

// Logout
await account.deleteSession({ sessionId: 'current' });

OAuth 2 Login (React Native)

React Native 中的 OAuth 2 登录

Important:
createOAuth2Session()
does not work on React Native. You must use
createOAuth2Token()
with deep linking instead.
重要提示:
createOAuth2Session()
在 React Native 中无法使用。你必须结合深度链接使用
createOAuth2Token()

Setup

配置步骤

Install the required dependencies:
bash
npx expo install react-native-appwrite react-native-url-polyfill
npm install expo-auth-session expo-web-browser expo-linking
Set the URL scheme in your
app.json
:
json
{
  "expo": {
    "scheme": "appwrite-callback-[PROJECT_ID]"
  }
}
安装所需依赖:
bash
npx expo install react-native-appwrite react-native-url-polyfill
npm install expo-auth-session expo-web-browser expo-linking
app.json
中设置 URL 协议:
json
{
  "expo": {
    "scheme": "appwrite-callback-[PROJECT_ID]"
  }
}

OAuth Flow

OAuth 登录流程

typescript
import { Client, Account, OAuthProvider } from 'react-native-appwrite';
import { makeRedirectUri } from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const account = new Account(client);

async function oauthLogin(provider: OAuthProvider) {
    // Create deep link that works across Expo environments
    const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));
    const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-[PROJECT_ID]://'

    // Get the OAuth login URL
    const loginUrl = await account.createOAuth2Token({
        provider,
        success: `${deepLink}`,
        failure: `${deepLink}`,
    });

    // Open browser and listen for the scheme redirect
    const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme);

    if (result.type !== 'success') return;

    // Extract credentials from the redirect URL
    const url = new URL(result.url);
    const secret = url.searchParams.get('secret');
    const userId = url.searchParams.get('userId');

    // Create session with the OAuth credentials
    await account.createSession({ userId, secret });
}

// Usage
await oauthLogin(OAuthProvider.Github);
await oauthLogin(OAuthProvider.Google);
typescript
import { Client, Account, OAuthProvider } from 'react-native-appwrite';
import { makeRedirectUri } from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const account = new Account(client);

async function oauthLogin(provider: OAuthProvider) {
    // Create deep link that works across Expo environments
    const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));
    const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-[PROJECT_ID]://'

    // Get the OAuth login URL
    const loginUrl = await account.createOAuth2Token({
        provider,
        success: `${deepLink}`,
        failure: `${deepLink}`,
    });

    // Open browser and listen for the scheme redirect
    const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme);

    if (result.type !== 'success') return;

    // Extract credentials from the redirect URL
    const url = new URL(result.url);
    const secret = url.searchParams.get('secret');
    const userId = url.searchParams.get('userId');

    // Create session with the OAuth credentials
    await account.createSession({ userId, secret });
}

// Usage
await oauthLogin(OAuthProvider.Github);
await oauthLogin(OAuthProvider.Google);

User Management (server-side)

服务器端用户管理

typescript
const users = new Users(client);

// Create user
const user = await users.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// List users
const list = await users.list({ queries: [Query.limit(25)] });

// Get user
const fetched = await users.get({ userId: '[USER_ID]' });

// Delete user
await users.delete({ userId: '[USER_ID]' });
typescript
const users = new Users(client);

// Create user
const user = await users.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// List users
const list = await users.list({ queries: [Query.limit(25)] });

// Get user
const fetched = await users.get({ userId: '[USER_ID]' });

// Delete user
await users.delete({ userId: '[USER_ID]' });

Database Operations

数据库操作

Note: Use
TablesDB
(not the deprecated
Databases
class) for all new code. Only use
Databases
if the existing codebase already relies on it or the user explicitly requests it.
Tip: Prefer the object-params calling style (e.g.,
{ databaseId: '...' }
) for all SDK method calls. Only use positional arguments if the existing codebase already uses them or the user explicitly requests it.
typescript
const tablesDB = new TablesDB(client);

// Create database (server-side only)
const db = await tablesDB.create({ databaseId: ID.unique(), name: 'My Database' });

// Create table (server-side only)
const col = await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'My Table'
});

// Create row
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World', content: 'Example content' }
});

// List rows with query
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('status', 'active'), Query.limit(10)]
});

// Get row
const row = await tablesDB.getRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

// Update row
await tablesDB.updateRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]',
    data: { title: 'Updated Title' }
});

// Delete row
await tablesDB.deleteRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});
注意: 所有新代码请使用
TablesDB
(而非已废弃的
Databases
类)。仅当现有代码库已依赖
Databases
或用户明确要求时,才使用
Databases
提示: 所有 SDK 方法调用优先使用对象参数风格(例如
{ databaseId: '...' }
)。仅当现有代码库已使用位置参数或用户明确要求时,才使用位置参数。
typescript
const tablesDB = new TablesDB(client);

// Create database (server-side only)
const db = await tablesDB.create({ databaseId: ID.unique(), name: 'My Database' });

// Create table (server-side only)
const col = await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'My Table'
});

// Create row
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World', content: 'Example content' }
});

// List rows with query
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('status', 'active'), Query.limit(10)]
});

// Get row
const row = await tablesDB.getRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

// Update row
await tablesDB.updateRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]',
    data: { title: 'Updated Title' }
});

// Delete row
await tablesDB.deleteRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

String Column Types

字符串列类型

Note: The legacy
string
type is deprecated. Use explicit column types for all new columns.
TypeMax charactersIndexingStorage
varchar
16,383Full index (if size ≤ 768)Inline in row
text
16,383Prefix onlyOff-page
mediumtext
4,194,303Prefix onlyOff-page
longtext
1,073,741,823Prefix onlyOff-page
  • varchar
    is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.
  • text
    ,
    mediumtext
    , and
    longtext
    are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget.
    size
    is not required for these types.
typescript
// Create table with explicit string column types
await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'articles',
    columns: [
        { key: 'title',    type: 'varchar',    size: 255, required: true  },  // inline, fully indexable
        { key: 'summary',  type: 'text',                  required: false },  // off-page, prefix index only
        { key: 'body',     type: 'mediumtext',            required: false },  // up to ~4 M chars
        { key: 'raw_data', type: 'longtext',              required: false },  // up to ~1 B chars
    ]
});
注意: 旧版
string
类型已废弃。所有新列请使用明确的列类型。
类型最大字符数索引方式存储位置
varchar
16,383全索引(若长度 ≤ 768)行内存储
text
16,383仅前缀索引页外存储
mediumtext
4,194,303仅前缀索引页外存储
longtext
1,073,741,823仅前缀索引页外存储
  • varchar
    存储在行内,占用64KB行大小限制。适合短文本、可索引字段,如名称、别名或标识符。
  • text
    mediumtext
    longtext
    存储在页外(行内仅保留20字节指针),因此不占用行大小配额。这些类型无需指定
    size
typescript
// Create table with explicit string column types
await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'articles',
    columns: [
        { key: 'title',    type: 'varchar',    size: 255, required: true  },  // inline, fully indexable
        { key: 'summary',  type: 'text',                  required: false },  // off-page, prefix index only
        { key: 'body',     type: 'mediumtext',            required: false },  // up to ~4 M chars
        { key: 'raw_data', type: 'longtext',              required: false },  // up to ~1 B chars
    ]
});

TypeScript Generics

TypeScript 泛型

typescript
import { Models } from 'appwrite';
// Server-side: import from 'node-appwrite'

// Define a typed interface for your row data
interface Todo {
    title: string;
    done: boolean;
    priority: number;
}

// listRows returns Models.DocumentList<Models.Document> by default
// Cast or use generics for typed results
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('done', false)]
});

// Each document includes built-in fields alongside your data
const doc = results.documents[0];
doc.$id;            // string — unique row ID
doc.$createdAt;     // string — ISO 8601 creation timestamp
doc.$updatedAt;     // string — ISO 8601 update timestamp
doc.$permissions;   // string[] — permission strings
doc.$databaseId;    // string
doc.$collectionId;  // string

// Common model types
// Models.User<Preferences>  — user account
// Models.Session             — auth session
// Models.File                — storage file metadata
// Models.Team                — team object
// Models.Execution           — function execution result
// Models.DocumentList<T>     — paginated list with total count
typescript
import { Models } from 'appwrite';
// Server-side: import from 'node-appwrite'

// Define a typed interface for your row data
interface Todo {
    title: string;
    done: boolean;
    priority: number;
}

// listRows returns Models.DocumentList<Models.Document> by default
// Cast or use generics for typed results
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('done', false)]
});

// Each document includes built-in fields alongside your data
const doc = results.documents[0];
doc.$id;            // string — unique row ID
doc.$createdAt;     // string — ISO 8601 creation timestamp
doc.$updatedAt;     // string — ISO 8601 update timestamp
doc.$permissions;   // string[] — permission strings
doc.$databaseId;    // string
doc.$collectionId;  // string

// Common model types
// Models.User<Preferences>  — user account
// Models.Session             — auth session
// Models.File                — storage file metadata
// Models.Team                — team object
// Models.Execution           — function execution result
// Models.DocumentList<T>     — paginated list with total count

Query Methods

查询方法

typescript
// Filtering
Query.equal('field', 'value')           // field == value (or pass array for IN)
Query.notEqual('field', 'value')        // field != value
Query.lessThan('field', 100)            // field < value
Query.lessThanEqual('field', 100)       // field <= value
Query.greaterThan('field', 100)         // field > value
Query.greaterThanEqual('field', 100)    // field >= value
Query.between('field', 1, 100)          // 1 <= field <= 100
Query.isNull('field')                   // field is null
Query.isNotNull('field')                // field is not null
Query.startsWith('field', 'prefix')     // string starts with prefix
Query.endsWith('field', 'suffix')       // string ends with suffix
Query.contains('field', 'substring')    // string/array contains value
Query.search('field', 'keywords')       // full-text search (requires full-text index)

// Sorting
Query.orderAsc('field')                 // sort ascending
Query.orderDesc('field')                // sort descending

// Pagination
Query.limit(25)                         // max rows returned (default 25, max 100)
Query.offset(0)                         // skip N rows
Query.cursorAfter('[ROW_ID]')           // paginate after this row ID (preferred for large datasets)
Query.cursorBefore('[ROW_ID]')          // paginate before this row ID

// Selection
Query.select(['field1', 'field2'])      // return only specified fields

// Logical
Query.or([Query.equal('a', 1), Query.equal('b', 2)])   // OR condition
Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // explicit AND (queries are AND by default)
typescript
// Filtering
Query.equal('field', 'value')           // field == value (or pass array for IN)
Query.notEqual('field', 'value')        // field != value
Query.lessThan('field', 100)            // field < value
Query.lessThanEqual('field', 100)       // field <= value
Query.greaterThan('field', 100)         // field > value
Query.greaterThanEqual('field', 100)    // field >= value
Query.between('field', 1, 100)          // 1 <= field <= 100
Query.isNull('field')                   // field is null
Query.isNotNull('field')                // field is not null
Query.startsWith('field', 'prefix')     // string starts with prefix
Query.endsWith('field', 'suffix')       // string ends with suffix
Query.contains('field', 'substring')    // string/array contains value
Query.search('field', 'keywords')       // full-text search (requires full-text index)

// Sorting
Query.orderAsc('field')                 // sort ascending
Query.orderDesc('field')                // sort descending

// Pagination
Query.limit(25)                         // max rows returned (default 25, max 100)
Query.offset(0)                         // skip N rows
Query.cursorAfter('[ROW_ID]')           // paginate after this row ID (preferred for large datasets)
Query.cursorBefore('[ROW_ID]')          // paginate before this row ID

// Selection
Query.select(['field1', 'field2'])      // return only specified fields

// Logical
Query.or([Query.equal('a', 1), Query.equal('b', 2)])   // OR condition
Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // explicit AND (queries are AND by default)

File Storage

文件存储

typescript
const storage = new Storage(client);

// Upload file (client-side — from file input)
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0]
});

// Upload file (server-side — from path)
import { InputFile } from 'node-appwrite/file';

const file2 = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: InputFile.fromPath('/path/to/file.png', 'file.png')
});

// List files
const files = await storage.listFiles({ bucketId: '[BUCKET_ID]' });

// Get file preview (image)
const preview = storage.getFilePreview({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]',
    width: 300,
    height: 300
});

// Download file
const download = await storage.getFileDownload({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]'
});

// Delete file
await storage.deleteFile({ bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]' });
typescript
const storage = new Storage(client);

// Upload file (client-side — from file input)
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0]
});

// Upload file (server-side — from path)
import { InputFile } from 'node-appwrite/file';

const file2 = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: InputFile.fromPath('/path/to/file.png', 'file.png')
});

// List files
const files = await storage.listFiles({ bucketId: '[BUCKET_ID]' });

// Get file preview (image)
const preview = storage.getFilePreview({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]',
    width: 300,
    height: 300
});

// Download file
const download = await storage.getFileDownload({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]'
});

// Delete file
await storage.deleteFile({ bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]' });

InputFile Factory Methods (server-side)

服务器端 InputFile 工厂方法

typescript
import { InputFile } from 'node-appwrite/file';

InputFile.fromPath('/path/to/file.png', 'file.png')          // from filesystem path
InputFile.fromBuffer(buffer, 'file.png')                       // from Buffer
InputFile.fromStream(readableStream, 'file.png', size)         // from ReadableStream (size in bytes required)
InputFile.fromPlainText('Hello world', 'hello.txt')            // from string content
typescript
import { InputFile } from 'node-appwrite/file';

InputFile.fromPath('/path/to/file.png', 'file.png')          // from filesystem path
InputFile.fromBuffer(buffer, 'file.png')                       // from Buffer
InputFile.fromStream(readableStream, 'file.png', size)         // from ReadableStream (size in bytes required)
InputFile.fromPlainText('Hello world', 'hello.txt')            // from string content

Teams

团队管理

typescript
const teams = new Teams(client);

// Create team
const team = await teams.create({ teamId: ID.unique(), name: 'Engineering' });

// List teams
const list = await teams.list();

// Create membership (invite a user by email)
const membership = await teams.createMembership({
    teamId: '[TEAM_ID]',
    roles: ['editor'],
    email: 'user@example.com',
});

// List memberships
const members = await teams.listMemberships({ teamId: '[TEAM_ID]' });

// Update membership roles
await teams.updateMembership({
    teamId: '[TEAM_ID]',
    membershipId: '[MEMBERSHIP_ID]',
    roles: ['admin'],
});

// Delete team
await teams.delete({ teamId: '[TEAM_ID]' });
Role-based access: Use
Role.team('[TEAM_ID]')
for all team members or
Role.team('[TEAM_ID]', 'editor')
for a specific team role when setting permissions.
typescript
const teams = new Teams(client);

// Create team
const team = await teams.create({ teamId: ID.unique(), name: 'Engineering' });

// List teams
const list = await teams.list();

// Create membership (invite a user by email)
const membership = await teams.createMembership({
    teamId: '[TEAM_ID]',
    roles: ['editor'],
    email: 'user@example.com',
});

// List memberships
const members = await teams.listMemberships({ teamId: '[TEAM_ID]' });

// Update membership roles
await teams.updateMembership({
    teamId: '[TEAM_ID]',
    membershipId: '[MEMBERSHIP_ID]',
    roles: ['admin'],
});

// Delete team
await teams.delete({ teamId: '[TEAM_ID]' });
基于角色的访问控制: 设置权限时,使用
Role.team('[TEAM_ID]')
表示所有团队成员,或使用
Role.team('[TEAM_ID]', 'editor')
表示特定团队角色。

Real-time Subscriptions (client-side)

客户端实时订阅

typescript
import { Realtime, Channel } from 'appwrite';

const realtime = new Realtime(client);

// Subscribe to row changes
const subscription = await realtime.subscribe(
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
    (response) => {
        console.log(response.events);   // e.g. ['tablesdb.*.tables.*.rows.*.create']
        console.log(response.payload);  // the affected resource
    }
);

// Subscribe to a specific row
await realtime.subscribe(
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row('[ROW_ID]'),
    (response) => { /* ... */ }
);

// Subscribe to multiple channels
await realtime.subscribe([
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
    Channel.bucket('[BUCKET_ID]').file(),
], (response) => { /* ... */ });

// Unsubscribe
await subscription.close();
Available channels:
ChannelDescription
account
Changes to the authenticated user's account
tablesdb.[DB_ID].tables.[TABLE_ID].rows
All rows in a table
tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
A specific row
buckets.[BUCKET_ID].files
All files in a bucket
buckets.[BUCKET_ID].files.[FILE_ID]
A specific file
teams
Changes to teams the user belongs to
teams.[TEAM_ID]
Changes to a specific team
memberships
Changes to the user's team memberships
memberships.[MEMBERSHIP_ID]
A specific membership
functions.[FUNCTION_ID].executions
Execution updates for a function
The
response
object includes:
events
(array of event strings),
payload
(the affected resource),
channels
(channels matched), and
timestamp
(ISO 8601).
typescript
import { Realtime, Channel } from 'appwrite';

const realtime = new Realtime(client);

// Subscribe to row changes
const subscription = await realtime.subscribe(
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
    (response) => {
        console.log(response.events);   // e.g. ['tablesdb.*.tables.*.rows.*.create']
        console.log(response.payload);  // the affected resource
    }
);

// Subscribe to a specific row
await realtime.subscribe(
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row('[ROW_ID]'),
    (response) => { /* ... */ }
);

// Subscribe to multiple channels
await realtime.subscribe([
    Channel.tablesdb('[DATABASE_ID]').table('[TABLE_ID]').row(),
    Channel.bucket('[BUCKET_ID]').file(),
], (response) => { /* ... */ });

// Unsubscribe
await subscription.close();
可用频道:
频道描述
account
已认证用户账户的变更
tablesdb.[DB_ID].tables.[TABLE_ID].rows
表中的所有行
tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
特定行
buckets.[BUCKET_ID].files
存储桶中的所有文件
buckets.[BUCKET_ID].files.[FILE_ID]
特定文件
teams
用户所属团队的变更
teams.[TEAM_ID]
特定团队的变更
memberships
用户团队成员身份的变更
memberships.[MEMBERSHIP_ID]
特定成员身份
functions.[FUNCTION_ID].executions
函数的执行更新
response
对象包含:
events
(事件字符串数组)、
payload
(受影响的资源)、
channels
(匹配的频道)和
timestamp
(ISO 8601格式时间戳)。

Serverless Functions (server-side)

服务器端无服务器函数

typescript
const functions = new Functions(client);

// Execute function
const execution = await functions.createExecution({
    functionId: '[FUNCTION_ID]',
    body: JSON.stringify({ key: 'value' })
});

// List executions
const executions = await functions.listExecutions({ functionId: '[FUNCTION_ID]' });
typescript
const functions = new Functions(client);

// Execute function
const execution = await functions.createExecution({
    functionId: '[FUNCTION_ID]',
    body: JSON.stringify({ key: 'value' })
});

// List executions
const executions = await functions.listExecutions({ functionId: '[FUNCTION_ID]' });

Writing a Function Handler (Node.js runtime)

编写函数处理器(Node.js 运行时)

When deploying your own Appwrite Function, the entry point file must export a default async function:
typescript
// src/main.js (or src/main.ts)
export default async ({ req, res, log, error }) => {
    // Request properties
    // req.body        — raw request body (string)
    // req.bodyJson    — parsed JSON body (object, or undefined if not JSON)
    // req.headers     — request headers (object)
    // req.method      — HTTP method (GET, POST, PUT, DELETE, PATCH)
    // req.path        — URL path (e.g. '/hello')
    // req.query       — parsed query parameters (object)
    // req.queryString — raw query string

    log('Processing request: ' + req.method + ' ' + req.path);

    if (req.method === 'GET') {
        return res.json({ message: 'Hello from Appwrite Function!' });
    }

    const data = req.bodyJson;
    if (!data?.name) {
        error('Missing name field');
        return res.json({ error: 'Name is required' }, 400);
    }

    // Response methods
    return res.json({ success: true });                    // JSON (sets Content-Type automatically)
    // return res.text('Hello');                           // plain text
    // return res.empty();                                 // 204 No Content
    // return res.redirect('https://example.com');         // 302 Redirect
    // return res.send('data', 200, { 'X-Custom': '1' }); // custom body, status, headers
};
部署自定义 Appwrite 函数时,入口文件必须导出一个默认异步函数:
typescript
// src/main.js (or src/main.ts)
export default async ({ req, res, log, error }) => {
    // Request properties
    // req.body        — raw request body (string)
    // req.bodyJson    — parsed JSON body (object, or undefined if not JSON)
    // req.headers     — request headers (object)
    // req.method      — HTTP method (GET, POST, PUT, DELETE, PATCH)
    // req.path        — URL path (e.g. '/hello')
    // req.query       — parsed query parameters (object)
    // req.queryString — raw query string

    log('Processing request: ' + req.method + ' ' + req.path);

    if (req.method === 'GET') {
        return res.json({ message: 'Hello from Appwrite Function!' });
    }

    const data = req.bodyJson;
    if (!data?.name) {
        error('Missing name field');
        return res.json({ error: 'Name is required' }, 400);
    }

    // Response methods
    return res.json({ success: true });                    // JSON (sets Content-Type automatically)
    // return res.text('Hello');                           // plain text
    // return res.empty();                                 // 204 No Content
    // return res.redirect('https://example.com');         // 302 Redirect
    // return res.send('data', 200, { 'X-Custom': '1' }); // custom body, status, headers
};

Server-Side Rendering (SSR) Authentication

服务器端渲染(SSR)认证

SSR apps (Next.js, SvelteKit, Nuxt, Remix, Astro) use the server SDK (
node-appwrite
) to handle auth. You need two clients:
  • Admin client — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
  • Session client — uses a session cookie, acts on behalf of a user (create per-request, never share)
typescript
import { Client, Account, OAuthProvider } from 'node-appwrite';

// Admin client (reusable)
const adminClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]')
    .setKey(process.env.APPWRITE_API_KEY);

// Session client (create per-request)
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const session = req.cookies['a_session_[PROJECT_ID]'];
if (session) {
    sessionClient.setSession(session);
}
SSR 应用(Next.js、SvelteKit、Nuxt、Remix、Astro)使用服务器 SDK
node-appwrite
)处理认证。你需要两个客户端:
  • 管理员客户端 — 使用 API 密钥,创建会话,绕过速率限制(可复用单例)
  • 会话客户端 — 使用会话 Cookie,代表用户操作(每个请求创建一次,切勿共享)
typescript
import { Client, Account, OAuthProvider } from 'node-appwrite';

// Admin client (reusable)
const adminClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]')
    .setKey(process.env.APPWRITE_API_KEY);

// Session client (create per-request)
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const session = req.cookies['a_session_[PROJECT_ID]'];
if (session) {
    sessionClient.setSession(session);
}

Email/Password Login

邮箱/密码登录

typescript
app.post('/login', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createEmailPasswordSession({
        email: req.body.email,
        password: req.body.password,
    });

    // Cookie name must be a_session_<PROJECT_ID>
    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        expires: new Date(session.expire),
        path: '/',
    });

    res.json({ success: true });
});
typescript
app.post('/login', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createEmailPasswordSession({
        email: req.body.email,
        password: req.body.password,
    });

    // Cookie name must be a_session_<PROJECT_ID>
    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        expires: new Date(session.expire),
        path: '/',
    });

    res.json({ success: true });
});

Authenticated Requests

已认证请求

typescript
app.get('/user', async (req, res) => {
    const session = req.cookies['a_session_[PROJECT_ID]'];
    if (!session) return res.status(401).json({ error: 'Unauthorized' });

    // Create a fresh session client per request
    const sessionClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]')
        .setSession(session);

    const account = new Account(sessionClient);
    const user = await account.get();
    res.json(user);
});
typescript
app.get('/user', async (req, res) => {
    const session = req.cookies['a_session_[PROJECT_ID]'];
    if (!session) return res.status(401).json({ error: 'Unauthorized' });

    // Create a fresh session client per request
    const sessionClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]')
        .setSession(session);

    const account = new Account(sessionClient);
    const user = await account.get();
    res.json(user);
});

OAuth2 SSR Flow

OAuth2 SSR 流程

typescript
// Step 1: Redirect to OAuth provider
app.get('/oauth', async (req, res) => {
    const account = new Account(adminClient);
    const redirectUrl = await account.createOAuth2Token({
        provider: OAuthProvider.Github,
        success: 'https://example.com/oauth/success',
        failure: 'https://example.com/oauth/failure',
    });
    res.redirect(redirectUrl);
});

// Step 2: Handle callback — exchange token for session
app.get('/oauth/success', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createSession({
        userId: req.query.userId,
        secret: req.query.secret,
    });

    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true, secure: true, sameSite: 'strict',
        expires: new Date(session.expire), path: '/',
    });
    res.json({ success: true });
});
Cookie security: Always use
httpOnly
,
secure
, and
sameSite: 'strict'
to prevent XSS. The cookie name must be
a_session_<PROJECT_ID>
.
Forwarding user agent: Call
sessionClient.setForwardedUserAgent(req.headers['user-agent'])
to record the end-user's browser info for debugging and security.
typescript
// Step 1: Redirect to OAuth provider
app.get('/oauth', async (req, res) => {
    const account = new Account(adminClient);
    const redirectUrl = await account.createOAuth2Token({
        provider: OAuthProvider.Github,
        success: 'https://example.com/oauth/success',
        failure: 'https://example.com/oauth/failure',
    });
    res.redirect(redirectUrl);
});

// Step 2: Handle callback — exchange token for session
app.get('/oauth/success', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createSession({
        userId: req.query.userId,
        secret: req.query.secret,
    });

    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true, secure: true, sameSite: 'strict',
        expires: new Date(session.expire), path: '/',
    });
    res.json({ success: true });
});
Cookie 安全: 始终使用
httpOnly
secure
sameSite: 'strict'
防止 XSS 攻击。Cookie 名称必须为
a_session_<PROJECT_ID>
转发用户代理: 调用
sessionClient.setForwardedUserAgent(req.headers['user-agent'])
记录终端用户的浏览器信息,用于调试和安全排查。

Error Handling

错误处理

typescript
import { AppwriteException } from 'appwrite';
// Server-side: import from 'node-appwrite'

try {
    const doc = await tablesDB.getRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]',
    });
} catch (err) {
    if (err instanceof AppwriteException) {
        console.log(err.message);   // human-readable error message
        console.log(err.code);      // HTTP status code (number)
        console.log(err.type);      // Appwrite error type string (e.g. 'document_not_found')
        console.log(err.response);  // full response body (object)
    }
}
Common error codes:
CodeMeaning
401
Unauthorized — missing or invalid session/API key
403
Forbidden — insufficient permissions for this action
404
Not found — resource does not exist
409
Conflict — duplicate ID or unique constraint violation
429
Rate limited — too many requests, retry after backoff
typescript
import { AppwriteException } from 'appwrite';
// Server-side: import from 'node-appwrite'

try {
    const doc = await tablesDB.getRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]',
    });
} catch (err) {
    if (err instanceof AppwriteException) {
        console.log(err.message);   // human-readable error message
        console.log(err.code);      // HTTP status code (number)
        console.log(err.type);      // Appwrite error type string (e.g. 'document_not_found')
        console.log(err.response);  // full response body (object)
    }
}
常见错误码:
错误码含义
401
未授权 — 缺少或无效的会话/API密钥
403
禁止访问 — 操作权限不足
404
未找到 — 资源不存在
409
冲突 — 重复ID或唯一约束违反
429
请求受限 — 请求过于频繁,请稍后重试

Permissions & Roles (Critical)

权限与角色(关键)

Appwrite uses permission strings to control access to resources. Each permission pairs an action (
read
,
update
,
delete
,
create
, or
write
which grants create + update + delete) with a role target. By default, no user has access unless permissions are explicitly set at the row/file level or inherited from the table/bucket settings. Permissions are arrays of strings built with the
Permission
and
Role
helpers.
typescript
import { Permission, Role } from 'appwrite';
// Server-side: import from 'node-appwrite'
Appwrite 使用权限字符串控制资源访问。每个权限将操作(
read
update
delete
create
,或包含创建+更新+删除的
write
)与角色目标配对。默认情况下,所有用户都无访问权限,除非在行/文件级别明确设置权限,或从表/存储桶设置继承权限。权限是使用
Permission
Role
助手构建的字符串数组。
typescript
import { Permission, Role } from 'appwrite';
// Server-side: import from 'node-appwrite'

Database Row with Permissions

带权限的数据库行

typescript
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World' },
    permissions: [
        Permission.read(Role.user('[USER_ID]')),     // specific user can read
        Permission.update(Role.user('[USER_ID]')),   // specific user can update
        Permission.read(Role.team('[TEAM_ID]')),     // all team members can read
        Permission.read(Role.any()),                 // anyone (including guests) can read
    ]
});
typescript
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World' },
    permissions: [
        Permission.read(Role.user('[USER_ID]')),     // specific user can read
        Permission.update(Role.user('[USER_ID]')),   // specific user can update
        Permission.read(Role.team('[TEAM_ID]')),     // all team members can read
        Permission.read(Role.any()),                 // anyone (including guests) can read
    ]
});

File Upload with Permissions

带权限的文件上传

typescript
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0],
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('[USER_ID]')),
        Permission.delete(Role.user('[USER_ID]')),
    ]
});
When to set permissions: Set row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row permissions empty.
Common mistakes:
  • Forgetting permissions — the resource becomes inaccessible to all users (including the creator)
  • Role.any()
    with
    write
    /
    update
    /
    delete
    — allows any user, including unauthenticated guests, to modify or remove the resource
  • Permission.read(Role.any())
    on sensitive data
    — makes the resource publicly readable
typescript
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0],
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('[USER_ID]')),
        Permission.delete(Role.user('[USER_ID]')),
    ]
});
权限设置时机: 当需要按资源控制访问权限时,设置行/文件级权限。如果表中所有行共享相同规则,请在表/存储桶级别配置权限,留空行权限即可。
常见错误:
  • 忘记设置权限 — 资源对所有用户(包括创建者)都不可访问
  • write
    /
    update
    /
    delete
    使用
    Role.any()
    — 允许任何用户(包括未认证访客)修改或删除资源
  • 对敏感数据使用
    Permission.read(Role.any())
    — 使资源可公开读取