Appwrite TypeScript SDK
Installation
Web
npm
install
appwrite
React Native
npm
install
react-native-appwrite
Node.js / Deno
npm
install
node-appwrite
Setting Up the Client
Client-side (Web / React Native)
// 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://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
;
Server-side (Node.js / Deno)
import
{
Client
,
Users
,
TablesDB
,
Storage
,
Functions
,
ID
,
Query
}
from
'node-appwrite'
;
const
client
=
new
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
process
.
env
.
APPWRITE_PROJECT_ID
)
.
setKey
(
process
.
env
.
APPWRITE_API_KEY
)
;
Code Examples
Authentication (client-side)
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)
Important:
createOAuth2Session()
does
not
work on React Native. You must use
createOAuth2Token()
with deep linking instead.
Setup
Install the required dependencies:
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
:
{
"expo"
:
{
"scheme"
:
"appwrite-callback-[PROJECT_ID]"
}
}
OAuth Flow
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://.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)
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.
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.
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
]
}
)
;
TypeScript Generics
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 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 — user account
// Models.Session — auth session
// Models.File — storage file metadata
// Models.Team — team object
// Models.Execution — function execution result
// Models.DocumentList — paginated list with total count
Query Methods
// 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
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)
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
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.
Real-time Subscriptions (client-side)
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:
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]
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).
Serverless Functions (server-side)
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)
When deploying your own Appwrite Function, the entry point file must export a default async function:
// 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 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)
import
{
Client
,
Account
,
OAuthProvider
}
from
'node-appwrite'
;
// Admin client (reusable)
const
adminClient
=
new
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
.
setKey
(
process
.
env
.
APPWRITE_API_KEY
)
;
// Session client (create per-request)
const
sessionClient
=
new
Client
(
)
.
setEndpoint
(
'https://.cloud.appwrite.io/v1'
)
.
setProject
(
'[PROJECT_ID]'
)
;
const
session
=
req
.
cookies
[
'a_session_[PROJECT_ID]'
]
;
if
(
session
)
{
sessionClient
.
setSession
(
session
)
;
}
Email/Password Login
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_
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
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://.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
// 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_
.
Forwarding user agent:
Call
sessionClient.setForwardedUserAgent(req.headers['user-agent'])
to record the end-user's browser info for debugging and security.
Error Handling
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:
Code
Meaning
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
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
{
Permission
,
Role
}
from
'appwrite'
;
// Server-side: import from 'node-appwrite'
Database Row with Permissions
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
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 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