{"id":13625,"plugin_id":"plugin_asdk_app_6aa2c33323108191b17b9ccf4233b3ce","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:07:50.366Z","digest":"f4211f9941622ce8dd6ba41c2aca2eb7b5b6f4f08903fd101a2ede13bc7a261f","against":null,"payload":{"name":"appwrite-rust","description":"Appwrite Rust SDK skill. Use when building server-side Rust applications with Appwrite. Covers async client setup with API keys, user management, TablesDB database/table/row operations, file storage, function executions, permissions, queries, and error handling. Uses the crates.io `appwrite` package and Tokio.","included_files":[],"skill_md_contents":"---\r\nname: appwrite-rust\r\ndescription: Appwrite Rust SDK skill. Use when building server-side Rust applications with Appwrite. Covers async client setup with API keys, user management, TablesDB database/table/row operations, file storage, function executions, permissions, queries, and error handling. Uses the crates.io `appwrite` package and Tokio.\r\n---\r\n\r\n\r\n# Appwrite Rust SDK\r\n\r\n## Installation\r\n\r\n```bash\r\ncargo add appwrite\r\ncargo add tokio --features full\r\ncargo add serde_json\r\n```\r\n\r\nOr add dependencies manually:\r\n\r\n```toml\r\n[dependencies]\r\nappwrite = \"0.3.0\"\r\ntokio = { version = \"1\", features = [\"full\"] }\r\nserde_json = \"1\"\r\n```\r\n\r\n## Setting Up the Client\r\n\r\nThe Rust SDK is async. Use it from a Tokio runtime and authenticate server-side with an API key.\r\n\r\n```rust\r\nuse appwrite::Client;\r\nuse std::env;\r\n\r\nlet client = Client::new()\r\n    .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n    .set_project(env::var(\"APPWRITE_PROJECT_ID\")?)\r\n    .set_key(env::var(\"APPWRITE_API_KEY\")?);\r\n```\r\n\r\n### Complete server skeleton\r\n\r\n```rust\r\nuse appwrite::query::Query;\r\nuse appwrite::services::Users;\r\nuse appwrite::Client;\r\nuse std::env;\r\n\r\n#[tokio::main]\r\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\r\n    let client = Client::new()\r\n        .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\r\n        .set_project(env::var(\"APPWRITE_PROJECT_ID\")?)\r\n        .set_key(env::var(\"APPWRITE_API_KEY\")?);\r\n\r\n    let users = Users::new(&client);\r\n    let _list = users\r\n        .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))\r\n        .await?;\r\n\r\n    Ok(())\r\n}\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```rust\r\nuse appwrite::id::ID;\r\nuse appwrite::query::Query;\r\nuse appwrite::services::Users;\r\n\r\nlet users = Users::new(&client);\r\n\r\n// Create user\r\nlet _user = users\r\n    .create(\r\n        ID::unique(),\r\n        Some(\"user@example.com\"),\r\n        None,\r\n        Some(\"password123\"),\r\n        Some(\"User Name\"),\r\n    )\r\n    .await?;\r\n\r\n// List users\r\nlet _list = users\r\n    .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))\r\n    .await?;\r\n\r\n// Get user\r\nlet _fetched = users.get(\"[USER_ID]\").await?;\r\n\r\n// Delete user\r\nusers.delete(\"[USER_ID]\").await?;\r\n```\r\n\r\n### Database Operations\r\n\r\n> **Note:** Use `TablesDB` for new code. Only use `Databases` if the existing project explicitly depends on the legacy Databases API.\r\n>\r\n> **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.\r\n\r\n```rust\r\nuse appwrite::id::ID;\r\nuse appwrite::permission::Permission;\r\nuse appwrite::query::Query;\r\nuse appwrite::role::Role;\r\nuse appwrite::services::TablesDB;\r\nuse serde_json::json;\r\n\r\nlet tables_db = TablesDB::new(&client);\r\n\r\n// Create database\r\nlet _database = tables_db\r\n    .create(ID::unique(), \"My Database\", Some(true))\r\n    .await?;\r\n\r\n// Create table\r\nlet _table = tables_db\r\n    .create_table(\r\n        \"[DATABASE_ID]\",\r\n        ID::unique(),\r\n        \"articles\",\r\n        Some(vec![\r\n            Permission::read(Role::any()).to_string(),\r\n            Permission::create(Role::users(None)).to_string(),\r\n        ]),\r\n        Some(true),\r\n        Some(true),\r\n        None,\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Create row\r\nlet _row = tables_db\r\n    .create_row(\r\n        \"[DATABASE_ID]\",\r\n        \"[TABLE_ID]\",\r\n        ID::unique(),\r\n        json!({\r\n            \"title\": \"Hello World\",\r\n            \"done\": false\r\n        }),\r\n        None,\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Query rows\r\nlet _rows = tables_db\r\n    .list_rows(\r\n        \"[DATABASE_ID]\",\r\n        \"[TABLE_ID]\",\r\n        Some(vec![\r\n            Query::equal(\"done\", false).to_string(),\r\n            Query::limit(10).to_string(),\r\n        ]),\r\n        None,\r\n        Some(true),\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Get row\r\nlet _row = tables_db\r\n    .get_row(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\", None, None)\r\n    .await?;\r\n\r\n// Update row\r\nlet _updated = tables_db\r\n    .update_row(\r\n        \"[DATABASE_ID]\",\r\n        \"[TABLE_ID]\",\r\n        \"[ROW_ID]\",\r\n        Some(json!({ \"done\": true })),\r\n        None,\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Delete row\r\ntables_db\r\n    .delete_row(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"[ROW_ID]\", None)\r\n    .await?;\r\n```\r\n\r\n#### String Column Types\r\n\r\n> **Note:** The legacy `string` column type is deprecated. Use explicit string column types for new tables.\r\n\r\n| Type | Max characters | Indexing | Storage |\r\n|------|---------------|----------|---------|\r\n| `varchar` | 16,383 | Full index (if size <= 768) | Inline in row |\r\n| `text` | 16,383 | Prefix only | Off-page |\r\n| `mediumtext` | 4,194,303 | Prefix only | Off-page |\r\n| `longtext` | 1,073,741,823 | Prefix only | Off-page |\r\n\r\n- `varchar` is stored inline and counts toward the 64 KB row size limit. Prefer it for short, indexed fields like names, slugs, and identifiers.\r\n- `text`, `mediumtext`, and `longtext` are stored off-page, so they do not consume the row size budget. `size` is not required for these types.\r\n\r\n```rust\r\nuse appwrite::enums::{OrderBy, TablesDBIndexType};\r\n\r\n// Short, indexed string\r\nlet _title = tables_db\r\n    .create_varchar_column(\r\n        \"[DATABASE_ID]\",\r\n        \"[TABLE_ID]\",\r\n        \"title\",\r\n        255,\r\n        true,\r\n        None,\r\n        None,\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Off-page longer text\r\nlet _summary = tables_db\r\n    .create_text_column(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"summary\", false, None, None, None)\r\n    .await?;\r\n\r\nlet _body = tables_db\r\n    .create_mediumtext_column(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"body\", false, None, None, None)\r\n    .await?;\r\n\r\nlet _raw_data = tables_db\r\n    .create_longtext_column(\"[DATABASE_ID]\", \"[TABLE_ID]\", \"raw_data\", false, None, None, None)\r\n    .await?;\r\n\r\n// Index only the varchar column fully.\r\nlet _index = tables_db\r\n    .create_index(\r\n        \"[DATABASE_ID]\",\r\n        \"[TABLE_ID]\",\r\n        \"title_idx\",\r\n        TablesDBIndexType::Key,\r\n        vec![\"title\"],\r\n        Some(vec![OrderBy::Asc]),\r\n        Some(vec![255]),\r\n    )\r\n    .await?;\r\n```\r\n\r\n### Query Methods\r\n\r\n`TablesDB` methods accept `Option<Vec<String>>` for queries. Convert each `Query` to a string.\r\n\r\n```rust\r\nuse appwrite::query::Query;\r\nuse serde_json::Value;\r\n\r\nlet queries = vec![\r\n    Query::equal(\"status\", \"published\").to_string(),\r\n    Query::not_equal(\"archived\", true).to_string(),\r\n    Query::greater_than(\"views\", 100).to_string(),\r\n    Query::less_than_equal(\"priority\", 5).to_string(),\r\n    Query::between(\"score\", 1, 100).to_string(),\r\n    Query::is_not_null(\"publishedAt\").to_string(),\r\n    Query::search(\"title\", \"rust appwrite\").to_string(),\r\n    Query::contains(\"tags\", \"rust\").to_string(),\r\n    Query::order_desc(\"$createdAt\").to_string(),\r\n    Query::limit(25).to_string(),\r\n    Query::offset(50).to_string(),\r\n];\r\n\r\nlet multi_value = Query::equal(\r\n    \"status\",\r\n    Value::Array(vec![\r\n        Value::String(\"draft\".to_string()),\r\n        Value::String(\"published\".to_string()),\r\n    ]),\r\n)\r\n.to_string();\r\n```\r\n\r\n### File Storage\r\n\r\n```rust\r\nuse appwrite::id::ID;\r\nuse appwrite::input_file::InputFile;\r\nuse appwrite::permission::Permission;\r\nuse appwrite::query::Query;\r\nuse appwrite::role::Role;\r\nuse appwrite::services::Storage;\r\n\r\nlet storage = Storage::new(&client);\r\n\r\n// Create bucket\r\nlet _bucket = storage\r\n    .create_bucket(\r\n        ID::unique(),\r\n        \"Uploads\",\r\n        Some(vec![\r\n            Permission::read(Role::any()).to_string(),\r\n            Permission::create(Role::users(None)).to_string(),\r\n        ]),\r\n        Some(true),\r\n        Some(true),\r\n        Some(30_000_000),\r\n        Some(vec![\"jpg\".to_string(), \"png\".to_string(), \"pdf\".to_string()]),\r\n        None,\r\n        Some(true),\r\n        Some(true),\r\n        Some(true),\r\n    )\r\n    .await?;\r\n\r\n// Upload from disk\r\nlet input = InputFile::from_path(\"avatar.png\", Some(\"image/png\")).await?;\r\nlet _file = storage\r\n    .create_file(\r\n        \"[BUCKET_ID]\",\r\n        ID::unique(),\r\n        input,\r\n        Some(vec![Permission::read(Role::any()).to_string()]),\r\n    )\r\n    .await?;\r\n\r\n// Upload from bytes\r\nlet input = InputFile::from_bytes(b\"hello\".to_vec(), \"hello.txt\", Some(\"text/plain\"));\r\nlet _file = storage.create_file(\"[BUCKET_ID]\", ID::unique(), input, None).await?;\r\n\r\n// List files\r\nlet _files = storage\r\n    .list_files(\"[BUCKET_ID]\", Some(vec![Query::limit(10).to_string()]), None, Some(true))\r\n    .await?;\r\n\r\n// Download file bytes\r\nlet _content = storage\r\n    .get_file_download(\"[BUCKET_ID]\", \"[FILE_ID]\", None)\r\n    .await?;\r\n\r\n// Delete file\r\nstorage.delete_file(\"[BUCKET_ID]\", \"[FILE_ID]\").await?;\r\n```\r\n\r\n### Functions\r\n\r\n```rust\r\nuse appwrite::enums::ExecutionMethod;\r\nuse appwrite::query::Query;\r\nuse appwrite::services::Functions;\r\nuse serde_json::json;\r\n\r\nlet functions = Functions::new(&client);\r\n\r\n// List functions\r\nlet _functions = functions\r\n    .list(Some(vec![Query::limit(25).to_string()]), None, Some(true))\r\n    .await?;\r\n\r\n// Execute function\r\nlet _execution = functions\r\n    .create_execution(\r\n        \"[FUNCTION_ID]\",\r\n        Some(r#\"{\"hello\":\"world\"}\"#),\r\n        Some(false),\r\n        Some(\"/jobs/sync\"),\r\n        Some(ExecutionMethod::POST),\r\n        Some(json!({ \"content-type\": \"application/json\" })),\r\n        None,\r\n    )\r\n    .await?;\r\n\r\n// Get execution\r\nlet _execution = functions\r\n    .get_execution(\"[FUNCTION_ID]\", \"[EXECUTION_ID]\")\r\n    .await?;\r\n```\r\n\r\n### Permissions\r\n\r\n```rust\r\nuse appwrite::permission::Permission;\r\nuse appwrite::role::Role;\r\n\r\nlet permissions = vec![\r\n    Permission::read(Role::any()).to_string(),\r\n    Permission::create(Role::users(None)).to_string(),\r\n    Permission::update(Role::user(\"[USER_ID]\", None)).to_string(),\r\n    Permission::delete(Role::team(\"[TEAM_ID]\", Some(\"owner\"))).to_string(),\r\n];\r\n```\r\n\r\n### Error Handling\r\n\r\n```rust\r\nmatch users.get(\"[USER_ID]\").await {\r\n    Ok(user) => {\r\n        let _user = user;\r\n    }\r\n    Err(error) if error.status_code() == 404 => {\r\n        eprintln!(\"User not found: {}\", error.get_message());\r\n    }\r\n    Err(error) => {\r\n        eprintln!(\"Appwrite error {}: {}\", error.status_code(), error.get_message());\r\n        return Err(Box::new(error) as Box<dyn std::error::Error>);\r\n    }\r\n}\r\n```\r\n\r\n## Common Pitfalls\r\n\r\n- 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.\r\n- Always `await` service calls.\r\n- Pass `None` for optional parameters you are not using.\r\n- Use `TablesDB`, not legacy `Databases`, for new database code.\r\n- Convert queries with `.to_string()` before passing them to APIs that expect `Option<Vec<String>>`.\r\n- Use `serde_json::json!({...})` for row data and JSON bodies.\r\n- Use `InputFile::from_path(...).await?` or `InputFile::from_bytes(...)` for uploads.\r\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}