Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build backends with Appwrite — open-source Backend-as-a-Service. Use when a user asks to set up user authentication, manage a database without writing backend code, handle file storage and uploads, add realtime subscriptions, set up cloud functions, build a mobile or web app backend, replace Firebase with an open-source alternative, or self-host a BaaS platform. Covers auth, databases, storage, functions, realtime, and SDK integration for web, mobile, and server-side.
.claude/skills/terminalskills-appwrite/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 110% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 193% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 136% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 214% | 0% |
Appwrite is an open-source Backend-as-a-Service (BaaS) providing authentication, databases, file storage, cloud functions, and realtime subscriptions — all through a single self-hosted Docker deployment. It's the open-source alternative to Firebase, with SDKs for web (JavaScript), mobile (Flutter, Swift, Kotlin), and server-side (Node.js, Python, PHP). This skill covers self-hosting setup, authentication, database operations, file storage, serverless functions, and realtime subscriptions.
bash# One-command Docker setup docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)/appwrite:/usr/src/code/appwrite:rw" \ --entrypoint="install" \ appwrite/appwrite:latest # Or with Docker Compose (production) curl -o docker-compose.yml https://appwrite.io/install/compose curl -o .env https://appwrite.io/install/env docker compose up -d # Console: http://localhost/console # Create your first project in the console UI
javascript// lib/appwrite.js — Client SDK setup and authentication import { Client, Account, ID } from 'appwrite' const client = new Client() .setEndpoint('http://localhost/v1') // Appwrite API endpoint .setProject('your-project-id') // from console const account = new Account(client) // Sign up async function signUp(email, password, name) { const user = await account.create(ID.unique(), email, password, name) // Auto-login after signup await account.createEmailPasswordSession(email, password) return user } // Login async function login(email, password) { return await account.createEmailPasswordSession(email, password) } // OAuth login (Google, GitHub, Apple, etc.) account.createOAuth2Session('google', 'http://localhost:3000/callback', 'http://localhost:3000/login') // Get current user async function getCurrentUser() { try { return await account.get() } catch { return null // not logged in } } // Logout async function logout() { await account.deleteSession('current') }
javascript// lib/database.js — CRUD operations with Appwrite Databases import { Client, Databases, ID, Query } from 'appwrite' const client = new Client() .setEndpoint('http://localhost/v1') .setProject('your-project-id') const databases = new Databases(client) const DB_ID = 'main' const COLLECTION_ID = 'posts' // Create document async function createPost(title, content, authorId) { return await databases.createDocument(DB_ID, COLLECTION_ID, ID.unique(), { title, content, author_id: authorId, status: 'draft', created_at: new Date().toISOString(), }) } // List with filters and pagination async function listPublishedPosts(page = 1, limit = 10) { return await databases.listDocuments(DB_ID, COLLECTION_ID, [ Query.equal('status', 'published'), Query.orderDesc('created_at'), Query.limit(limit), Query.offset((page - 1) * limit), ]) } // Update async function publishPost(postId) { return await databases.updateDocument(DB_ID, COLLECTION_ID, postId, { status: 'published', published_at: new Date().toISOString(), }) } // Delete async function deletePost(postId) { await databases.deleteDocument(DB_ID, COLLECTION_ID, postId) }
javascript// lib/storage.js — Upload and manage files import { Client, Storage, ID } from 'appwrite' const storage = new Storage(new Client() .setEndpoint('http://localhost/v1') .setProject('your-project-id')) const BUCKET_ID = 'uploads' // Upload file async function uploadFile(file) { /** * Upload a file to Appwrite storage. * Args: * file: File object from <input type="file"> or drag-and-drop */ return await storage.createFile(BUCKET_ID, ID.unique(), file) } // Get file URL (with transformations for images) function getFilePreview(fileId, width = 400, height = 300) { return storage.getFilePreview(BUCKET_ID, fileId, width, height) } // Download file function getFileDownload(fileId) { return storage.getFileDownload(BUCKET_ID, fileId) } // Delete file async function deleteFile(fileId) { await storage.deleteFile(BUCKET_ID, fileId) }
javascript// functions/on-order-created/src/main.js — Serverless function triggered by database event // Deploy via Appwrite CLI: appwrite deploy function import { Client, Databases, Users } from 'node-appwrite' export default async ({ req, res, log, error }) => { const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT) .setKey(process.env.APPWRITE_API_KEY) const payload = JSON.parse(req.body) const order = payload.$id ? payload : payload.data log(`New order: ${order.$id}, total: ${order.total}`) // Send notification, update inventory, etc. const users = new Users(client) const user = await users.get(order.user_id) log(`Order by: ${user.email}`) return res.json({ success: true, orderId: order.$id }) }
javascript// hooks/useRealtime.js — Subscribe to live database changes import { Client } from 'appwrite' const client = new Client() .setEndpoint('http://localhost/v1') .setProject('your-project-id') // Subscribe to changes in a collection const unsubscribe = client.subscribe( 'databases.main.collections.messages.documents', (response) => { // Fires on create, update, delete const event = response.events[0] const document = response.payload if (event.includes('.create')) { console.log('New message:', document) } else if (event.includes('.update')) { console.log('Updated:', document) } else if (event.includes('.delete')) { console.log('Deleted:', document.$id) } } ) // Cleanup // unsubscribe()
User prompt: "I want to build a recipe sharing app. Users sign up, post recipes with photos, and browse others' recipes. Use an open-source backend I can self-host."
The agent will:
User prompt: "We're using Firebase but want to self-host for data sovereignty. Migrate our auth, Firestore, and storage to something open-source."
The agent will:
read("any"), write("user:USER_ID")) to control document access. Default is owner-only — explicitly set permissions for public content.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 14,814 | 11,271 | -24% | 1 | 1 | 0% | 3,317 | 4,669 | +41% | 0 | 0 | — |
case-02 | fail→pass | 10,105 | 6,935 | -31% | 1 | 1 | 0% | 2,124 | 3,492 | +64% | 0 | 0 | — |
case-03 | pass→pass | 4,246 | 2,362 | -44% | 1 | 1 | 0% | 853 | 2,500 | +193% | 0 | 0 | — |
case-04 | pass→pass | 7,244 | 5,512 | -24% | 1 | 1 | 0% | 1,397 | 3,302 | +136% | 0 | 0 | — |
case-05 | pass→pass | 7,427 | 4,328 | -42% | 1 | 1 | 0% | 960 | 3,012 | +214% | 0 | 0 | — |
case-06 | pass→pass | 6,780 | 4,080 | -40% | 1 | 1 | 0% | 1,224 | 2,975 | +143% | 0 | 0 | — |
case-07 | fail→fail | 7,430 | 6,003 | -19% | 1 | 1 | 0% | 1,501 | 3,003 | +100% | 0 | 0 | — |
case-08 | pass→pass | 5,828 | 4,458 | -24% | 1 | 1 | 0% | 1,171 | 3,003 | +156% | 0 | 0 | — |
case-09 | pass→pass | 8,221 | 4,631 | -44% | 1 | 1 | 0% | 1,563 | 3,039 | +94% | 0 | 0 | — |
case-10 | pass→pass | 11,947 | 5,853 | -51% | 1 | 1 | 0% | 2,186 | 3,271 | +50% | 0 | 0 | — |
case-11 | pass→pass | 3,612 | 3,493 | -3% | 1 | 1 | 0% | 688 | 2,816 | +309% | 0 | 0 | — |
case-12 | pass→pass | 11,321 | 1,956 | -83% | 1 | 1 | 0% | 1,835 | 2,446 | +33% | 0 | 0 | — |
case-13 | fail→pass | 7,875 | 3,369 | -57% | 1 | 1 | 0% | 1,276 | 2,677 | +110% | 0 | 0 | — |
case-14 | pass→pass | 5,273 | 4,549 | -14% | 1 | 1 | 0% | 869 | 3,016 | +247% | 0 | 0 | — |
case-15 | fail→fail | 6,495 | 4,326 | -33% | 1 | 1 | 0% | 1,115 | 2,990 | +168% | 0 | 0 | — |
case-16 | pass→pass | 7,893 | 7,866 | -0% | 1 | 1 | 0% | 1,365 | 3,472 | +154% | 0 | 0 | — |
case-17 | pass→pass | 12,819 | 11,798 | -8% | 1 | 1 | 0% | 2,301 | 4,228 | +84% | 0 | 0 | — |
case-18 | pass→pass | 15,558 | 11,052 | -29% | 1 | 1 | 0% | 2,788 | 4,263 | +53% | 0 | 0 | — |
case-19 | pass→pass | 5,098 | 3,326 | -35% | 1 | 1 | 0% | 967 | 2,786 | +188% | 0 | 0 | — |
case-20 | pass→pass | 11,974 | 12,940 | +8% | 1 | 1 | 0% | 2,427 | 4,619 | +90% | 0 | 0 | — |
case-21 | pass→pass | 3,604 | 3,938 | +9% | 1 | 1 | 0% | 697 | 2,879 | +313% | 0 | 0 | — |
case-22 | fail→fail | 19,951 | 10,322 | -48% | 1 | 1 | 0% | 3,904 | 4,242 | +9% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.