Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
appwrite avatar

Appwrite Rust

  • 109 installs
  • 25 repo stars
  • Updated May 6, 2026
  • appwrite/agent-skills

appwrite-rust is a Claude skill for building server-side Rust applications with the Appwrite backend using the async crates.io appwrite SDK and Tokio.

About

appwrite-rust is a skill for building server-side Rust applications against Appwrite. It covers async client setup with API keys on Tokio, user management, TablesDB database/table/row operations, file storage, function executions, permissions, queries and error handling. A developer uses it when writing a Rust backend that talks to Appwrite Cloud or a self-hosted instance. It uses the crates.io appwrite package and the modern TablesDB API rather than the deprecated Databases class.

  • Async Appwrite Rust SDK setup with API keys on a Tokio runtime
  • Covers TablesDB database/table/row CRUD, queries, permissions and file storage
  • Uses the crates.io appwrite package (appwrite 0.3.0) with serde_json

Appwrite Rust by the numbers

  • 109 all-time installs (skills.sh)
  • Ranked #2,939 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Jul 31, 2026 (Skillselion catalog sync)
At a glance

appwrite-rust capabilities & compatibility

requires an Appwrite project ID and API key; Appwrite has a free cloud tier

Capabilities
appwrite cli · api development · database
Works with
supabase
Use cases
api development · database
Pricing
Bring your own API key
From the docs

What appwrite-rust says it does

Appwrite Rust SDK skill. Use when building server-side Rust applications with Appwrite.
SKILL.md
The Rust SDK is async. Use it from a Tokio runtime and authenticate server-side with an API key.
SKILL.md
npx skills add https://github.com/appwrite/agent-skills --skill appwrite-rust

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs109
repo stars25
Last updatedMay 6, 2026
Repositoryappwrite/agent-skills

What it does

Build a server-side Rust backend against Appwrite for users, TablesDB CRUD, storage and functions using API keys.

Who is it for?

writing a server-side Rust backend that uses Appwrite for auth, database, storage and functions

Skip if: client-side or non-Rust Appwrite work

When should I use this skill?

building a Rust server that calls Appwrite for users, TablesDB, storage or function execution

What you get

Correct async Appwrite Rust client, CRUD, storage and function calls with proper permissions and queries.

  • async Appwrite Rust client
  • TablesDB CRUD code
  • storage and function calls

By the numbers

  • appwrite crate version 0.3.0
  • four documented string column types

Files

SKILL.mdMarkdownGitHub ↗

Appwrite Rust SDK

Installation

cargo add appwrite
cargo add tokio --features full
cargo add serde_json

Or add dependencies manually:

[dependencies]
appwrite = "0.3.0"
tokio = { version = "1", features = ["full"] }
serde_json = "1"

Setting Up the Client

The Rust SDK is async. Use it from a Tokio runtime and authenticate server-side with an API key.

use appwrite::Client;
use std::env;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project(env::var("APPWRITE_PROJECT_ID")?)
    .set_key(env::var("APPWRITE_API_KEY")?);

Complete server skeleton

use appwrite::query::Query;
use appwrite::services::Users;
use appwrite::Client;
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
        .set_project(env::var("APPWRITE_PROJECT_ID")?)
        .set_key(env::var("APPWRITE_API_KEY")?);

    let users = Users::new(&client);
    let _list = users
        .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))
        .await?;

    Ok(())
}

Code Examples

User Management

use appwrite::id::ID;
use appwrite::query::Query;
use appwrite::services::Users;

let users = Users::new(&client);

// Create user
let _user = users
    .create(
        ID::unique(),
        Some("user@example.com"),
        None,
        Some("password123"),
        Some("User Name"),
    )
    .await?;

// List users
let _list = users
    .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))
    .await?;

// Get user
let _fetched = users.get("[USER_ID]").await?;

// Delete user
users.delete("[USER_ID]").await?;

Database Operations

Note: Use TablesDB for new code. Only use Databases if the existing project explicitly depends on the legacy Databases API.

>

Rust SDK calling convention: Methods are async, use positional parameters, and represent optional API parameters as Option<T>. Pass None for optional values you are not setting.
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::query::Query;
use appwrite::role::Role;
use appwrite::services::TablesDB;
use serde_json::json;

let tables_db = TablesDB::new(&client);

// Create database
let _database = tables_db
    .create(ID::unique(), "My Database", Some(true))
    .await?;

// Create table
let _table = tables_db
    .create_table(
        "[DATABASE_ID]",
        ID::unique(),
        "articles",
        Some(vec![
            Permission::read(Role::any()).to_string(),
            Permission::create(Role::users(None)).to_string(),
        ]),
        Some(true),
        Some(true),
        None,
        None,
    )
    .await?;

// Create row
let _row = tables_db
    .create_row(
        "[DATABASE_ID]",
        "[TABLE_ID]",
        ID::unique(),
        json!({
            "title": "Hello World",
            "done": false
        }),
        None,
        None,
    )
    .await?;

// Query rows
let _rows = tables_db
    .list_rows(
        "[DATABASE_ID]",
        "[TABLE_ID]",
        Some(vec![
            Query::equal("done", false).to_string(),
            Query::limit(10).to_string(),
        ]),
        None,
        Some(true),
        None,
    )
    .await?;

// Get row
let _row = tables_db
    .get_row("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]", None, None)
    .await?;

// Update row
let _updated = tables_db
    .update_row(
        "[DATABASE_ID]",
        "[TABLE_ID]",
        "[ROW_ID]",
        Some(json!({ "done": true })),
        None,
        None,
    )
    .await?;

// Delete row
tables_db
    .delete_row("[DATABASE_ID]", "[TABLE_ID]", "[ROW_ID]", None)
    .await?;
String Column Types
Note: The legacy string column type is deprecated. Use explicit string column types for new tables.
TypeMax charactersIndexingStorage
varchar16,383Full index (if size <= 768)Inline in row
text16,383Prefix onlyOff-page
mediumtext4,194,303Prefix onlyOff-page
longtext1,073,741,823Prefix onlyOff-page
  • varchar is stored inline and counts toward the 64 KB row size limit. Prefer it for short, indexed fields like names, slugs, and identifiers.
  • text, mediumtext, and longtext are stored off-page, so they do not consume the row size budget. size is not required for these types.
use appwrite::enums::{OrderBy, TablesDBIndexType};

// Short, indexed string
let _title = tables_db
    .create_varchar_column(
        "[DATABASE_ID]",
        "[TABLE_ID]",
        "title",
        255,
        true,
        None,
        None,
        None,
    )
    .await?;

// Off-page longer text
let _summary = tables_db
    .create_text_column("[DATABASE_ID]", "[TABLE_ID]", "summary", false, None, None, None)
    .await?;

let _body = tables_db
    .create_mediumtext_column("[DATABASE_ID]", "[TABLE_ID]", "body", false, None, None, None)
    .await?;

let _raw_data = tables_db
    .create_longtext_column("[DATABASE_ID]", "[TABLE_ID]", "raw_data", false, None, None, None)
    .await?;

// Index only the varchar column fully.
let _index = tables_db
    .create_index(
        "[DATABASE_ID]",
        "[TABLE_ID]",
        "title_idx",
        TablesDBIndexType::Key,
        vec!["title"],
        Some(vec![OrderBy::Asc]),
        Some(vec![255]),
    )
    .await?;

Query Methods

TablesDB methods accept Option<Vec<String>> for queries. Convert each Query to a string.

use appwrite::query::Query;
use serde_json::Value;

let queries = vec![
    Query::equal("status", "published").to_string(),
    Query::not_equal("archived", true).to_string(),
    Query::greater_than("views", 100).to_string(),
    Query::less_than_equal("priority", 5).to_string(),
    Query::between("score", 1, 100).to_string(),
    Query::is_not_null("publishedAt").to_string(),
    Query::search("title", "rust appwrite").to_string(),
    Query::contains("tags", "rust").to_string(),
    Query::order_desc("$createdAt").to_string(),
    Query::limit(25).to_string(),
    Query::offset(50).to_string(),
];

