Skip to content
Blog

Building a Mobile App with Appwrite: Auth, DB, and Storage

Learn how to build a full-stack mobile app with Appwrite — authentication, databases, and file storage — from a single Flutter codebase.

Published on August 18, 2026

AI Assistant

Every app needs the same backend: sign-up and login, a place to store data, and a place to store files. You could stand up a server, a database, and object storage yourself — or use Appwrite, an open-source backend platform that provides Auth, Databases, Storage, Functions, Realtime, and Messaging behind a single API. You can run it self-hosted or on Appwrite Cloud, and its SDKs keep your client code thin.

In this tutorial, you will learn how to build a mobile app with Appwrite from scratch: initialize the SDK, implement email authentication, persist documents, and upload files — all in Flutter. Key technologies: Flutter, the appwrite Dart SDK, Appwrite Auth, Databases, and Storage.

Prerequisites

  • An Appwrite Cloud project (or a self-hosted instance)
  • Flutter 3.x installed
  • A project ID and API endpoint from your Appwrite console

Core Content

Set up the project and SDK

Create a project in the Appwrite console, then add the SDK:

dependencies:
  appwrite: ^13.0.0

Initialize the client once, at app startup. The endpoint is https://cloud.appwrite.io/v1 for Appwrite Cloud, or your self-hosted URL:

import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('your-project-id');

final account = Account(client);
final databases = Databases(client);
final storage = Storage(client);

Email authentication

Appwrite Auth covers email/password, OAuth, anonymous, and phone. The email flow:

// Sign up — creates the user and starts a session
final user = await account.create(
  userId: ID.unique(),
  email: emailController.text,
  password: passwordController.text,
  name: nameController.text,
);

// Sign in
final session = await account.createEmailSession(
  email: emailController.text,
  password: passwordController.text,
);

// Restore an existing session on app start
try {
  await account.get();
} catch (e) {
  // No session — show the login screen
}

account.get() is the idiomatic “am I logged in?” check; it throws if there’s no valid session, so a persistent login check is one call.

Store and query documents

Create a database and collection in the console, then use Databases to write and read documents:

// Create a document
final doc = await databases.createDocument(
  databaseId: 'main',
  collectionId: 'posts',
  documentId: ID.unique(),
  data: {
    'title': 'Hello Appwrite',
    'body': 'Created from a Flutter app',
    'author': user.name,
  },
);

// List documents
final result = await databases.listDocuments(
  databaseId: 'main',
  collectionId: 'posts',
  queries: [Query.orderDesc('createdAt')],
);

for (final item in result.documents) {
  print(item.data['title']);
}

Documents are JSON; collection schemas define attributes and permissions, so server-side validation and security live in the console rather than your client.

Upload and fetch files

Storage handles images, videos, and documents with built-in permission rules:

// Upload a file
final uploaded = await storage.createFile(
  bucketId: 'avatars',
  fileId: ID.unique(),
  file: InputFile.fromPath(
    path: pickedFile.path,
    filename: 'profile.jpg',
  ),
);

// Build a viewable URL
final fileUrl = Uri.parse(
  'https://cloud.appwrite.io/v1/storage/buckets/avatars/files/${uploaded.\$id}/view'
);

Pass the view URL to an Image.network widget, or read the file bytes with storage.getFileDownload.

Enforce permissions with Appwrite rules

Every document and file in Appwrite has role-based permissions — the client can only read/write what the rules allow. A common pattern for user-owned posts:

final doc = await databases.createDocument(
  databaseId: 'main',
  collectionId: 'posts',
  documentId: ID.unique(),
  data: {'title': 'Private note', 'owner': user.\$id},
  permissions: [
    Permission.read(User(user.\$id)),
    Permission.update(User(user.\$id)),
    Permission.delete(User(user.\$id)),
  ],
);

This is Appwrite’s real strength for mobile: security is configured declaratively, so a client can never access data it shouldn’t, even though the SDK is calling the API directly.

Listen to changes with Realtime

Appwrite Realtime pushes live updates to subscribed clients — useful for chat, feeds, and presence:

final subscription = client.subscribe(
  ['databases.main.collections.posts.documents'],
  (payload) {
    print('Document changed: ${payload.data}');
  },
);

// Later, unsubscribe
await subscription.close();

Putting It All Together

A complete app wires the pieces together: Account for sign-up/login and session restore, Databases for structured data with permissions, Storage for files, and Realtime for live updates. Because Appwrite is framework-agnostic, the same project powers a Flutter app, a web app, and server-side functions — and you can migrate from a self-hosted instance to Cloud without changing your code.

Conclusion & Next Steps

You’ve built a mobile app backend with Appwrite: authentication, databases, file storage, permissions, and realtime — all from a thin Flutter client.

Next Steps: add OAuth login (Google, GitHub) with a few lines, use Appwrite Functions for server-side logic like email notifications, and explore the Appwrite MCP server and agent skills for AI-assisted development.

References: