Appwrite Dart SDK
Installation
Flutter (client-side)
flutter pub
add
appwrite
Dart (server-side)
dart pub
add
dart_appwrite
Setting Up the Client
Client-side (Flutter)
import
'package:appwrite/appwrite.dart'
;
final
client
=
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
;
Server-side (Dart)
import
'package:dart_appwrite/dart_appwrite.dart'
;
final
client
=
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
Platform
.
environment
[
'APPWRITE_PROJECT_ID'
]
!
)
.
setKey
(
Platform
.
environment
[
'APPWRITE_API_KEY'
]
!
)
;
Code Examples
Authentication (client-side)
final
account
=
Account
(
client
)
;
// Signup
await
account
.
create
(
userId
:
ID
.
unique
(
)
,
email
:
'user@example.com'
,
password
:
'password123'
,
name
:
'User Name'
)
;
// Login
final
session
=
await
account
.
createEmailPasswordSession
(
email
:
'user@example.com'
,
password
:
'password123'
)
;
// OAuth login
await
account
.
createOAuth2Session
(
provider
:
OAuthProvider
.
google
)
;
// Get current user
final
user
=
await
account
.
get
(
)
;
// Logout
await
account
.
deleteSession
(
sessionId
:
'current'
)
;
User Management (server-side)
final
users
=
Users
(
client
)
;
// Create user
final
user
=
await
users
.
create
(
userId
:
ID
.
unique
(
)
,
email
:
'user@example.com'
,
password
:
'password123'
,
name
:
'User Name'
)
;
// List users
final
list
=
await
users
.
list
(
queries
:
[
Query
.
limit
(
25
)
]
)
;
// Get user
final
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 named parameters (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.
final
tablesDB
=
TablesDB
(
client
)
;
// Create database (server-side only)
final
db
=
await
tablesDB
.
create
(
databaseId
:
ID
.
unique
(
)
,
name
:
'My Database'
)
;
// Create table (server-side only)
final
col
=
await
tablesDB
.
createTable
(
databaseId
:
'[DATABASE_ID]'
,
tableId
:
ID
.
unique
(
)
,
name
:
'My Table'
)
;
// Create row
final
doc
=
await
tablesDB
.
createRow
(
databaseId
:
'[DATABASE_ID]'
,
tableId
:
'[TABLE_ID]'
,
rowId
:
ID
.
unique
(
)
,
data
:
{
'title'
:
'Hello'
,
'done'
:
false
}
,
)
;
// Query rows
final
results
=
await
tablesDB
.
listRows
(
databaseId
:
'[DATABASE_ID]'
,
tableId
:
'[TABLE_ID]'
,
queries
:
[
Query
.
equal
(
'done'
,
false
)
,
Query
.
limit
(
10
)
]
,
)
;
// Get row
final
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
:
{
'done'
:
true
}
,
)
;
// 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.
Type
Max characters
Indexing
Storage
varchar
16,383
Full index (if size ≤ 768)
Inline in row
text
16,383
Prefix only
Off-page
mediumtext
4,194,303
Prefix only
Off-page
longtext
1,073,741,823
Prefix only
Off-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.
// 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
]
,
)
;
Query Methods
// Filtering
Query
.
equal
(
'field'
,
'value'
)
// == (or pass list for IN)
Query
.
notEqual
(
'field'
,
'value'
)
// !=
Query
.
lessThan
(
'field'
,
100
)
// <
Query
.
lessThanEqual
(
'field'
,
100
)
// <=
Query
.
greaterThan
(
'field'
,
100
)
// >
Query
.
greaterThanEqual
(
'field'
,
100
)
// >=
Query
.
between
(
'field'
,
1
,
100
)
// 1 <= field <= 100
Query
.
isNull
(
'field'
)
// is null
Query
.
isNotNull
(
'field'
)
// is not null
Query
.
startsWith
(
'field'
,
'prefix'
)
// starts with
Query
.
endsWith
(
'field'
,
'suffix'
)
// ends with
Query
.
contains
(
'field'
,
'sub'
)
// contains
Query
.
search
(
'field'
,
'keywords'
)
// full-text search (requires index)
// Sorting
Query
.
orderAsc
(
'field'
)
Query
.
orderDesc
(
'field'
)
// Pagination
Query
.
limit
(
25
)
// max rows (default 25, max 100)
Query
.
offset
(
0
)
// skip N rows
Query
.
cursorAfter
(
'[ROW_ID]'
)
// cursor pagination (preferred)
Query
.
cursorBefore
(
'[ROW_ID]'
)
// Selection & Logic
Query
.
select
(
[
'field1'
,
'field2'
]
)
// return only specified fields
Query
.
or
(
[
Query
.
equal
(
'a'
,
1
)
,
Query
.
equal
(
'b'
,
2
)
]
)
// OR
Query
.
and
(
[
Query
.
greaterThan
(
'age'
,
18
)
,
Query
.
lessThan
(
'age'
,
65
)
]
)
// AND (default)
File Storage
final
storage
=
Storage
(
client
)
;
// Upload file
final
file
=
await
storage
.
createFile
(
bucketId
:
'[BUCKET_ID]'
,
fileId
:
ID
.
unique
(
)
,
file
:
InputFile
.
fromPath
(
path
:
'/path/to/file.png'
,
filename
:
'file.png'
)
,
)
;
// Get file preview
final
preview
=
storage
.
getFilePreview
(
bucketId
:
'[BUCKET_ID]'
,
fileId
:
'[FILE_ID]'
,
width
:
300
,
height
:
300
)
;
// List files
final
files
=
await
storage
.
listFiles
(
bucketId
:
'[BUCKET_ID]'
)
;
// Delete file
await
storage
.
deleteFile
(
bucketId
:
'[BUCKET_ID]'
,
fileId
:
'[FILE_ID]'
)
;
InputFile Factory Methods
// Client-side (Flutter)
InputFile
.
fromPath
(
path
:
'/path/to/file.png'
,
filename
:
'file.png'
)
// from path
InputFile
.
fromBytes
(
bytes
:
uint8List
,
filename
:
'file.png'
)
// from Uint8List
// Server-side (Dart)
InputFile
.
fromPath
(
path
:
'/path/to/file.png'
,
filename
:
'file.png'
)
InputFile
.
fromBytes
(
bytes
:
uint8List
,
filename
:
'file.png'
)
Teams
final
teams
=
Teams
(
client
)
;
// Create team
final
team
=
await
teams
.
create
(
teamId
:
ID
.
unique
(
)
,
name
:
'Engineering'
)
;
// List teams
final
list
=
await
teams
.
list
(
)
;
// Create membership (invite user by email)
final
membership
=
await
teams
.
createMembership
(
teamId
:
'[TEAM_ID]'
,
roles
:
[
'editor'
]
,
email
:
'user@example.com'
,
)
;
// List memberships
final
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.
Real-time Subscriptions (client-side)
final
realtime
=
Realtime
(
client
)
;
// Subscribe to row changes
final
subscription
=
realtime
.
subscribe
(
[
Channel
.
tablesdb
(
'[DATABASE_ID]'
)
.
table
(
'[TABLE_ID]'
)
.
row
(
)
,
]
)
;
subscription
.
stream
.
listen
(
(
response
)
{
print
(
response
.
events
)
;
// e.g. ['tablesdb..tables..rows.*.create']
print
(
response
.
payload
)
;
// the affected resource
}
)
;
// Subscribe to multiple channels
final
multi
=
realtime
.
subscribe
(
[
Channel
.
tablesdb
(
'[DATABASE_ID]'
)
.
table
(
'[TABLE_ID]'
)
.
row
(
)
,
Channel
.
bucket
(
'[BUCKET_ID]'
)
.
file
(
)
,
]
)
;
// Cleanup
subscription
.
close
(
)
;
Available channels:
Channel
Description
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]
A specific team
memberships
The user's team memberships
memberships.[MEMBERSHIP_ID]
A specific membership
functions.[FUNCTION_ID].executions
Function execution updates
Response fields:
events
(array),
payload
(resource),
channels
(matched),
timestamp
(ISO 8601).
Serverless Functions (server-side)
final
functions
=
Functions
(
client
)
;
// Execute function
final
execution
=
await
functions
.
createExecution
(
functionId
:
'[FUNCTION_ID]'
,
body
:
'{"key": "value"}'
)
;
// List executions
final
executions
=
await
functions
.
listExecutions
(
functionId
:
'[FUNCTION_ID]'
)
;
Writing a Function Handler (Dart runtime)
// lib/main.dart — Appwrite Function entry point
Future
<
dynamic
main
(
final
context
)
async
{
// context.req.body — raw body (String)
// context.req.bodyJson — parsed JSON (Map or null)
// context.req.headers — headers (Map)
// context.req.method — HTTP method
// context.req.path — URL path
// context.req.query — query params (Map)
context
.
log
(
'Processing:
${
context
.
req
.
method
}
${
context
.
req
.
path
}
'
)
;
if
(
context
.
req
.
method
==
'GET'
)
{
return
context
.
res
.
json
(
{
'message'
:
'Hello from Appwrite Function!'
}
)
;
}
return
context
.
res
.
json
(
{
'success'
:
true
}
)
;
// JSON
// return context.res.text('Hello'); // plain text
// return context.res.empty(); // 204
// return context.res.redirect('https://...'); // 302
}
Server-Side Rendering (SSR) Authentication
SSR apps using server-side Dart (Dart Frog, Shelf, etc.) use the
server SDK
(
dart_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)
import
'package:dart_appwrite/dart_appwrite.dart'
;
// Admin client (reusable)
final
adminClient
=
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
.
setKey
(
Platform
.
environment
[
'APPWRITE_API_KEY'
]
!
)
;
// Session client (create per-request)
final
sessionClient
=
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
;
final
session
=
request
.
cookies
[
'a_session_[PROJECT_ID]'
]
;
if
(
session
!=
null
)
{
sessionClient
.
setSession
(
session
)
;
}
Email/Password Login
final
account
=
Account
(
adminClient
)
;
final
session
=
await
account
.
createEmailPasswordSession
(
email
:
body
[
'email'
]
,
password
:
body
[
'password'
]
,
)
;
// Cookie name must be a_session_
response
.
headers
.
add
(
'Set-Cookie'
,
'a_session_[PROJECT_ID]=
${
session
.
secret
}
; '
'HttpOnly; Secure; SameSite=Strict; '
'Expires=
${
HttpDate
.
format
(
DateTime
.
parse
(
session
.
expire
)
)
}
; Path=/'
)
;
Authenticated Requests
final
session
=
request
.
cookies
[
'a_session_[PROJECT_ID]'
]
;
if
(
session
==
null
)
{
return
Response
(
statusCode
:
401
,
body
:
'Unauthorized'
)
;
}
final
sessionClient
=
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
.
setSession
(
session
)
;
final
account
=
Account
(
sessionClient
)
;
final
user
=
await
account
.
get
(
)
;
OAuth2 SSR Flow
// Step 1: Redirect to OAuth provider
final
account
=
Account
(
adminClient
)
;
final
redirectUrl
=
await
account
.
createOAuth2Token
(
provider
:
OAuthProvider
.
github
,
success
:
'https://example.com/oauth/success'
,
failure
:
'https://example.com/oauth/failure'
,
)
;
return
Response
(
statusCode
:
302
,
headers
:
{
'Location'
:
redirectUrl
}
)
;
// Step 2: Handle callback — exchange token for session
final
account
=
Account
(
adminClient
)
;
final
session
=
await
account
.
createSession
(
userId
:
request
.
uri
.
queryParameters
[
'userId'
]
!
,
secret
:
request
.
uri
.
queryParameters
[
'secret'
]
!
,
)
;
// Set session cookie as above
Cookie security:
Always use
HttpOnly
,
Secure
, and
SameSite=Strict
to prevent XSS. The cookie name must be
a_session_
.
Forwarding user agent:
Call
sessionClient.setForwardedUserAgent(request.headers['user-agent'])
to record the end-user's browser info for debugging and security.
Error Handling
import
'package:appwrite/appwrite.dart'
;
// AppwriteException is included in the main import
try
{
final
row
=
await
tablesDB
.
getRow
(
databaseId
:
'[DATABASE_ID]'
,
tableId
:
'[TABLE_ID]'
,
rowId
:
'[ROW_ID]'
)
;
}
on
AppwriteException
catch
(
e
)
{
print
(
e
.
message
)
;
// human-readable message
print
(
e
.
code
)
;
// HTTP status code (int)
print
(
e
.
type
)
;
// error type (e.g. 'document_not_found')
print
(
e
.
response
)
;
// full response body (Map)
}
Common error codes:
Code
Meaning
401
Unauthorized — missing or invalid session/API key
403
Forbidden — insufficient permissions
404
Not found — resource does not exist
409
Conflict — duplicate ID or unique constraint
429
Rate limited — too many requests
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 document/file level or inherited from the collection/bucket settings. Permissions are arrays of strings built with the
Permission
and
Role
helpers.
import
'package:appwrite/appwrite.dart'
;
// Permission and Role are included in the main package import
Database Row with Permissions
final
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
final
file
=
await
storage
.
createFile
(
bucketId
:
'[BUCKET_ID]'
,
fileId
:
ID
.
unique
(
)
,
file
:
InputFile
.
fromPath
(
path
:
'/path/to/file.png'
,
filename
:
'file.png'
)
,
permissions
:
[
Permission
.
read
(
Role
.
any
(
)
)
,
Permission
.
update
(
Role
.
user
(
'[USER_ID]'
)
)
,
Permission
.
delete
(
Role
.
user
(
'[USER_ID]'
)
)
,
]
,
)
;
When to set permissions:
Set document/file-level permissions when you need per-resource access control. If all documents in a collection share the same rules, configure permissions at the collection/bucket level and leave document 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