let multi_value = Query::equal(
    "status",
    Value::Array(vec![
        Value::String("draft".to_string()),
        Value::String("published".to_string()),
    ]),
)
.to_string();

File Storage

use appwrite::id::ID;
use appwrite::input_file::InputFile;
use appwrite::permission::Permission;
use appwrite::query::Query;
use appwrite::role::Role;
use appwrite::services::Storage;

let storage = Storage::new(&client);

// Create bucket
let _bucket = storage
    .create_bucket(
        ID::unique(),
        "Uploads",
        Some(vec![
            Permission::read(Role::any()).to_string(),
            Permission::create(Role::users(None)).to_string(),
        ]),
        Some(true),
        Some(true),
        Some(30_000_000),
        Some(vec!["jpg".to_string(), "png".to_string(), "pdf".to_string()]),
        None,
        Some(true),
        Some(true),
        Some(true),
    )
    .await?;

// Upload from disk
let input = InputFile::from_path("avatar.png", Some("image/png")).await?;
let _file = storage
    .create_file(
        "[BUCKET_ID]",
        ID::unique(),
        input,
        Some(vec![Permission::read(Role::any()).to_string()]),
    )
    .await?;

// Upload from bytes
let input = InputFile::from_bytes(b"hello".to_vec(), "hello.txt", Some("text/plain"));
let _file = storage.create_file("[BUCKET_ID]", ID::unique(), input, None).await?;

// List files
let _files = storage
    .list_files("[BUCKET_ID]", Some(vec![Query::limit(10).to_string()]), None, Some(true))
    .await?;

// Download file bytes
let _content = storage
    .get_file_download("[BUCKET_ID]", "[FILE_ID]", None)
    .await?;

// Delete file
storage.delete_file("[BUCKET_ID]", "[FILE_ID]").await?;

Functions

use appwrite::enums::ExecutionMethod;
use appwrite::query::Query;
use appwrite::services::Functions;
use serde_json::json;

let functions = Functions::new(&client);

// List functions
let _functions = functions
    .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))
    .await?;

// Execute function
let _execution = functions
    .create_execution(
        "[FUNCTION_ID]",
        Some(r#"{"hello":"world"}"#),
        Some(false),
        Some("/jobs/sync"),
        Some(ExecutionMethod::POST),
        Some(json!({ "content-type": "application/json" })),
        None,
    )
    .await?;

// Get execution
let _execution = functions
    .get_execution("[FUNCTION_ID]", "[EXECUTION_ID]")
    .await?;

Permissions

use appwrite::permission::Permission;
use appwrite::role::Role;

let permissions = vec![
    Permission::read(Role::any()).to_string(),
    Permission::create(Role::users(None)).to_string(),
    Permission::update(Role::user("[USER_ID]", None)).to_string(),
    Permission::delete(Role::team("[TEAM_ID]", Some("owner"))).to_string(),
];

Error Handling

match users.get("[USER_ID]").await {
    Ok(user) => {
        let _user = user;
    }
    Err(error) if error.status_code() == 404 => {
        eprintln!("User not found: {}", error.get_message());
    }
    Err(error) => {
        eprintln!("Appwrite error {}: {}", error.status_code(), error.get_message());
        return Err(Box::new(error) as Box<dyn std::error::Error>);
    }
}

Common Pitfalls

  • The Rust SDK is currently a server-side SDK. Prefer TypeScript/Web, Flutter, Apple, Android, or React Native SDKs for browser/mobile client auth flows.
  • Always await service calls.
  • Pass None for optional parameters you are not using.
  • Use TablesDB, not legacy Databases, for new database code.
  • Convert queries with .to_string() before passing them to APIs that expect Option<Vec<String>>.
  • Use serde_json::json!({...}) for row data and JSON bodies.
  • Use InputFile::from_path(...).await? or InputFile::from_bytes(...) for uploads.

Related skills

FAQ

Which crate does it use?

The crates.io appwrite package (appwrite 0.3.0) with tokio full features and serde_json.

Databases or TablesDB?

Use TablesDB for new code; only use the legacy Databases API if the existing project explicitly depends on it.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.