| name | appwrite-python |
| description | Appwrite Python SDK skill. Use when building server-side Python applications with Appwrite, including Django, Flask, and FastAPI integrations. Covers user management, database/table CRUD, file storage, and functions via API keys. |
Appwrite Python SDK
Installation
pip install appwrite
Setting Up the Client
from appwrite.client import Client
from appwrite.id import ID
from appwrite.query import Query
from appwrite.services.users import Users
from appwrite.services.tablesdb import TablesDB
from appwrite.services.storage import Storage
from appwrite.services.functions import Functions
from appwrite.enums.o_auth_provider import OAuthProvider
import os
client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project(os.environ['APPWRITE_PROJECT_ID'])
.set_key(os.environ['APPWRITE_API_KEY']))
Code Examples
User Management
users = Users(client)
user = users.create(ID.unique(), 'user@example.com', None, 'password123', 'User Name')
result = users.list([Query.limit(25)])
fetched = users.get('[USER_ID]')
users.delete('[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 keyword arguments (e.g., database_id='...') over positional arguments for all SDK method calls. Only use positional style if the existing codebase already uses it or the user explicitly requests it.
tables_db = TablesDB(client)
db = tables_db.create(ID.unique(), 'My Database')
doc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {
'title': 'Hello World'
})
results = tables_db.list_rows('[DATABASE_ID]', '[TABLE_ID]', [
Query.equal('title', 'Hello World'),
Query.limit(10)
])
row = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')
tables_db.update_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', {
'title': 'Updated'
})
tables_db.delete_row('[DATABASE_ID]', '[TABLE_ID]', '[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.
tables_db.create_table(
database_id='[DATABASE_ID]',
table_id=ID.unique(),
name='articles',
columns=[
{'key': 'title', 'type': 'varchar', 'size': 255, 'required': True},
{'key': 'summary', 'type': 'text', 'required': False},
{'key': 'body', 'type': 'mediumtext', 'required': False},
{'key': 'raw_data', 'type': 'longtext', 'required': False},
]
)
Query Methods
Query.equal('field', 'value')
Query.not_equal('field', 'value')
Query.less_than('field', 100)
Query.less_than_equal('field', 100)
Query.greater_than('field', 100)
Query.greater_than_equal('field', 100)
Query.between('field', 1, 100)
Query.is_null('field')
Query.is_not_null('field')
Query.starts_with('field', 'prefix')
Query.ends_with('field', 'suffix')
Query.contains('field', 'sub')
Query.search('field', 'keywords')
Query.order_asc('field')
Query.order_desc('field')
Query.limit(25)
Query.offset(0)
Query.cursor_after('[ROW_ID]')
Query.cursor_before('[ROW_ID]')
Query.select(['field1', 'field2'])
Query.or_queries([Query.equal('a', 1), Query.equal('b', 2)])
Query.and_queries([Query.greater_than('age', 18), Query.less_than('age', 65)])
File Storage
from appwrite.input_file import InputFile
storage = Storage(client)
file = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'))
files = storage.list_files('[BUCKET_ID]')
storage.delete_file('[BUCKET_ID]', '[FILE_ID]')
InputFile Factory Methods
from appwrite.input_file import InputFile
InputFile.from_path('/path/to/file.png')
InputFile.from_bytes(byte_data, 'file.png')
InputFile.from_string('Hello world', 'hello.txt')
Teams
from appwrite.services.teams import Teams
teams = Teams(client)
team = teams.create(ID.unique(), 'Engineering')
team_list = teams.list()
membership = teams.create_membership('[TEAM_ID]', roles=['editor'], email='user@example.com')
members = teams.list_memberships('[TEAM_ID]')
teams.update_membership('[TEAM_ID]', '[MEMBERSHIP_ID]', roles=['admin'])
teams.delete('[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.
Serverless Functions
functions = Functions(client)
execution = functions.create_execution('[FUNCTION_ID]', body='{"key": "value"}')
executions = functions.list_executions('[FUNCTION_ID]')
Writing a Function Handler (Python runtime)
def main(context):
context.log('Processing: ' + context.req.method + ' ' + context.req.path)
if context.req.method == 'GET':
return context.res.json({'message': 'Hello from Appwrite Function!'})
data = context.req.body_json or {}
if 'name' not in data:
context.error('Missing name field')
return context.res.json({'error': 'Name is required'}, 400)
return context.res.json({'success': True})
Server-Side Rendering (SSR) Authentication
SSR apps (Flask, Django, FastAPI, etc.) use the server SDK 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)
from appwrite.client import Client
from appwrite.services.account import Account
from flask import request, jsonify, make_response, redirect
admin_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_key(os.environ['APPWRITE_API_KEY']))
session_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]'))
session = request.cookies.get('a_session_[PROJECT_ID]')
if session:
session_client.set_session(session)
Email/Password Login
@app.post('/login')
def login():
account = Account(admin_client)
session = account.create_email_password_session(
request.json['email'], request.json['password']
)
resp = make_response(jsonify({'success': True}))
resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],
httponly=True, secure=True, samesite='Strict',
expires=session['expire'], path='/')
return resp
Authenticated Requests
@app.get('/user')
def get_user():
session = request.cookies.get('a_session_[PROJECT_ID]')
if not session:
return jsonify({'error': 'Unauthorized'}), 401
session_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_session(session))
account = Account(session_client)
return jsonify(account.get())
OAuth2 SSR Flow
@app.get('/oauth')
def oauth():
account = Account(admin_client)
redirect_url = account.create_o_auth2_token(
OAuthProvider.Github,
'https://example.com/oauth/success',
'https://example.com/oauth/failure',
)
return redirect(redirect_url)
@app.get('/oauth/success')
def oauth_success():
account = Account(admin_client)
session = account.create_session(request.args['userId'], request.args['secret'])
resp = make_response(jsonify({'success': True}))
resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],
httponly=True, secure=True, samesite='Strict',
expires=session['expire'], path='/')
return resp
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 session_client.set_forwarded_user_agent(request.headers.get('user-agent')) to record the end-user's browser info for debugging and security.
Error Handling
from appwrite.exception import AppwriteException
try:
row = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')
except AppwriteException as e:
print(e.message)
print(e.code)
print(e.type)
print(e.response)
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.
from appwrite.permission import Permission
from appwrite.role import Role
Database Row with Permissions
doc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {
'title': 'Hello World'
}, [
Permission.read(Role.user('[USER_ID]')),
Permission.update(Role.user('[USER_ID]')),
Permission.read(Role.team('[TEAM_ID]')),
Permission.read(Role.any()),
])
File Upload with Permissions
file = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'), [
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