# Appwrite > --- # Appwrite Documentation Source: https://appwrite.io/llms-full.txt --- # Appwrite ## Migrations https://appwrite.io/docs/advanced/migrations If you're looking to migrate existing projects to Appwrite, Migrations can help you make the move more quickly. You can move your app from Firebase, Supabase, Nhost, and even move between self-hosted and Cloud projects using Migrations. You can also use Migrations to move between two self-hosted instances or even to duplicate projects on the same instance. Migrations will automatically move accounts, database rows, and storage files from one source to another. ### Sources {% #sources %} Appwrite supports multiple source destinations for migrating your data. You can transfer data from these sources to a new or existing Appwrite project. Resources marked as 'enabled' are migrated automatically. Resources marked as 'partial' can be migrated but with limitations or caveats; please refer to the guide for each source to learn more. Resources marked as 'manual' require manual migration. | Source | Users | Databases | Documents | Files | Functions | |--------|-------|-----------|-----------|-------|-----------| | [Firebase](/docs/advanced/migrations/firebase) | enabled | enabled | partial | enabled | manual | | [Supabase](/docs/advanced/migrations/supabase) | enabled | enabled | partial | enabled | manual | | [Nhost](/docs/advanced/migrations/nhost) | enabled | enabled | partial | enabled | manual | | [Cloud](/docs/advanced/migrations/cloud) | enabled | enabled | enabled | enabled | enabled | | [Self hosted](/docs/advanced/migrations/self-hosted) | enabled | enabled | enabled | enabled | enabled | ### Limitations {% #limitations %} Migrations cannot transfer all data perfectly, so certain fields, such as `$createdAt` and `$updatedAt`, may not be transferred. More information can be found on the migration page for each source. Migrations help you jump-start your move, but because each product is unique, complex databases and product unique features like functions might need to be migrated manually. We also recommend you carefully **validate permissions and data integrity** when moving between platforms. ### Charges {% #charges %} When you migrate data from another source to Appwrite Cloud, the resource usage during the migration will not count towards your Appwrite Cloud usage charges. However, your source vendor may have data transfer charges. The same is true for moving data between self-hosted Appwrite instances hosted on different cloud providers. --- ## Migrate from Cloud https://appwrite.io/docs/advanced/migrations/cloud Migrations make it as easy as a couple of clicks to move all your Appwrite Cloud data into a self-hosted instance. {% section #notices step=1 title="Things to keep in mind" %} 1. Data transferred by migrations will reset `$createdAt` and `$updatedAt` timestamps to the date of the migration. 2. Your self-hosted Appwrite project must be accessible from the internet for the migration to work. 3. Migrations are non-destructive. No data will be deleted or lost in the source project. {% /section %} {% section #create-migration step=2 title="Create migration" %} To begin migrating to self-hosted, make sure to read the [migration overview](/docs/advanced/migrations) and [things to keep in mind](#notices) sections above. 1. Navigate to your Cloud project's console, navigate to **Settings** and click on the **Migrations** tab. 1. Under **Export to self-hosted instance**, click **Export data**. 1. You will complete the migration on your self-hosted instance. {% /section %} {% section #continue-on-self-hosted step=3 title="Continue on self-hosted" %} 1. Once redirected to your self-hosted project, you'll be prompted to select an organization and a project. You can migrate to an existing project or create a new one. 1. Select the data you wish to migrate. You can choose among accounts, databases, rows, files, and functions. 1. Click **Start Migration** to start the migration process. You do not need to keep the Appwrite Console open through the process. {% info title="Keep in mind" %} Your self-hosted instance will generate an API Key in the background to pass to Appwrite Cloud. You can revoke this key after the migration process is complete. {% /info %} {% /section %} --- ## Migrate from Firebase https://appwrite.io/docs/advanced/migrations/firebase Appwrite migrations help you quickly migrate your data from Firebase or other [sources](/docs/advanced/migrations#sources) to Appwrite. You can follow the instructions on the Appwrite Console migration wizard or use this guide to perform your data migration. While migrations are a great way to move your data from other services to Appwrite and get started quickly, they're not perfect. Make sure to understand the different [limitations](#limitations) before completing your migration. {% info title="Charges" %} When you migrate data from Firebase to Appwrite Cloud, the resource usage during the migration will not count towards your Appwrite Cloud usage charges. However, Firebase, may have data transfer charges. {% /info %} {% section #create-service-account step=1 title="Create service account" %} To begin migrating to Appwrite, follow these steps. 1. Open your Firebase console. 1. Access your **Project Settings** by clicking the gear icon. 1. Click on **Service Accounts**, then click on the **Manage service account permissions** link, which will redirect you to the Google Cloud Console. 1. Click on **Create Service Account**, provide a name, ID, and description, and click **Continue**. 1. Grant the service account the following roles | Role | Description | |-------------------------|------------------------------------------------------------------------------| | Firebase Viewer | Read access to your entire Firebase project, including Database and Storage. | | Identity Toolkit Viewer | Read access to your users, including their hash config. | {% /section %} {% section #create-api-key step=2 title="Create API key" %} 1. Find the service account you just created. 1. Click the triple-dot icon to the right to see more options and click the **Manage keys** button. 1. Click **Add Key** and select **Create new key**. Choose **JSON** as the key type and click **Create**. This will download a JSON file to your computer. {% /section %} {% section #start-migration step=3 title="Start Migration" %} 1. Create a new project and navigate to the **Migrations** tab in **Project Settings**. 1. Click on the **Create Migration** button and select **Firebase** as your source. 1. Paste the contents of your JSON file into the textbox and follow the migration wizard to select which resources you need to migrate. Finally click **Start migration** to begin the migration process. {% /section %} {% section #next-steps step=4 title="Next steps" %} 1. In your Appwrite Console, navigate to **Overview** > **Integrations** > **Platforms**, add the platforms for your Web, Flutter, Android, and iOS apps. Appwrite will reject requests from unknown web, Flutter, and mobile apps to protect from malicious attacks. You app **must be added as a platform** for Appwrite to accept requests. 1. Remember to [add appropriate permissions](/docs/advanced/platform/permissions) to the migrated resources to protect user data and privacy. 1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop). 1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs). {% /section %} ### Limitations {% #limitations %} Not all vendors make their APIs publicly accessible or easy to use for extracting and fully owning your data. Furthermore, due to varying design philosophies, certain resources cannot be migrated on a one-to-one basis. Below, you'll find a list of some known limitations when migrating data from Firebase to Appwrite. It's advisable to review this list before initiating your migration or deploying your product in a production environment. - Appwrite Migrations only supports Firestore as a database source; Realtime Database is currently not supported. - At the moment, only top-level row migration is supported. Nested rows will not be migrated automatically. - OAuth users will not be migrated because the sessions are managed by the third-party OAuth provider. Users will need to re-authenticate with your OAuth provider after the migration is complete. - Functions are not automatically migrated because of syntax and runtime differences. --- ## Migrate from Nhost https://appwrite.io/docs/advanced/migrations/nhost Appwrite migrations help you quickly migrate your data from Nhost or other [sources](/docs/advanced/migrations#sources) to Appwrite. You can follow the instructions on the Appwrite Console migration wizard or use this guide to perform your data migration. While migrations are a great way to move your data from other services to Appwrite and get started quickly, they're not perfect. Make sure to understand the different [limitations](#limitations) before completing your migration. {% info title="Charges" %} When you migrate data from Nhost to Appwrite Cloud, the resource usage during the migration will not count towards your Appwrite Cloud usage charges. However, Nhost, may have data transfer charges. {% /info %} {% section #obtain-credentials step=1 title="Obtain credentials" %} Find all of the following credentials from your Nhost project. | Field | Description | | --------------- | -------------------------------------------------------------------------------------------------------------- | | **Region** | The region your Nhost project is hosted in. This can be found in your Nhost project environment variables as `NHOST_REGION`. | | **Subdomain** | The subdomain of your Nhost project. This can be found in your Nhost project environment variables as `NHOST_SUBDOMAIN`. | | **Database** | The name of your Nhost database. This can be found in your Nhost project Database settings. | | **Username** | The username of your Nhost database. This can be found in your Nhost project Database settings. | | **Password** | The password of your Nhost database. You set this when you created your Nhost project, if you don't remember it you can reset it from your Nhost project Database settings. | | **Admin Secret** | The admin secret of your Nhost project. This can be found in your Nhost project environment variables as `NHOST_ADMIN_SECRET`. We use this to transfer your Nhost files to Appwrite. | {% /section %} {% section #migration-process step=2 title="Migrating to Appwrite from Nhost" %} Before migrating to Appwrite make sure you've read the [migration overview](/docs/advanced/migrations) page. 1. Create a new project and click on the **Migrations** tab in **Project Settings**. 1. Click on the **Create Migration** button and select **Nhost** as your source. 1. Enter the credentials from the [Obtain credentials](#obtain-credentials) step and click **Next**. 1. Select the resources you want to migrate and finally click **Start migration** to begin the migration process. {% /section %} {% section #next-steps step=3 title="Next steps" %} 1. In your Appwrite Console, navigate to **Overview** > **Integrations** > **Platforms**, add the platforms for your Web, Flutter, Android, and iOS apps. Appwrite will reject requests from unknown web, Flutter, and mobile apps to protect from malicious attacks. You app **must be added as a platform** for Appwrite to accept requests. 1. Remember to [add appropriate permissions](/docs/advanced/platform/permissions) to the migrated resources to protect user data and privacy. 1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop). 1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs). {% /section %} ### Limitations {% #limitations %} Not all vendors make their APIs publicly accessible or easy to use for extracting and fully owning your data. Furthermore, due to varying design philosophies, certain resources cannot be migrated on a one-to-one basis. Below, you'll find a list of some known limitations when migrating data from Nhost to Appwrite. It's advisable to review this list before initiating your migration or deploying your product in a production environment. - Appwrite's Database doesn't support all the features of the PostgreSQL database so postgres centric things like advanced indexes, PostgreSQL functions and scheduling will not be migrated. - OAuth users will not be migrated because the sessions are managed by the third-party OAuth provider. Users will need to re-authenticate with your OAuth provider after the migration is complete. - Functions are not automatically migrated because of syntax and runtime differences. --- ## Migrate from self-hosted https://appwrite.io/docs/advanced/migrations/self-hosted Migrations makes it as easy as a couple clicks to move all of your self-hosted project data to a Cloud instance. {% section #notices step=1 title="Things to keep in mind" %} 1. Data transferred by migrations will reset `$createdAt` and `$updatedAt` timestamps to the date of the migration. 2. Your self-hosted Appwrite project must be accessible from the internet for the migration to work. 3. Migrations are non-destructive. No data will be deleted or lost in the source project. {% /section %} {% section #create-migration step=2 title="Create migration" %} To begin migrating to Cloud, make sure to read the [migration overview](/docs/advanced/migrations) and [things to keep in mind](#notices) sections above. 1. Navigate to your self-hosted project's Console and click on the **Migrations** tab. 2. Click **Deploy to Cloud**, you will be redirected to Appwrite Cloud. 3. You will complete the migration on Appwrite Cloud. {% /section %} {% section #continue-on-cloud step=3 title="Continue on Appwrite Cloud" %} 1. Once redirected to Appwrite Cloud, you'll be prompted to select an organization and a project. You can migrate to an existing project or create a new one. 2. Select the data you wish to migrate. You can choose among accounts, databases, rows, files, and functions. 3. Click **Start migration** to start the migration process. You do not need to keep the Appwrite Console open through the process. {% /section %} --- ## Migrate from Supabase https://appwrite.io/docs/advanced/migrations/supabase Appwrite migrations help you quickly migrate your data from Supabase or other [sources](/docs/advanced/migrations#sources) to Appwrite. You can follow the instructions on the Appwrite Console migration wizard or use this guide to perform your data migration. While migrations are a great way to move your data from other services to Appwrite and get started quickly, they're not perfect. Make sure to understand the different [limitations](#limitations) before completing your migration. {% info title="Charges" %} When you migrate data from Supabase to Appwrite Cloud, the resource usage during the migration will not count towards your Appwrite Cloud usage charges. However, Supabase, may have data transfer charges. {% /info %} {% section #obtain-credentials step=1 title="Obtain credentials" %} Find all of the following credentials from your Supabase project. | Field | Description | | ---------------| -------------------------------------------------------------------------------------------------------------- | | **Host** | The host of your Supabase Database, this can be found in your Supabase project settings under **Database Settings** and **Host**. | | **Port** | The port of your Supabase Database, this can be found in your Supabase project settings under **Database Settings** and **Port**. By default, this is **5432**. | | **Username** | The username of your Supabase Database, this can be found in your Supabase project settings under **Database Settings** and **Username**. | | **Password** | The password of your Supabase Database, this was set when you created your Supabase project. If you forgot your password, you can reset it within **Database Settings**. | | **Endpoint** | This is the endpoint of your Supabase instance under **Project Settings > API**. This is used to migrate your files. | | **API Key** | This is the key of your Supabase instance under **Project Settings > API**. This is used to migrate your files. Make sure to use the hidden **service_role** key. | {% /section %} {% section #create-migration step=2 title="Create migration" %} Before migrating to Appwrite make sure you've read the [migration overview](/docs/advanced/migrations) page. 1. Create a new project and click on the **Migrations** tab in **Project Settings**. 1. Click on the **Create Migration** button and select **Supabase** as your source. 1. Enter the credentials from the [Obtain credentials](#obtain-credentials) step and click **Next**. 1. Select the resources you want to migrate and finally click **Start migration** to begin the migration process. {% /section %} {% section #next-steps step=3 title="Next steps" %} 1. In your Appwrite Console, navigate to **Overview** > **Integrations** > **Platforms**, add the platforms for your Web, Flutter, Android, and iOS apps. Appwrite will reject requests from unknown web, Flutter, and mobile apps to protect from malicious attacks. You app **must be added as a platform** for Appwrite to accept requests. 1. Remember to [add appropriate permissions](/docs/advanced/platform/permissions) to the migrated resources to protect user data and privacy. 1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop). 1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs). {% /section %} ### Limitations {% #limitations %} Not all vendors make their APIs publicly accessible or easy to use for extracting and fully owning your data. Furthermore, due to varying design philosophies, certain resources cannot be migrated on a one-to-one basis. Below, you'll find a list of some known limitations when migrating data from Supabase to Appwrite. It's advisable to review this list before initiating your migration or deploying your product in a production environment. - Appwrite's Databases services support a different set of features as PostgreSQL. Some features like advanced indexes, Postgres functions, and scheduling will not be migrated. - OAuth users will not be migrated because the sessions are managed by the third-party OAuth provider. Users will need to re-authenticate with your OAuth provider after the migration is complete. - Functions are not automatically migrated because of syntax and runtime differences. --- ## Platform https://appwrite.io/docs/advanced/platform Appwrite is a development platform designed to adapt your unique use cases. Appwrite provides features that help you maintain, scale, and integrate Appwrite with other platforms. ### Integration {% #integration %} Appwrite is designed to integrate with both frontend and backend apps. Learn about advanced integrations and API response codes. {% cards %} {% cards_item href="/docs/advanced/platform/events" title="Events" %} Appwrite allows you to react to events that occur on the platform. {% /cards_item %} {% cards_item href="/docs/advanced/platform/webhooks" title="Webhooks" %} Use webhooks to update backend integrations about Appwrite events. {% /cards_item %} {% cards_item href="/docs/advanced/platform/response-codes" title="Response codes" %} Learn about response codes and errors returned by Appwrite APIs. {% /cards_item %} {% /cards %} ### Access control {% #access-control %} Appwrite is secure by default and provides tools for you to manage access control and prevent abuse. {% cards %} {% cards_item href="/docs/advanced/platform/permissions" title="Permissions" %} Control which users can access which resources. {% /cards_item %} {% cards_item href="/docs/advanced/platform/rate-limits" title="Rate limits" %} Appwrite has rate limits on some endpoints to prevent abuse. {% /cards_item %} {% cards_item href="/docs/advanced/platform/api-keys" title="API keys" %} Create and manage API keys used by Server SDKs. {% /cards_item %} {% cards_item href="/docs/advanced/platform/dev-keys" title="Dev keys" %} Create and manage dev keys used by Client SDKs in dev environments. {% /cards_item %} {% /cards %} ### Plans {% #plans %} Learn which plan best suits your organization and how to manage billing. {% cards %} {% cards_item href="/docs/advanced/platform/billing" title="Billing" %} Learn to manage your billing information. {% /cards_item %} {% cards_item href="/docs/advanced/platform/free" title="Free" %} Learn about Appwrite Free plan. Free plan for hobby projects and learners. {% /cards_item %} {% cards_item href="/docs/advanced/platform/pro" title="Pro" %} Learn about Appwrite Pro, for growing organizations that need to scale. {% /cards_item %} {% cards_item href="/docs/advanced/platform/scale" title="Scale" %} Coming soon. {% /cards_item %} {% cards_item href="/docs/advanced/platform/enterprise" title="Enterprise" %} Learn about Appwrite Enterprise, for large organizations with advanced needs. {% /cards_item %} {% cards_item href="/docs/advanced/platform/oss" title="Open source" %} Appwrite provides special plans for open source projects. {% /cards_item %} {% /cards %} ### Configuration {% #configuration %} Configure custom domains and customize communication templates. {% cards %} {% cards_item href="/docs/advanced/platform/custom-domains" title="Custom domains" %} Add a custom domain for your Appwrite APIs. {% /cards_item %} {% cards_item href="/docs/advanced/platform/message-templates" title="Message templates" %} Create custom branding and messages when you communicate with users. {% /cards_item %} {% /cards %} ### Add Ons {% #add-ons %} Learn about additional features and functionalities that Appwrite offers. {% cards %} {% cards_item href="/docs/advanced/platform/compute" title="Compute" %} Learn about Appwrite's different compute add-ons. {% /cards_item %} {% cards_item href="/docs/advanced/platform/phone-otp" title="Phone OTP" %} Learn how Appwrite handles SMS-based OTP authentication. {% /cards_item %} {% cards_item href="/docs/advanced/platform/image-transformations" title="Image Transformations" %} Learn how to transform images dynamically with Appwrite. {% /cards_item %} {% cards_item href="/docs/advanced/platform/database-reads-and-writes" title="Database Reads and Writes" %} Learn how Appwrite handles database reads and writes. {% /cards_item %} {% /cards %} --- ## Abuse policy https://appwrite.io/docs/advanced/platform/abuse Appwrite is committed to providing a fair, secure, and high-quality experience for all users. This Abuse Policy, as part of our overall Fair Use Policy, outlines unacceptable behaviors and the steps you can take to report any suspected abuse. Our goal is to maintain a safe environment where everyone can build, innovate, and collaborate without fear of harmful or illegal activity. {% partial file="prohibited-activities.md" /%} ### Reporting Abuse {% #reporting-abuse %} If you observe or suspect any prohibited activity, please report it as soon as possible to [abuse@appwrite.io](mailto:abuse@appwrite.io). Please include any relevant details (e.g., specific URLs, project IDs, or screenshots) so that we can effectively investigate and address the issue. We will review each report confidentially and take any necessary actions, which may include account suspension, service termination, or referral to law enforcement. --- ## API keys https://appwrite.io/docs/advanced/platform/api-keys API keys are secrets used by Appwrite [Server SDKs](/docs/sdks#server) and the Appwrite CLI to prove their identity. What can be accessed each API key is restricted by [scopes](#scopes) instead of permissions. {% info title="Best practice" %} It is a best practice to grant only the scopes you need to meet your project's goals to an API key. API keys should be treated as a secret. Never share the API key and keep API keys out of client applications. {% /info %} ### API keys vs Dev keys {% #api-keys-vs-dev-keys %} API keys and [Dev keys](/docs/advanced/platform/dev-keys) are not the same and cannot be used interchangeably. API keys permit access to Appwrite services in production environments, with access controlled through scopes to ensure secure and controlled server-side operations. Dev keys, conversely, are specifically designed to help you avoid abuse limits and CORS errors in test and development environments. API keys are for server SDKs and the CLI in production environments, while Dev keys are for client SDKs in development environments. ### Create API key {% #create-api-key %} {% only_dark %} ![Project settings screen](/images/docs/platform/dark/create-api-key.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/platform/create-api-key.png) {% /only_light %} To create a new API key, navigate to **Overview** > **Integration** > **API keys** and click **Create API key**. You can then use the API key to initialize the Appwrite client in your server-side apps. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); // Your API key ``` ```server-python from appwrite.client import Client client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') # Your API key ``` ```server-php use Appwrite\Client; $client = new Client(); $client ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); // Your API key ``` ```server-go package main import "github.com/appwrite/sdk-for-go/client" func main() { c := client.NewClient() c.SetEndpoint("https://.cloud.appwrite.io/v1") c.SetProject("") c.SetKey("") // Your API key } ``` ```server-ruby require 'appwrite' client = Appwrite::Client.new client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') # Your API key ``` ```server-deno import { Client } from 'npm:appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); // Your API key ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); // Your API key ``` ```server-kotlin import io.appwrite.Client val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") // Your API key ``` ```server-dotnet using Appwrite; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); // Your API key ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") // Your API key ``` ```server-java import io.appwrite.Client; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); // Your API key ``` {% /multicode %} When adding a new API Key, you can choose which [scopes](#scopes) to grant your application. If you need to replace your API Key, create a new key, update your app credentials and, once ready, delete your old key. ### Scopes {% #scopes %} | Name | Description | |-----------------------|---------------------------------------------------------------------------------| | `sessions.write` | Access to create, update, and delete user sessions | | `users.read` | Access to read your project's users | | `users.write` | Access to create, update, and delete your project's users | | `teams.read` | Access to read your project's teams | | `teams.write` | Access to create, update, and delete your project's teams | | `databases.read` | Access to read your project's databases | | `databases.write` | Access to create, update, and delete your project's databases | | `tables.read` | Access to read your project's database tables | | `tables.write` | Access to create, update, and delete your project's database tables | | `columns.read` | Access to read your project's database table's columns | | `columns.write` | Access to create, update, and delete your project's database table's columns | | `indexes.read` | Access to read your project's database table's indexes | | `indexes.write` | Access to create, update, and delete your project's database table's indexes | | `rows.read` | Access to read your project's database rows | | `rows.write` | Access to create, update, and delete your project's database rows | | `files.read` | Access to read your project's storage files and preview images | | `files.write` | Access to create, update, and delete your project's storage files | | `buckets.read` | Access to read your project's storage buckets | | `buckets.write` | Access to create, update, and delete your project's storage buckets | | `functions.read` | Access to read your project's functions and code deployments | | `functions.write` | Access to create, update, and delete your project's functions and code deployments| | `sites.read` | Access to read your project's sites and deployments | | `sites.write` | Access to create, update, and delete your project's sites and deployments | | `log.read` | Access to read your site's logs | | `log.write` | Access to update, and delete your site's logs | | `execution.read` | Access to read your project's execution logs | | `execution.write` | Access to execute your project's functions | | `locale.read` | Access to access your project's Locale service | | `avatars.read` | Access to access your project's Avatars service | | `health.read` | Access to read your project's health status | | `providers.read` | Access to read your project's providers | | `providers.write` | Access to create, update, and delete your project's providers | | `messages.read` | Access to read your project's messages | | `messages.write` | Access to create, update, and delete your project's messages | | `topics.read` | Access to read your project's topics | | `topics.write` | Access to create, update, and delete your project's topics | | `subscribers.read` | Access to read your project's subscribers | | `subscribers.write` | Access to create, update, and delete your project's subscribers | | `targets.read` | Access to read your project's targets | | `targets.write` | Access to create, update, and delete your project's targets | | `rules.read` | Access to read your project's proxy rules | | `rules.write` | Access to create, update, and delete your project's proxy rules | | `migrations.read` | Access to read your project's migrations | | `migrations.write` | Access to create, update, and delete your project's migrations. | | `vcs.read` | Access to read your project's VCS repositories | | `vcs.write` | Access to create, update, and delete your project's VCS repositories | | `assistant.read` | Access to read the Assistant service | | `tokens.read` | Access to read your project's tokens | | `tokens.write` | Access to create, update, and delete your project's tokens | --- ## Billing https://appwrite.io/docs/advanced/platform/billing Appwrite allows you to configure billing per organization. You can access your organizations billing information under the **Billing** tab of your organization. ### Plans {% #plans %} You can view or change your organization's plan under the **Billing** section. You'll also find the expected cost, as well as the start and end date of the current billing period. #### Billing period {% #billing-period %} Billing periods begin the day you change your plan, and lasts 30 days. Your resource limits are reset at the beginning of each billing period. Charges are applied at different times: - **Projects and add-ons** are billed at the start of the billing period - **Additional usage** (like bandwidth, execution time, and GB-hours) is billed at the start of the following billing period based on your actual usage ### Payment history {% #payment-history %} You can view and download you past invoices under **Payment history**. You can click the three-dots menu to view and download your invoices. ### Payment methods {% #payment-methods %} Appwrite Cloud accepts credit and debit cards as payment methods and will bill the card at the end of each billing cycle. Appwrite accepts Visa, Mastercard, American Express, Discover & Diners Club, China UnionPay, Japan Credit Bureau (JCB), Cartes Bancaires, and eftpos Australia. ### Billing address {% #billing-address %} Your billing address will be displayed on your invoices and used when Appwrite Cloud bills your payment method at the end of a billing cycle. ### Tax ID {% #tax-id %} If you'd like you or your company's tax ID displayed on your generated invoice, you can provide it under **Tax ID**. ### Budget cap {% #budget-cap %} Appwrite allows you to set budget caps for your organization. Appwrite will automatically scale your projects by purchasing add-ons as they require more resources. Budget caps limit the amount of automatic scaling and prevent unexpected bills. This budget cap does **not include the plan's recurring cost**, only add-ons. You can enable budget caps under **Budget cap**, toggle the option **Enable budget cap**. You will be able to set a budget cap in USD. #### Budget alerts {% #budget-alerts %} When you enable budget cap, you'll be able to configure alerts under **Budget alerts** to warn you when your organization is near the budget cap. By default, an email alert will be sent at 75% of the budget cap. You can add up to three checkpoints that send budget alerts when reached. ### Redeem credit {% #redeem-credit %} If you received a redeemable code for Appwrite Cloud credits, you can redeem them in the **Available credit** section. When you redeem credit, the credit balance will be automatically applied to the next billing cycle. ### Recurring payments for Indian developers {% #recurring-payments-for-indian-developers %} The Reserve Bank of India (RBI) mandates additional security measures for recurring payments on **Indian cards**. Appwrite is obligated to ask for verification before billing your card. Appwrite asks for verification for up to $150 in case you use add-ons. Confirming the verification will not charge your card. When you are charged at the end of the billing period, you will not be charged more than your monthly plan plus add-ons used. If you set a budget cap, the verification will remain at $150, but the budget cap will still be applied for your add-ons. Again, you will not be charged when confirming the verification. When you are charged at the end of the billing period, you will not be charged more than your monthly plan plus add-ons used. If you need higher limits, [contact us](mailto:billing@appwrite.io). --- ## Compute https://appwrite.io/docs/advanced/platform/compute Appwrite's paid plans give you the ability to change your function's allocated CPU Cores and Memory, enabling functions and builds to perform more computionally demanding actions quicker. These options enable greater performance and flexibility, allowing developers to optimize their functions based on specific requirements. For instance, resource-intensive tasks such as real-time data processing or complex computational operations can now be executed more efficiently. Additionally, enhanced memory configurations support larger datasets and more demanding applications, broadening the scope of what can be achieved with Appwrite Functions. ### Specifications {% #specifications %} Appwrite Cloud has the following specifications available: | Memory | CPU cores | Hourly usage | |--------|-----------|--------------| | 512MB | 0.5 | 0.25 | | 512MB | 1 | 0.5 | | 1GB | 1 | 1 | | 2GB | 2 | 4 | | 4GB | 2 | 8 | | 4GB | 4 | 16 | {% info title="Note" %} Only customers on either Pro or Scale are able to change their specification from the default 512MB & 0.5 CPU option. For custom compute options please contact our [sales team](https://appwrite.io/contact-us/enterprise). {% /info %} ### GB-Hours {% #gb-hours %} GB-hours is a metric used to quantify the consumption of compute resources by combining both memory usage and the duration of that usage. Specifically, it represents the number of gigabytes (GB) of memory utilized multiplied by the number of hours those resources are active. This metric provides a comprehensive view of resource usage over time, allowing for accurate tracking, optimization, and billing based on actual compute needs. How It Works: - Memory allocation: Determine the amount of memory (in GB) that your application or function requires while running. - Duration: Measure the total time (in hours) that the allocated memory is in use. - Calculation: Multiply the memory allocated by the duration to obtain the total GB-hours consumed. **Example:** Assuming you have a function that requires 4 GB of memory to operate. If this function runs continuously for 2 hours, the compute resource usage would be: **4GB * 2 hours = 8 GB-hours** This means the function has consumed 8 GB-hours of compute resources. #### Pricing {% #pricing %} - Free plan includes up to 100 GB-hours of execution and build time per month. - Pro and Scale plans include up to 1,000GB of execution and build time per month. Additional usage is available at a rate of $0.09 per GB-hour. Once the monthly GB-hours limit is reached, additional usage will automatically apply add-ons to your Pro or Scale account. It is recommended to set budget alerts and a budget cap to prevent unexpected payments. --- ## Custom domains https://appwrite.io/docs/advanced/platform/custom-domains Appwrite custom domains allows you to use your own domain as your Appwrite API endpoint. ### Third-party cookies {% #third-party-cookies %} A recent change made in modern browsers will not allow your web app to use 3rd-party cookies. This change is done to protect your users' privacy from malicious web tracking services. When accessing Appwrite from a 3rd party domain, like `cloud.appwrite.io` or `example.com`, some browsers will treat our secure cookies as 3rd-party cookies and block them, as a fallback Appwrite will store your users' sessions on the browser localStorage. Using localStorage is very convenient to help you get started quickly with Appwrite, but it is not the best practice for your users' security. The browser localStorage can't protect your users' sessions from being hijacked by a 3rd party script or an XSS vulnerability in your web app. ### Appwrite API endpoint {% #endpoint %} To prevent your browser from blocking your cookies, your Appwrite API endpoint should be set to under same domain of your web app's domain. When accessing Appwrite from the same domain as the one your app uses, Appwrite cookies will no longer be treated as 3rd-party cookies by any browser and will store your users' sessions securely. For example, if your app runs on [my-app.com](https://my-app.com), you can set the subdomain [appwrite.my-app.com](https://appwrite.my-app.com) to access the Appwrite API. This will allow browsers to respect the Appwrite sessions cookies as they are set on the same domain as your app. ### Add a custom domain {% #domain %} 1. Go to the Appwrite Console and navigate to your project. 2. Click on the **Settings** tab in the left sidebar. 3. Select the **Custom domains** section and click **Create domain**. 4. Add your domain, and copy associated CNAME record to your DNS provider. See the [Add a CNAME record](#cname-record) section. 5. Verify your domain. DNS changes might take up to 48 hours to propagate worldwide, you may not be able to do this in the same day. 6. Once you verify your domain, you can generate an SSL certificate. With these steps, your Appwrite project will accept API requests from your custom domain. If you encounter any issues during the setup process or have questions, don't hesitate to [contact us](/contact-us), and we'll be happy to assist you. ### Add a CNAME record {% #cname-record %} A [CNAME record](https://en.wikipedia.org/wiki/CNAME_record) (or a Canonical Name record) is a type of resource record in the Domain Name System (DNS), which maps one domain name (an alias) to another. Every DNS host has its own way of updating DNS settings, and, unfortunately, their dashboard interfaces usually aren't the most intuitive. We recommend that you read the help documentation of your DNS host, also do not hesitate to contact their support for help with their interface and settings. Below, you'll find a list of registrars and links to their DNS setting documentation. If your domain provider isn't listed above, please [contact us](/contact-us), and we'll include their settings as well. | Provider | Documentation | | --------------- | -------- | | IONOS | [Settings](https://www.ionos.com/help/domains/dns-settings/) | | 101domain | [Settings](https://help.101domain.com/domain-management/name-servers-dns/modifying-name-servers-and-records/managing-name-server-records) | | 123 Reg | [A Record](https://www.123-reg.co.uk/support/domains/how-do-i-point-my-domain-name-to-an-ip-address/) / [CNAME Record](https://www.123-reg.co.uk/support/domains/how-do-i-set-up-a-cname-record-on-my-domain-name/) | | AWS Route 53 | [Settings](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-creating.html) | | Alfahosting | [Settings](https://alfahosting.de/antworten-auf-ihre-fragen/?cid=78#faqContent) | | Binero | [Settings](https://docs.binero.com/dns.html) | | Bluehost | [A Record](https://my.bluehost.com/hosting/help/whats-an-a-record) / [CNAME Record](https://my.bluehost.com/hosting/help/cname) / [Settings](https://my.bluehost.com/hosting/help/559) | | ClouDNS | [A Record](https://www.cloudns.net/wiki/article/10/) / [CNAME Record](https://www.cloudns.net/wiki/article/13/) | | Cloudflare | [Settings](https://support.cloudflare.com/hc/en-us/articles/360019093151) | | Crazydomains | [Settings](https://www.crazydomains.com.au/help/manage-dns-records-in-cpanel/) | | DNS Made Easy | [A Record](https://support.dnsmadeeasy.com/support/solutions/articles/47001024724-a-record) / [CNAME Record](https://support.dnsmadeeasy.com/support/solutions/articles/47001001393-cname-record) | | DNSimple | [A Record](https://support.dnsimple.com/articles/manage-a-record/) / [CNAME Record](https://support.dnsimple.com/articles/manage-cname-record/) | | DigitalOcean | [A Record](https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-dns#a-records) / [CNAME Record](https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-dns#cname-records) / [Settings](https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-dns) | | DreamHost | [A Record](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-#A_record) / [CNAME Record](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-#CNAME_record) | | Freeparking | [Settings](https://www.freeparking.co.nz/help/manage-dns-records-in-freeparking-dashboard) | | Gandi | [A Record](https://wiki.gandi.net/en/dns/zone/a-record) / [CNAME Record](https://wiki.gandi.net/en/dns/zone/cname-record) | | Godaddy | [A Record](https://www.godaddy.com/help/add-an-a-record-19238) / [CNAME Record](https://www.godaddy.com/help/add-a-cname-record-19236) | | Google Domains | [A Record](https://support.google.com/a/answer/2579934?hl=en&ref_topic=2721296) / [CNAME Record](https://support.google.com/a/answer/47283?hl=en) | | Host Europe | [Settings](https://www.hosteurope.de/faq/domains/verwaltung/nameserver-eintraege/) | | Hover | [Settings](https://help.hover.com/hc/en-us/articles/217282457-Managing-DNS-records) | | Hostinger | [Settings](https://support.hostinger.com/en/articles/1583249-how-to-manage-dns-records-at-hostinger) | | Infomaniak | [Settings](https://www.infomaniak.com/en/support/faq/2000/change-dns-zone-simple-mode) | | InMotion Hosting| [Settings](https://www.inmotionhosting.com/support/product-guides/wordpress-hosting/central/domains/dns-management/) / [CNAME Record](https://www.inmotionhosting.com/support/domain-names/create-cname-record/) | | Internet.bs | [Settings](https://faq.internetbs.net/hc/en-gb/sections/360004926357-DNS-Nameservers) | | LeaseWeb | [Settings](https://kb.leaseweb.com/products/hosting/domain-name) | | LCN.com | [Settings](https://www.lcn.com/support/articles/how-to-manage-dns-settings-in-cpanel/) | | Loopia | [Settings](https://support.loopia.com/wiki/dns-editor-a-and-cname-2/) | | Media Temple | [Settings](https://mediatemple.zendesk.com/hc/en-us/articles/204403794-How-can-I-change-the-DNS-records-for-my-domain) | | Namecheap | [A Record](https://www.namecheap.com/support/knowledgebase/article.aspx/319/78/how-can-i-set-up-an-a-address-record-for-my-domain) / [CNAME Record](https://www.namecheap.com/support/knowledgebase/article.aspx/9256/2208/how-can-i-set-up-a-cname-record-for-my-domain) | | Namesilo | [A Record](https://www.namesilo.com/Support/DNS-Manager) / [CNAME Record](https://www.namesilo.com/Support/DNS-Manager) | | Network Solutions | [A Record](https://customerservice.networksolutions.com/prweb/PRAuth/webkm/help/article/manage-dns-adns-records) / [CNAME Record](https://customerservice.networksolutions.com/prweb/PRAuth/webkm/help/article/manage-dns-adns-records) | | One.com | [Settings](https://help.one.com/hc/en-us/articles/115005595925-Manage-your-DNS-settings) | | OVH | [Settings](https://help.ovhcloud.com/csm/en-dns-edit-dns-zone?id=kb_article_view&sysparm_article=KB0051682) | | Porkbun | [A Record](https://kb.porkbun.com/article/54-pointing-your-domain-to-hosting-with-a-records) / [CNAME Record](https://kb.porkbun.com/article/68-how-to-edit-dns-records) | | Register.it | [Settings](https://www.register.it/assistenza/cambiare-dns/) | | SiteGround | [Settings](https://www.siteground.com/kb/manage-dns-records/) | | United Domains | [A Record](https://www.uniteddomains.com/faq/question/11) / [CNAME Record](https://www.uniteddomains.com/faq/question/14)| | Vercel | [Settings](https://vercel.com/docs/custom-domains) | | Wix | [Settings](https://support.wix.com/en/article/connecting-a-wix-domain-to-an-external-site) | | Yahoo Small Business | [A Record](https://help.turbify.com/s/article/how-do-i-add-edit-and-delete-an-a-record) / [CNAME Record](https://help.turbify.com/s/article/how-do-i-add-edit-and-delete-a-cname-record) | DNS changes might take up to 48 hours to propagate worldwide. This means that it might take up to two days for your new domain to become accessible using Appwrite. For debugging, you can try using [this online tool](https://dnschecker.org/) to check your DNS propagation status. In addition to the DNS setup, you might also want to update the "Allowed Domains" section in your Appwrite project settings. By default, Appwrite only allows API calls from localhost, appwrite.io, and your project's default custom domains. You can add your custom domain to this list to ensure that API requests from your domain are accepted. --- ## Database Reads and Writes https://appwrite.io/docs/advanced/platform/database-reads-and-writes {% info title="Note" %} Updated pricing will take effect on April 10th, 2025. Check out this [blog post](/blog/post/announcing-database-reads-and-writes-pricing) for more information. {% /info %} Appwrite provides powerful database capabilities through its [Database API](/docs/products/databases), allowing you to perform read and write operations across your application data. Understanding how these operations are counted and billed is essential for planning your application's scalability. #### Database Operations Database operations in Appwrite are categorized into two types: **Read Operations**: Any action that retrieves data from your database, including: - Fetching rows with `getRow` or `listRows`. **Write Operations**: Any action that modifies data in your database, including: - Creating rows with `createRow`. - Updating rows with `updateRow`. - Deleting rows with `deleteRow`. How it works: 1. Perform database operations through the Appwrite SDK or API. 2. Appwrite automatically tracks and logs these operations. 3. Operations are counted based on the number of rows affected, not API calls. 4. Quotas are refreshed monthly based on your subscription plan. For example, if you fetch a table of 50 rows with a single API call, this counts as 50 read operations, not as a single operation. If your query returns no rows, this counts as a single operation. #### Pricing ##### Free Plan - **Included**: 500,000 read operations and 250,000 write operations per month. - **Overage**: Not available (operations are throttled when limits are reached). ##### Pro and Scale Plans - **Included**: 1,750,000 read operations and 750,000 write operations per month. - **Overage**: $0.060 per 100,000 additional read operations and $0.10 per 100,000 additional write operations. ##### Enterprise Plan - **Included**: Unlimited read and write operations. - **Overage**: Not applicable. For detailed information about the different pricing options and features, please visit the [pricing page](/pricing). #### Best Practices To optimize your database operations and control costs: 1. **Use efficient queries**: Filter data on the server side rather than retrieving large datasets and filtering client-side. 2. **Implement pagination**: Use the `limit` and `offset` parameters to retrieve only the data you need. 3. **Monitor usage**: Keep track of your database operations through the Appwrite Console. 4. **Consider caching**: Cache frequently accessed data to reduce repeated read operations. --- ## Dev keys https://appwrite.io/docs/advanced/platform/dev-keys Dev keys are secrets used by Appwrite [Client SDKs](/docs/sdks#client) to avoid abuse limits in testing. They are meant to be used specifically in development environments, where they hold several developer experience-related benefits: - Appwrite rate limits and CORS errors are bypassed - Configurable expiration date with 1 day, 7 days, and 30 day options This is highly beneficial in scenarios where you are repeatedly sending the same requests to Appwrite in a short period of time, such as manual or E2E testing and checks in your CI/CD pipeline. {% info title="Important note" %} Dev keys should never be used in production environments, only in development environments, as they can make your app more susceptible to abuse and security breaches. {% /info %} ### Dev keys vs API keys {% #dev-keys-vs-api-keys %} Dev keys and [API keys](/docs/advanced/platform/api-keys) are not the same and cannot be used interchangeably. Dev keys are specifically designed to help you avoid abuse limits and CORS errors in test environments, making them ideal for development and testing workflows. API keys, on the other hand, permit usage of Appwrite services in production environments with fine-grained scope control. Dev keys are for client SDKs in development environments, while API keys are for server SDKs and the CLI in production environments. ### Create dev key {% #create-dev-key %} To create a new dev key, navigate to **Overview** > **Integrations** > **Dev keys** and click **Create Dev key**. {% only_dark %} ![Create dev key](/images/docs/dev-keys/dark.png) {% /only_dark %} {% only_light %} ![Create dev key](/images/docs/dev-keys/light.png) {% /only_light %} You can then implement the dev key while initializing the Appwrite client in your app. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setDevKey(''); // Your dev key ``` ```client-flutter import 'package:appwrite/appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setDevKey(''); // Your dev key ``` ```client-react-native import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setDevKey(''); // Your dev key ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") .setDevKey("") // Your dev key ``` ```client-android-kotlin import io.appwrite.Client val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") .setDevKey("") // Your dev key ``` ```client-android-java import io.appwrite.Client; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setDevKey(""); // Your dev key ``` {% /multicode %} If you need to replace your dev key, create a new key, update your app credentials and, once ready, delete your old key. --- ## Enterprise https://appwrite.io/docs/advanced/platform/enterprise Enterprise development teams face unique challenges and have unique needs. Appwrite can provide tailored solutions for enterprise customers with custom hosting, training, and support needs. If you're interested to learn about what Appwrite can do for your enterprise development teams, [contact us](https://appwrite.io/contact-us/enterprise) for more details. --- ## Error handling https://appwrite.io/docs/advanced/platform/error-handling When integrating Appwrite into your applications, proper error handling is important for delivering a good user experience while still being able to troubleshoot issues effectively. ### Consider user-friendly error messages **It's generally best to avoid returning Appwrite's raw error messages directly to your users.** These messages are designed for developers and may contain technical details that: - Could confuse non-technical users - Might expose implementation details - Often create an inconsistent user experience Instead, consider this approach: 1. Catch errors from Appwrite 2. Identify the error type 3. Return a user-friendly message appropriate for your application ### Use error types for better handling Appwrite returns not just HTTP status codes but also specific error types that provide more precise information about what went wrong. These error types are available in the `type` field of the error response: ```json { "message": "Invalid credentials. Please check the email and password.", "type": "user_invalid_credentials", "code": 401 } ``` By checking the error type rather than just the status code, you can create more precise error handling logic. This approach allows you to: - Display contextual error messages based on what actually went wrong - Implement different recovery strategies for different error types - Log specific errors for debugging while showing friendly messages to users - Handle temporary issues (like network problems) differently from permanent ones ### Common error types and suggested messages Here are some examples of how you might map Appwrite error types to user-friendly messages: | Error Type | Technical Meaning | User-Friendly Message | |------------|-------------------|----------------------| | `user_invalid_credentials` | Invalid credentials. Please check the email and password. | "The email or password you entered is incorrect. Please try again." | | `user_blocked` | The current user has been blocked. | "Your account has been temporarily suspended. Please contact support." | | `general_rate_limit_exceeded` | Rate limit for the current endpoint has been exceeded. | "Please wait a moment before trying again." | | `storage_file_not_found` | The requested file could not be found. | "The file you requested is not available." | | `row_not_found` | Row with the requested ID could not be found. | "The information you're looking for could not be found." | ### Complete list of error types Appwrite provides a comprehensive list of error types organized by category. For a complete reference, see the [Response codes](/docs/advanced/platform/response-codes#error-types) documentation. ### Recommended practices - **Log the actual errors server-side** for debugging while returning friendly messages to users - **Handle common error scenarios** with specific user guidance - **Provide clear next steps** when possible (e.g., "Try resetting your password") - **Use consistent error message styling** throughout your application - **Consider different error categories** (validation errors, authentication issues, server problems) - **Consider implementing retry logic** for transient errors like rate limiting (429) or service unavailability (503) By implementing thoughtful error handling, you improve both the user experience of your application and your ability to troubleshoot issues effectively. --- ## Events https://appwrite.io/docs/advanced/platform/events Appwrite provides a variety of events that allows your application to react to changes as they happen. An event will fire when a change occurs in your Appwrite project, like when a new user registers or a new file is uploaded to Appwrite. You can subscribe to these events with Appwrite [Functions](/docs/products/functions), [Realtime](/docs/apis/realtime), or [Webhooks](/docs/advanced/platform/webhooks). You can subscribe to events for specific resources using their ID or subscribe to changes of all resources of the same type by using a wildcard character * instead of an ID. You can also filter for events of specific actions like create, update, upsert, or delete. You can find a list of events for Storage, Databases, Functions, and Authentication services below. {% accordion %} {% accordion_item title="Authentication" %} {% partial file="auth-events.md" /%} {% /accordion_item %} {% accordion_item title="Databases" %} {% partial file="databases-events.md" /%} {% /accordion_item %} {% accordion_item title="Storage" %} {% partial file="storage-events.md" /%} {% /accordion_item %} {% accordion_item title="Functions" %} {% partial file="functions-events.md" /%} {% /accordion_item %} {% accordion_item title="Messaging" %} {% partial file="messaging-events.md" /%} {% /accordion_item %} {% /accordion %} ### Known limitations {% #known-limitations %} When events fire, only existing subscriptions for that event will receive the update. If your client or server side integrations lose network connection temporarily, delivery of the event is not guaranteed. For self-hosted instances, when the Appwrite containers are shut down and restarted, events with pending webhooks and subscription updates will not be delivered. A change to a resource can cause multiple events to fire. For example adding a new row with ID `"lion-king"` to a table with the ID `"movies"` will cause all of the below events to fire. ```json { "events": [ "databases.default.tables.movies.rows.lion-king.create", "databases.*.tables.*.rows.*.create", "databases.default.tables.*.rows.lion-king.create", "databases.*.tables.*.rows.lion-king.create", "databases.*.tables.movies.rows.lion-king.create", "databases.default.tables.movies.rows.*.create", "databases.*.tables.movies.rows.*.create", "databases.default.tables.*.rows.*.create", "databases.default.tables.movies.rows.lion-king", "databases.*.tables.*.rows.*", "databases.default.tables.*.rows.lion-king", "databases.*.tables.*.rows.lion-king", "databases.*.tables.movies.rows.lion-king", "databases.default.tables.movies.rows.*", "databases.*.tables.movies.rows.*", "databases.default.tables.*.rows.*", "databases.default.tables.movies", "databases.*.tables.*", "databases.default.tables.*", "databases.*.tables.movies", "databases.default", "databases.*" ] } ``` --- ## Fair use policy https://appwrite.io/docs/advanced/platform/fair-use-policy At Appwrite, we are committed to providing high-quality, reliable, and scalable backend services for all users. Our Fair Use Policy ensures that resources are used responsibly and that every user receives a consistent experience. This policy applies to all users and outlines acceptable usage patterns and limitations. ### Definitions and scope {% #definitions-and-scope %} - **Normal usage:** Resource usage that falls within expected thresholds for a user's selected plan. - **Excessive usage:** Usage that exceeds defined thresholds and may affect the platform's performance for other users. - **Prohibited activities:** Actions or behaviors that violate this policy, including, but not limited to, abuse of resources and security breaches. This policy applies to all resources provided by Appwrite Cloud, including but not limited to API requests, data storage, compute resources, and database operations. ### Usage limits and thresholds {% #usage-limits-and-thresholds %} Each user plan includes specific resource limits, which are available to view on our [Pricing page](/pricing). Exceeding these limits may lead to throttling or suspension of services, as described under [Enforcement actions](#enforcement-actions). {% partial file="prohibited-activities.md" /%} ### Enforcement actions {% #enforcement-actions %} In cases of excessive or prohibited usage, Appwrite reserves the right to enforce the following actions: - **Temporary suspension** of access to the project or organization. - **Permanent suspension** of the account for repeated or serious policy violations. In all cases, users will be notified of the action taken and provided with guidance on how to correct the issue. ### Monitoring and compliance {% #monitoring-and-compliance %} We continuously monitor resource usage to ensure compliance with this policy. Users will receive automated alerts when nearing their plan limits and may review their usage statistics in the [Console](https://cloud.appwrite.io/) at any time. If users believe their usage has been unfairly flagged, they can contact support to discuss and resolve the issue. ### Options for users exceeding limits {% #options-for-users-exceeding-limits %} Users who consistently exceed usage limits have the following options: - **Upgrade to a higher plan:** Users may select a plan that better fits their needs. - **Resource upgrades:** Pro and Scale plan users may upgrade the usage limits for specific resources. Pricing details are available [here](/pricing). ### Policy updates {% #policy-updates %} Appwrite reserves the right to update this policy to reflect evolving platform capabilities or changing usage patterns at any time. ### Contact us {% #contact-us %} For questions or further clarification, please [contact our support team](/contact-us). --- ## Free https://appwrite.io/docs/advanced/platform/free Appwrite Cloud provides a **Free** plan to all developers to start building with Appwrite. Appwrite Free plan is perfect for personal hobby projects for students and professional developers alike. Learn more about the Free plan's generous resource limits on the [pricing page](https://appwrite.io/pricing). ### Create a Free plan organization {% #create-a-free-plan-organizations %} Appwrite Cloud's different plans are applied at an organization level. Resources on the Free plan are shared across projects, while paid plans offer dedicated resources per project. When you create your Appwrite Cloud account, a **Personal Projects** organization using the Free plan is automatically created. Each Appwrite Cloud account can only have one organization and 2 projects on the **Free** plan. ### Resource limits {% #resource-limits %} Each plan in Appwrite Cloud has resource limits. The details of these resource limits are on the [pricing page](https://appwrite.io/pricing). Each resource limit is applied per billing period and resets at the beginning of each billing period. You cannot purchase additional resources under the Free plan. {% partial file="resource-limits.md" /%} --- ## Image Transformations https://appwrite.io/docs/advanced/platform/image-transformations {% info title="Note" %} Changes will take effect on April 1st, 2025. Check out this [blog post](/blog/post/announcing-image-transformations-pricing) for more information. {% /info %} Appwrite enables the transformation of images before retrieval using the [getFilePreview](/docs/references/cloud/client-web/storage#getFilePreview) endpoint. This functionality supports resizing images by width and height, adjusting quality, and applying filters such as opacity, border colour, border radius, and more. ##### Origin Image An "origin image" represents the original, unmodified image file in Appwrite Storage. Each origin image serves as the base for unlimited transformations, allowing the creation of multiple variants without incurring additional origin image charges. How it works: 1. Upload an image to Appwrite Storage 2. Use the `getFilePreview` method to apply transformations to the image 3. Retrieve transformed images through the preview endpoint 4. Pay only for unique origin images, regardless of transformation count For example, suppose there are around 100 images in storage. If only 50 of these images undergo transformations, but transformations are applied around 200 times, the origin image transformations are only 50 and **not 200**. #### Pricing - Currently, this feature is **unavailable on the free plan**. - Pro and Scale plans include 100 origin images per month. Additional usage is available at $5 per 1000 origin images. For detailed information about the different pricing options and features, please visit the [pricing page](/pricing). --- ## Message templates https://appwrite.io/docs/advanced/platform/message-templates Appwrite uses emails to communicate with users to perform authentication and verification actions. Emails can be customized to fit your app's design and voice. Each Appwrite project can have its own set of unique templates. Templates also support localization, so every template can be written in multiple languages and served depending on the configured locale. ### Custom SMTP server {% #smtp %} Appwrite Cloud has a default SMTP server to get you started. This SMTP server sends generic emails and doesn't allow customizing SMTP templates. To use custom SMTP templates, you will need to configure your own SMTP server. There are many third-party SMTP providers like SendGrid and Mailgun. Before proceeding, pick an SMTP provider, create an account, and obtain **Sender name**, **Sender email**, **Server host**, **Server port**, **Username**, and **Password**. 1. Navigate to your project's **Settings**. 1. Navigate to the **SMTP** tab. 1. Under **SMTP server**, toggle **Custom SMTP server**. 1. Input **Sender name**, **Sender email**, **Server host**, **Server port**, **Username**, and **Password** from your provider. 1. Click **Update**. ### Customize templates {% #customize %} You can customize email templates for each of your projects in the Appwrite Console. {% info title="Custom SMTP server required" %} The built-in email service does not support custom email templates to prevent malicious templates. Configure a [custom SMTP server](#smtp) to enable custom email templates. {% /info %} 1. In your project, navigate to the **Auth** service. 1. Under the **Auth** service, navigate to the **Templates** tab. 1. Expand the email template you want to edit. 1. Select the **Template language**. You can have a different template for each language your app supports. 1. Update the email template fields and click **Update** to save your changes. ### Email templates {% #email-templates %} You can customize the email templates for account verification, magic-url authentication, password resets, and user invites. #### Email template components {% #email-template-components %} Each email template has the following components that you can customize. | Component | Description | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sender name | Readers will see this as a display name of the sender. | | Sender email | Readers will see this as a display email of the sender. This email must be authenticated on the SMTP provider you've configured, otherwise it will be delivered to the spam folder. This usually means the email must end with the same domain as your SMTP username. | | Reply to | Readers will reply to this email address instead of the sender address. You can leave this field empty, and the sender email will be used automatically. | | Subject | The title of the email. | | Message | The body of the email in HTML format. You can find the variables available in the [Email Template Syntax](#email-template-syntax) section. | #### Email template syntax {% #email-template-syntax %} Variables can be used in email templates to dynamically construct unique emails for each reader. These variables can only be used in the **Message** field of the email template. | Variable | Description | | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `{{project}}` | The project name. | | `{{team}}` | The project team's name. | | `{{user}}` | The name of the user receiving the email. This variable is not available in the Magic URL template, as there might not be a user yet. | | `{{redirect}}` | The URL for the user to complete the email template's action. | #### Email template syntax {% #email-template-examples %} Here's an example of using these variables in a template. ```html

{{subject}}

Hello

Follow this link to reset your {{project}} password.

{{redirect}}


If you didn't ask to reset your password, you can ignore this message.


Thanks
{{project}} team

``` ### Localization {% #localization %} Each template can have multiple supported locales, displayed in different format and language. This can be configured under the **Template language** selector of each template. You can send messages in different languages by setting the locale with `client.setLocale()` in the SDKs or the `X-Appwrite-Locale` HTTP header. [View here the list of available locales](https://github.com/appwrite/appwrite/blob/master/app/config/locale/codes.php). For example, you can send an email verification in French. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client(); const account = new Account(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setLocale('fr') // Your locale ; const promise = account.createVerification({ url: 'https://example.com' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Account account = Account(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setLocale('fr') // Your locale ; Future result = account.createVerification('https://example.com'); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setLocale('fr') // Your locale val account = Account(client) val response = account.createVerification('https://example.com') ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setLocale('fr') // Your locale let account = Account(client) let token = try await account.createVerification('https://example.com') ``` {% /multicode %} --- ## Open source https://appwrite.io/docs/advanced/platform/oss Appwrite remains open source and continues to support open-source maintainers that build fundamental software that modern developers depend upon with the OSS Program. The OSS Program supports open-projects and their maintainers by alleviating financial burdens and promoting growth. You will receive a free Appwrite Pro subscription and benefit from all its resources and support. The program has no fixed end date but will be reviewed annually to ensure optimal mutual support. ### Criteria {% #criteria %} To apply for this program, you must adhere to the official criteria for having an open-source project, which is integral to being an acknowledged open-source maintainer. In addition, we have Appwrite criteria that will apply. You will need to provide the following information: - Your project needs to meet the criteria of the [Open Source Initiative definition](https://opensource.org/osd/) - Have obtained an approved [OSS license](https://opensource.org/licenses/) - Have an open-source GitHub repository - Have an active project that has at least 15 contributions and 100 stars - Have a nonprofit or pre-revenue project ### Application {% #application %} If your open-source project has outgrown our free Free plan and you adhere to the criteria, you can apply for a free Pro plan by filling out our [OSS contact form](https://appwrite.io/oss-program). We will review your request as soon as possible and get back to you to go over your application. Please note that Appwrite holds the sole discretion on deciding whether to accept projects. --- ## Permissions https://appwrite.io/docs/advanced/platform/permissions Appwrite's permission mechanism offers a simple, yet flexible way to manage which users, teams, or roles can access a specific resource in your project, such as rows and files. Using permissions, you can decide that only **user A** and **user B** will have read and update access to a specific database row, while **user C** and **team X** will be the only ones with delete access. As the name suggests, read permission allows a user to read a resource, create allows users to create new resources, update allows a user to make changes to a resource, and delete allows the user to remove the resource. All permissions can be granted to individuals or groups of users, entire teams, or only to team members with a specific role. Permission can also be granted based on authentication status, such as to all users, only authenticated users, or only guest users. A project user can only grant permissions to a resource that they have. For example, if a user is trying to share a row with a team that they are not a member of, they will encounter a 401 not authorized error. If your app needs users to grant access to teams they're not a member of, you can create Appwrite Functions with a [Server SDK](/docs/sdks#server) to achieve this functionality. ### Appwrite resource {% #appwrite-resource %} An Appwrite resource can be a database, table, row, bucket, or file. Each resource has its own set of permissions to define who can interact with it. Using the Appwrite permissions mechanism, you can grant resource access to users, teams, and members with different roles. ### Default values {% #default-values %} If you create a resource using a Server SDK or the Appwrite Console without explicit permissions, no one can access it by default because the permissions will be empty. If you create a resource using a Client SDK without explicit permissions, the creator will be granted read, update, and delete permissions on that resource by default. ### Server integration {% #server-integration %} Server integrations can be used for increased flexibility. When using a Server SDK in combination with the proper [API key scopes](/docs/advanced/platform/api-keys#scopes), you can have any type of access to any of your project resources regardless of their permissions. Using the server integration flexibility, you can change resource permissions, share resources between different users and teams, or edit and delete them without any limitations. ### Permission types {% #permission-types %} In Client and Server SDKs, you will find a **Permission** class with helper methods for each role described below: | Type | Description | | ---- | ----------- | | `Permission.read()` | Access to read a resource. | | `Permission.create()` | Access to create new resources. Does not apply to files or rows. Applying this type of access to files or rows results in an error. | | `Permission.update()` | Access to change a resource, but not remove or create new resources. Does not apply to functions. | | `Permission.delete()` | Access to remove a resource. Does not apply to functions. | | `Permission.write()` | Alias to grant create, update, and delete access for tables and buckets and update and delete access for rows and files. | ### Permission roles {% #permission-roles %} In Client and Server SDKs, you will find a **Role** class with helper methods for each role described below: | Type | Description | | ---- | ----------- | | `Role.any()` | Grants access to anyone. | | `Role.guests()` | Grants access to any guest user without a session. Authenticated users don't have access to this role. | | `Role.users([STATUS])` | Grants access to any authenticated or anonymous user. You can optionally pass the **verified** or **unverified** string to target specific types of users. | | `Role.user([USER_ID], [STATUS])` | Grants access to a specific user by user ID. You can optionally pass the **verified** or **unverified** string to target specific types of users. | | `Role.team([TEAM_ID])` | Grants access to any member of the specific team. To gain access to this permission, the user must be the team creator (owner), or receive and accept an invitation to join this team. | | `Role.team([TEAM_ID], [ROLE])` | Grants access to any member who possesses a specific role in a team. To gain access to this permission, the user must be a member of the specific team and have the given role assigned to them. Team roles can be assigned when inviting a user to become a team member. | | `Role.member([MEMBERSHIP_ID])` | Grants access to a specific member of a team. When the member is removed from the team, they will no longer have access. | | `Role.label([LABEL_ID])` | Grants access to all accounts with a specific label ID. Once the label is removed from the user, they will no longer have access. [Learn more about labels](/docs/products/auth/labels). | ### Examples {% #examples %} The examples below will show you how you can use the different Appwrite permissions to manage access control to your project resources. The following examples are using the [Appwrite Web SDK](https://github.com/appwrite/sdk-for-web) but can be applied similarly to any of the other [Appwrite SDKs](/docs/sdks). #### Example 1 - Basic usage {% #example-1-basic-usage %} In the following example, we are creating a row that can be read by anyone, edited by writers or admins, and deleted by administrators or a user with the user ID `user:5c1f88b42259e`. ```client-web import { Client, TablesDB, Permission, Role } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); let promise = tablesDB.createRow( '', '', {'actorName': 'Chris Evans', 'height': 183}, [ Permission.read(Role.any()), // Anyone can view this row Permission.update(Role.team("writers")), // Writers can update this row Permission.update(Role.team("admin")), // Admins can update this row Permission.delete(Role.user("5c1f88b42259e")), // User 5c1f88b42259e can delete this row Permission.delete(Role.team("admin")) // Admins can delete this row ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` #### Example 2 - Team roles {% #example-2-team-roles %} In the following example, we are creating a row that can be read by members of the team with ID `5c1f88b87435e` and can only be edited or deleted by members of the same team that possess the team role `owner`. ```client-web import { Client, TablesDB, Permission, Role } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); let promise = tablesDB.createRow( '', '', {'actorName': 'Chris Evans', 'height': 183}, [ Permission.read(Role.team("5c1f88b87435e")), // Only users of team 5c1f88b87435e can read the row Permission.update(Role.team("5c1f88b87435e", "owner")), // Only users of team 5c1f88b87435e with the role owner can update the row Permission.delete(Role.team("5c1f88b87435e", "owner")) // Only users of team 5c1f88b87435e with the role owner can delete the row ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` #### Example 3 - Private rows {% #example-3-private-rows %} A common use case is to allow users to create rows that are only accessible to them. Here's how this can be achieved: ##### Configure the table First, configure your table to: 1. Enable **Row Security** in Table **Settings** 2. Grant only **CREATE** permission to **all users** at the table level {% info title="Why this setup?" %} - **Row Security** enables per-row permissions - Table-level **CREATE** permission allows users to create rows - Omitting **READ/UPDATE/DELETE** at table level prevents users from accessing all rows {% /info %} ##### Create a row for a user When creating rows in your application, set row-level permissions to restrict access to only the creator: {% multicode %} ```client-web import { Client, TablesDB, Permission, Role } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); let promise = tablesDB.createRow( '', '', { 'title': 'My Private Row' }, [ Permission.read(Role.user('')), // Only this user can read Permission.update(Role.user('')), // Only this user can update Permission.delete(Role.user('')) // Only this user can delete ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final row = await tablesDB.createRow( databaseId: '', tableId: '', data: { 'title': 'My Private Row' }, permissions: [ Permission.read(Role.user('')), // Only this user can read Permission.update(Role.user('')), // Only this user can update Permission.delete(Role.user('')) // Only this user can delete ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject(""); let tablesDB = TablesDB(client); do { let row = try await tablesDB.createRow( databaseId: "", tableId: "", data: ["title": "My Private Row"], permissions: [ Permission.read(Role.user("")), // Only this user can read Permission.update(Role.user("")), // Only this user can update Permission.delete(Role.user("")) // Only this user can delete ] ); } catch { print(error.localizedDescription); } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Permission import io.appwrite.Role import io.appwrite.services.TablesDB import io.appwrite.exceptions.AppwriteException suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://cloud.appwrite.io/v1") .setProject(""); val tablesDB = TablesDB(client); try { val row = tablesDB.createRow( databaseId = "", tableId = "", data = mapOf("title" to "My Private Row"), permissions = listOf( Permission.read(Role.user("")), // Only this user can read Permission.update(Role.user("")), // Only this user can update Permission.delete(Role.user("")) // Only this user can delete ) ); } catch (e: AppwriteException) { Log.e("Appwrite", e.message); } } ``` {% /multicode %} {% info title="Understanding the flow" %} 1. Table-level **CREATE** permission allows users to create new rows 2. When a row is created, we set permissions for only the creator 3. These row-level permissions ensure only the creator can read, update, or delete their rows 4. Other users can create their own rows but cannot access rows they didn't create {% /info %} --- ## Phone OTP https://appwrite.io/docs/advanced/platform/phone-otp {% info title="Note" %} Changes will take effect on February 10th, 2025. Check out this [blog post](https://appwrite.io/blog/post/announcing-phone-OTP-pricing) for more information. {% /info %} Appwrite supports SMS-based OTP (One-Time Password) authentication to provide secure and reliable user verification. This feature enhances your app's security by adding an extra layer of authentication. ### Free messages {% #free-messages %} All paid Appwrite plans include **10 free SMS messages** per month, which allows you to test and implement OTP functionality without immediate costs. In addition you can also use the [Mock phone numbers](/docs/products/auth/security#mock-phone-numbers) feature to continue testing your integrations without incurring additional costs. ### Additional messages {% #additional-messages %} After the first 10 free SMS messages, you'll be charged per SMS. The cost for additional messages is calculated based on two factors: 1. The number of messages sent 2. The destination country of each message As part of our commitment to making Appwrite as accessible as possible, we regularly collaborate with telecom providers to negotiate lower SMS rates. Our goal is to keep costs affordable for all users. However, due to the unique pricing structures of each vendor, our rates may fluctuate from time to time. #### Rates {% #rates %} SMS rates vary by country due to differences in telecom infrastructure and regulations. Here is a breakdown of the rates: | Country code | Country name | Price / SMS (USD) | |--------------|-------------------------------------|-------------------| | +213 | Algeria | $ 0.23 | | +376 | Andorra | $ 0.09 | | +244 | Angola | $ 0.07 | | +54 | Argentina | $ 0.06 | | +374 | Armenia | $ 0.11 | | +297 | Aruba | $ 0.06 | | +61 | Australia | $ 0.03 | | +43 | Austria | $ 0.03 | | +994 | Azerbaijan | $ 0.22 | | +973 | Bahrain | $ 0.04 | | +880 | Bangladesh | $ 0.25 | | +375 | Belarus | $ 0.16 | | +32 | Belgium | $ 0.09 | | +501 | Belize | $ 0.21 | | +229 | Benin | $ 0.12 | | +975 | Bhutan | $ 0.18 | | +591 | Bolivia | $ 0.06 | | +387 | Bosnia and Herzegovina | $ 0.06 | | +267 | Botswana | $ 0.03 | | +55 | Brazil | $ 0.03 | | +673 | Brunei | $ 0.03 | | +359 | Bulgaria | $ 0.12 | | +226 | Burkina Faso | $ 0.09 | | +257 | Burundi | $ 0.18 | | +855 | Cambodia | $ 0.17 | | +237 | Cameroon | $ 0.10 | | +238 | Cape Verde Islands | $ 0.12 | | +56 | Chile | $ 0.05 | | +86 | China | $ 0.02 | | +57 | Colombia | $ 0.04 | | +269 | Comoros and Mayotte | $ 0.10 | | +242 | Congo | $ 0.10 | | +682 | Cook Islands | $ 0.04 | | +506 | Costa Rica | $ 0.01 | | +385 | Croatia | $ 0.05 | | +53 | Cuba | $ 0.08 | | +357 | Cyprus | $ 0.01 | | +420 | Czech Republic | $ 0.06 | | +45 | Denmark | $ 0.05 | | +253 | Djibouti | $ 0.10 | | +593 | Ecuador | $ 0.22 | | +20 | Egypt | $ 0.23 | | +503 | El Salvador | $ 0.05 | | +240 | Equatorial Guinea | $ 0.09 | | +291 | Eritrea | $ 0.15 | | +372 | Estonia | $ 0.05 | | +251 | Ethiopia | $ 0.24 | | +500 | Falkland Islands | $ 0.04 | | +298 | Faroe Islands | $ 0.03 | | +679 | Fiji | $ 0.12 | | +358 | Finland | $ 0.06 | | +33 | France | $ 0.07 | | +594 | French Guiana | $ 0.10 | | +689 | French Polynesia | $ 0.10 | | +241 | Gabon | $ 0.12 | | +220 | Gambia | $ 0.09 | | +995 | Georgia | $ 0.13 | | +49 | Germany | $ 0.09 | | +233 | Ghana | $ 0.19 | | +350 | Gibraltar | $ 0.06 | | +30 | Greece | $ 0.05 | | +299 | Greenland | $ 0.03 | | +590 | Guadeloupe | $ 0.07 | | +1671 | Guam | $ 0.03 | | +502 | Guatemala | $ 0.05 | | +224 | Guinea | $ 0.11 | | +245 | Guinea-Bissau | $ 0.08 | | +592 | Guyana | $ 0.10 | | +509 | Haiti | $ 0.09 | | +504 | Honduras | $ 0.04 | | +852 | Hong Kong | $ 0.05 | | +36 | Hungary | $ 0.07 | | +354 | Iceland | $ 0.07 | | +91 | India | $ 0.003 | | +62 | Indonesia | $ 0.28 | | +98 | Iran | $ 0.20 | | +964 | Iraq | $ 0.23 | | +353 | Ireland | $ 0.06 | | +972 | Israel | $ 0.01 | | +39 | Italy | $ 0.05 | | +81 | Japan | $ 0.06 | | +962 | Jordan | $ 0.25 | | +254 | Kenya | $ 0.25 | | +686 | Kiribati | $ 0.07 | | +965 | Kuwait | $ 0.20 | | +996 | Kyrgyzstan | $ 0.25 | | +856 | Laos | $ 0.17 | | +371 | Latvia | $ 0.04 | | +961 | Lebanon | $ 0.21 | | +266 | Lesotho | $ 0.11 | | +231 | Liberia | $ 0.08 | | +218 | Libya | $ 0.24 | | +423 | Liechtenstein | $ 0.04 | | +370 | Lithuania | $ 0.04 | | +352 | Luxembourg | $ 0.09 | | +853 | Macao | $ 0.03 | | +389 | Macedonia | $ 0.03 | | +261 | Madagascar | $ 0.12 | | +265 | Malawi | $ 0.09 | | +60 | Malaysia | $ 0.18 | | +960 | Maldives | $ 0.17 | | +223 | Mali | $ 0.13 | | +356 | Malta | $ 0.05 | | +692 | Marshall Islands | $ 0.03 | | +596 | Martinique | $ 0.09 | | +222 | Mauritania | $ 0.17 | | +52 | Mexico | $ 0.07 | | +691 | Micronesia | $ 0.03 | | +373 | Moldova | $ 0.07 | | +377 | Monaco | $ 0.09 | | +976 | Mongolia | $ 0.11 | | +212 | Morocco | $ 0.20 | | +258 | Mozambique | $ 0.10 | | +95 | Myanmar | $ 0.15 | | +264 | Namibia | $ 0.05 | | +674 | Nauru | $ 0.07 | | +977 | Nepal | $ 0.24 | | +31 | Netherlands | $ 0.10 | | +687 | New Caledonia | $ 0.08 | | +64 | New Zealand | $ 0.07 | | +505 | Nicaragua | $ 0.09 | | +227 | Niger | $ 0.10 | | +234 | Nigeria | $ 0.35 | | +683 | Niue | $ 0.04 | | +672 | Norfolk Islands | $ 0.05 | | +1 | North America | $ 0.007 | | +850 | North Korea | $ 0.02 | | +1670 | Northern Mariana Islands | $ 0.09 | | +47 | Norway | $ 0.05 | | +968 | Oman | $ 0.16 | | +680 | Palau | $ 0.03 | | +92 | Pakistan | $ 0.39 | | +507 | Panama | $ 0.10 | | +675 | Papua New Guinea | $ 0.16 | | +595 | Paraguay | $ 0.08 | | +51 | Peru | $ 0.03 | | +63 | Philippines | $ 0.18 | | +48 | Poland | $ 0.02 | | +351 | Portugal | $ 0.03 | | +974 | Qatar | $ 0.19 | | +262 | Reunion | $ 0.06 | | +40 | Romania | $ 0.06 | | +7 | Russia, Kazakhstan and Turkmenistan | $ 0.36 | | +250 | Rwanda | $ 0.11 | | +378 | San Marino | $ 0.05 | | +239 | Sao Tome and Principe | $ 0.09 | | +966 | Saudi Arabia | $ 0.15 | | +221 | Senegal | $ 0.22 | | +381 | Serbia | $ 0.12 | | +248 | Seychelles | $ 0.05 | | +232 | Sierra Leone | $ 0.16 | | +65 | Singapore | $ 0.05 | | +421 | Slovak Republic | $ 0.06 | | +386 | Slovenia | $ 0.10 | | +677 | Solomon Islands | $ 0.07 | | +252 | Somalia | $ 0.15 | | +27 | South Africa | $ 0.03 | | +82 | South Korea | $ 0.02 | | +34 | Spain | $ 0.05 | | +94 | Sri Lanka | $ 0.32 | | +290 | St. Helena | $ 0.05 | | +249 | Sudan | $ 0.25 | | +597 | Suriname | $ 0.09 | | +268 | Swaziland | $ 0.11 | | +46 | Sweden | $ 0.06 | | +41 | Switzerland | $ 0.04 | | +963 | Syria | $ 0.24 | | +886 | Taiwan | $ 0.23 | | +992 | Tajikistan | $ 0.18 | | +66 | Thailand | $ 0.02 | | +228 | Togo | $ 0.13 | | +676 | Tonga | $ 0.04 | | +216 | Tunisia | $ 0.10 | | +90 | Turkey | $ 0.008 | | +688 | Tuvalu | $ 0.11 | | +256 | Uganda | $ 0.13 | | +380 | Ukraine | $ 0.14 | | +971 | United Arab Emirates | $ 0.10 | | +44 | United Kingdom | $ 0.05 | | +598 | Uruguay | $ 0.06 | | +998 | Uzbekistan | $ 0.33 | | +678 | Vanuatu | $ 0.11 | | +58 | Venezuela | $ 0.08 | | +84 | Vietnam | $ 0.12 | | +967 | Yemen | $ 0.18 | | +260 | Zambia | $ 0.14 | | +255 | Zanzibar | $ 0.11 | | +263 | Zimbabwe | $ 0.11 | --- ## Pro https://appwrite.io/docs/advanced/platform/pro Appwrite Cloud's Pro plan is designed for professional developers or development teams that need to build applications at scale. When applications outgrow Appwrite's Free plan, organizations can switch to a Pro plan to continue growing their apps. You can learn more about the Pro plan on the [pricing page](https://appwrite.io/pricing). ### Create a Pro plan organization {% #create-a-pro-plan-organizations %} Appwrite's plans are applied to an entire organization, but resources are allocated per project. Get started with a Pro plan organization by visiting the [pricing page](https://appwrite.io/pricing) and click **Start building** or create a new organization from the Appwrite Console and select **Pro plan**. #### Switch to Pro plan {% #switch-to-pro-plan %} You can access your organization's overview through the profile menu at the top right of your Appwrite Console. Under the **Billing** tab, you can click **Change plan** to update your organization's plan. ### Resource limits {% #resource-limits %} Each plan in Appwrite Cloud has a set of resource limits per project. You can find the details of these resource limits on the [pricing page](https://appwrite.io/pricing). Additional resources are automatically purchased when your organization exceeds the resource limits to continue scaling until the budget cap is reached. Each resource limit is applied per billing period and resets at the beginning of each billing period. #### Budget cap {% #budget-cap %} Appwrite allows organizations to set budget caps when using a Pro plan. Appwrite will automatically scale Pro plan projects as they require more resources. Budget caps limit the amount of automatic scaling and prevent unexpected bills. Organization budget caps can be set by navigating to the organization's **Billing** tab, under **Budget cap**, toggling, and setting a budget cap. Appwrite will send emails to warn organization members when they are near the budget cap. {% partial file="resource-limits.md" /%} --- ## Rate-limits https://appwrite.io/docs/advanced/platform/rate-limits Some of Appwrite's API endpoints have a rate limit to avoid abuse or brute-force attacks against Appwrite's REST API. Each Appwrite route documentation has information about any rate limits that might apply to them. Rate limits only apply to Client SDKs. Rate limits do not apply when accessing Appwrite with a Server SDK authenticated using an API key. ### Headers {% #headers %} You can check the returned HTTP headers of any API request to see your current rate limit status: ```http HTTP/1.1 200 Date: Mon, 01 Jul 2013 17:27:06 GMT Status: 200 X-RateLimit-Limit: 60 X-RateLimit-Remaining: 56 X-RateLimit-Reset: 1372700873 ``` The headers tell you everything you need to know about your current rate limit status: | Header | Description | | -------------------- | ------------------------------------------------------------------------------ | | X-RateLimit-Limit | The maximum number of requests that the consumer is permitted to make per hour. | | X-RateLimit-Remaining| The number of requests remaining in the current rate limit window. | | X-RateLimit-Reset | The time at which the current rate limit window resets in UTC epoch seconds. | If you need the time in a different format, any modern programming language can get the job done. For example, if you open up the console on your web browser, you can easily get the reset time as a JavaScript Date object. You can also read more about [Unix Time](https://en.wikipedia.org/wiki/Unix_time). ```js new Date(1372700873 * 1000) // => Mon Jul 01 2013 13:47:53 GMT-0400 (EDT) ``` Once you go over the rate limit you will receive an error response: ```http HTTP/1.1 429 Date: Tue, 20 Aug 2013 14:50:41 GMT Status: 429 X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1377013266 { "message": "Too many requests", "code": 429 } ``` ### Dev keys {% #dev-keys %} Rate limits are necessary to protect your apps and users from abuse; however, they can sometimes add unwanted friction when a developer is trying to repeatedly consume certain Appwrite APIs to test their application in a short period. [Dev keys](/docs/advanced/platform/dev-keys) are a type of secret used by client apps to bypass these rate limits in development environments. To use dev keys, client apps add a header `X-Appwrite-Dev-Key` containing the secret to all HTTP requests sent to the Appwrite API. Appwrite recognizes this header, verifies the secret, and if valid, allows the request to bypass the rate limit. ```http X-Appwrite-Dev-Key: 5b0be23...abda7c6 ``` Dev keys should never be included in production applications as they can expose your application to abuse. They are meant for development and testing purposes only. ### Service abuse {% #service-abuse %} To protect the quality of service from Appwrite, additional rate limits may apply to some actions. For example, rapidly creating content, polling aggressively instead of using webhooks, making API calls with a high concurrency, or repeatedly requesting data that is computationally expensive may result in abuse rate limiting. It is not intended for this rate limit to interfere with any legitimate use of the API. Your normal rate limits should be the only limit you target. If you are exceeding your rate limit, you can likely fix the issue by caching API responses and using webhooks for data polling. If your application triggers this rate limit, you'll receive an informative response: ```http HTTP/1.1 429 Content-Type: application/json; charset=utf-8 Connection: close { "message": "Too many login attempts", "code": 429 } ``` --- ## Refund policy https://appwrite.io/docs/advanced/platform/refund-policy At Appwrite, we strive to provide exceptional backend services that meet your development needs. This policy outlines our approach to refunds for Appwrite services and ensures a fair and consistent process for all customers. ### General policy {% #general-policy %} Appwrite services are **non-refundable by default**. All purchases, including self-hosted support plans, Appwrite Cloud subscriptions, and professional services (e.g., onboarding, solution engineering, consulting) are considered final transactions. However, we recognize that exceptional circumstances may arise. In rare and specific situations where service performance, billing, or delivery significantly deviates from expectations, refunds may be considered upon thorough review and approval. ### Eligible scenarios {% #eligible-scenarios %} Refunds may be considered in the following circumstances: #### Service delivery failure {% #service-delivery-failure %} Critical and unresolved service outages affecting paid plans, or confirmed infrastructure issues that significantly impact service availability and performance. #### Billing errors {% #billing-errors %} Customers who are incorrectly charged due to: - Duplicate payments - Plan mismatches - Proven invoicing errors #### Unauthorized transactions {% #unauthorized-transactions %} Payments made without proper authorization or the account owner's permission, reported within 30 days of the transaction. #### First-time customer dissatisfaction {% #first-time-customer-dissatisfaction %} First-time cloud users may request a refund within the first 14 days of their subscription, provided they can demonstrate that the platform did not meet critical expectations and no significant usage occurred (subject to technical review at Appwrite's sole discretion). ### Ineligible scenarios {% #ineligible-scenarios %} The following scenarios do **not** qualify for refunds: - **Change in business needs or direction:** Decisions to pivot, discontinue use, or change platforms - **Incorrect setup or inactivity:** Customer misconfiguration, failure to complete onboarding, or lack of usage - **Expired refund window:** Requests submitted more than 30 days after the transaction date - **Third-party limitations:** Issues caused by third-party tools, plugins, or unsupported integrations - **Terms of service violations:** Any usage found to be in breach of Appwrite's [Terms](/terms) or [Fair use](/docs/advanced/platform/fair-use-policy) policy ### Request process {% #request-process %} To request a refund, please follow these steps: 1. **Submit a ticket** via the Appwrite Console 1. **Provide detailed information** about the issue, including relevant evidence and documentation 1. **Allow for review time** - we aim to respond within 5 business days, with final resolution within 10 business days #### Alternative solutions {% #alternative-solutions %} Whenever possible, we will first consider non-monetary solutions such as: - Service credits - Extended trial periods - Plan adjustments {% partial file="policy-modifications.md" /%} {% partial file="policy-contact-us.md" /%} --- ## Release policy https://appwrite.io/docs/advanced/platform/release-policy We value the trust of developers in Appwrite as the backbone of their applications. Our release policy is designed to provide developers with a reliable and consistent experience when using Appwrite. We are committed to providing support for our API, SDKs, and product versions for a reasonable length of time, and we follow industry-standard versioning protocols. Appwrite will prioritize security updates and will release new versions as soon as possible to fix any security vulnerabilities. ### Schedule {% #schedule %} We work to release a new minor version of the product every quarter, which will include new features and enhancements. We prioritize the timely release of patch versions with bug fixes and security updates on top of these feature releases, and we make every effort to ensure that our releases are thoroughly tested and stable before they are made available to developers. In rare cases where there are significant delays or changes to our release schedule, we will notify through our website's [changelog](/changelog), [Discord](https://appwrite.io/discord), newsletter, and other communication channels. ### Scope {% #scope %} Appwrite will provide two phases of continued support for older versions of Appwrite. {% table %} * Phase {% width=100 %} * Scope --- * Support * Receive continued bug fixes and security updates. --- * Extended security support * Receives only security updates. {% /table %} ### Support {% #support %} Appwrite commits to the continued support of our software with extended support policies for security related fixes for older versions of Appwrite. Supported versions will continue to receive bug fixes and security updates. Extended security support versions will only receive security updates. {% table %} * Releases {% width=100 %} * Current Version * Support * Extended Security Support --- * API * `v1` * 3 latest versions * 10 years --- * SDK * [See list](/docs/sdks) * 5 latest versions * 10 years --- * Self-hosted * `1.5.x` * Latest major version `>= 1.x.x` * 10 years --- * Runtimes * [See list](/docs/products/functions/runtimes) * 24 months * Per vendor {% /table %} ### API versions {% #api-versions %} We are committed to providing developers with a stable and reliable API. Appwrite API versions don’t change very often but are reserved for breaking changes. Currently, the latest stable version of the Appwrite API is `/v1`. We use this prefix in all our API endpoint paths to allow API versioning. Any new Appwrite version will retain **backward compatibility** for any supported API version as long as this API version is still under maintenance support. We will provide standard maintenance support for the last 3 API versions. Once a version is no longer in this maintenance period, we will continue to provide support for security fixes for an additional ten years. Based on usage, we may decide to extend support for a specific version. Self-hosted versions of Appwrite will receive continued support for the latest major version of Appwrite. If you need **extended support** for older versions of Appwrite, [contact us](/contact-us) for more information. ### SDK versioning {% #sdk-verversioningsions %} For our different SDKs, we follow the Semantic Versioning (semver) protocol to assign versions to our releases. This means that we assign version numbers using a three-part system: major, minor, and patch. The major version changes when we make significant changes to our API or product, which may require significant changes to developers' code. The minor version changes when we add new features or functionality that do not significantly impact developers' systems. Finally, the patch version changes when we make bug fixes or minor improvements. All Appwrite SDKs will have backward compatibility with the Appwrite APIs. In case a new version of the API or product has been released, you should expect your applications to continue working properly without any action from your side. Once we release a major version of the SDK and you decide to upgrade, look in the [changelog of the relevant SDK on GitHub](/docs/sdks) to understand what changes have been made and what adjustments are required. We provide early notice to developers and slowly introduce breaking changes to let developers adjust their application at a reasonable pace. We will also continue to support the last five major versions of each SDK to provide developers with more flexibility and time to adjust their apps to take advantage of new features. ### Self-hosted versioning {% #self-hosted-versioning %} When you self-host Appwrite, we also follow the Semantic Versioning (semver) protocol for versioning our releases. This means that we assign version numbers using a three-part system: major, minor, and patch. The major version changes when we make significant changes to our API or product, which may require significant changes to developers' apps. The minor version changes when we add new features or functionality that do not significantly impact developers' existing apps. Finally, the patch version changes when we make bug fixes or minor improvements. Appwrite Cloud receives the latest features and updates first. If you want access to the newest features and capabilities as soon as they are released, we recommend using Appwrite Cloud. Self-hosted releases are typically updated about 2 months after major feature releases to Cloud, allowing time to finalize migrations and prepare the release for self-hosted environments. [Contact us](/contact-us/enterprise) for more advanced self-hosting capabilities. All the self-hosted versions of Appwrite `>=1.x.x` continue to have support and backward compatibility with the Appwrite API and SDKs within each major version. In case a new version of the product has been released and you decide to update, you should expect your applications to continue working properly without any action from your side. Once we release a version of the product and you decide to upgrade, look in the [changelog](https://github.com/appwrite/appwrite/releases) to understand if your version requires migration of data from your previous setup. This is usually required when we make adjustments to the under the hood data structure for supporting new features and improving maintainability. If this is the case, you could use our [built-in migration tool](/docs/advanced/self-hosting/update#running-the-migration) for helping you to upgrade your self-hosted Appwrite version. {% arrow_link href="/docs/advanced/self-hosting/update" %} Learn more about updating self-hosted Appwrite {% /arrow_link %} ### Runtime versioning {% #runtime-versioning %} [Appwrite Function runtimes](/docs/products/functions/runtimes) are built around a combination of operating system, programming language, and software libraries that are subject to maintenance and security updates. Appwrite will support and maintain runtimes as a package, which covers the specific combination of operating system, programming language, and software libraries. Appwrite will support the latest stable versions of our [Appwrite Function runtime environment](/docs/products/functions/runtimes) for a minimum of 24 months after its initial release as long as security updates for components of the specific runtime are still provided. You can review the [list of supported runtimes](/docs/products/functions/runtimes) on Appwrite. In most cases, the end-of-life date of a language version or operating system is known well in advance. The links below give end-of-life schedules for each language that Appwrite supports as a managed runtime. #### Runtimes language {% #runtime-languages %} {% table %} * Language {% width=150 %} * LTS policy --- * Node.js * [https://nodejs.org](https://nodejs.org) --- * Python * [devguide.python.org](https://devguide.python.org) --- * Ruby * [www.ruby-lang.org](https://www.ruby-lang.org) --- * Java * [www.oracle.com](https://www.oracle.com) --- * .NET Core * [dotnet.microsoft.com](https://dotnet.microsoft.com) --- * PHP * [https://www.php.net](https://www.php.net) --- * Dart * [https://dart.dev/](https://dart.dev/) --- * Deno * [https://deno.com/runtime](https://deno.com/runtime) --- * Go * [https://go.dev/](https://go.dev/) --- * Swift * [https://developer.apple.com/swift](https://developer.apple.com/swift) --- * Kotlin * [https://kotlinlang.org](https://kotlinlang.org) --- * C++ * [https://en.cppreference.com/w/](https://en.cppreference.com/w/) {% /table %} #### Runtimes OS {% #runtime-os %} {% table %} * OS {% width=150 %} * LTS policy --- * Alpine * [https://www.alpinelinux.org](https://www.alpinelinux.org) --- * Debian * [https://www.debian.org/](https://www.debian.org/) --- * Ubuntu * [https://releases.ubuntu.com/](https://releases.ubuntu.com/) {% /table %} --- ## Response codes https://appwrite.io/docs/advanced/platform/response-codes Appwrite uses conventional HTTP response codes to indicate the success or failure of an API request. - Codes in the `2xx` range indicate success. - Codes in the `4xx` range indicate an error caused by invalid request, usually caused by user error. - Codes in the `5xx` range indicate an error with Appwrite, please check Docker container logs. ### Response codes {% #response-codes %} | Code | Text | Description | |------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 200 | OK | Success! | | 201 | Created | The requested resource has been created successfully. | | 202 | Accepted | The requested change has been accepted for processing but has not been completed. | | 204 | No Content | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. This status will usually return on successful delete operations. | | 301 | Moved Permanently | The URL of the requested resource has been changed permanently. The new URL is given in the response. | | 304 | Not Modified | There was no new data to return. | | 400 | Bad Request | The request was invalid or cannot be otherwise served. An accompanying error message will explain further. Requests with wrong or invalid input will yield this response. | | 401 | Unauthorized | Missing or incorrect authentication credentials can happen when the API key or user permission is not sufficient. | | 403 | Forbidden | The request is understood, but it has been refused, or access is not allowed. An accompanying error message will explain why. Make sure to register your app in your project's dashboard platform list. | | 404 | Not Found | The URI requested is invalid or the resource requested, such as a user, does not exist. | | 409 | Conflict | This response is sent when a request conflicts with the current state of the server. This status code will usually appear when you're trying to create an already existing resource. | | 413 | Payload Too Large | This indicates that the request entity is larger than limits defined by server. This status code will usually appear happen when uploading a file or function that is too large | | 416 | Invalid Range | Invalid value in the range or content-range headers. Usually returned while uploading or downloading files using the range header but the provided range value is not valid. | | 429 | Too Many Requests | Returned in when a request cannot be served due to the application's rate limit having been exhausted for the resource. See [Rate Limits](/docs/advanced/platform/rate-limits). | | 500 | Internal Server Error | Something is broken. Contact our [team](/support), or raise a [GitHub issue](https://github.com/appwrite/appwrite/issues/new). | | 501 | Not Implemented | The feature is not implemented. Usually returned when the project owner has disabled an auth method or an entire service. | | 503 | Service Unavailable | The Appwrite servers are up but overloaded with requests. Try again later. | | 504 | Gateway timeout | The Appwrite servers are up, but the request couldn't be serviced due to some failure within the stack. Try again later. | ### Error messages {% #error-messages %} When the Appwrite APIs return error messages, it does so in JSON format. For example, an error might look like this: ```json { "message": "Invalid id: Parameter must be a valid number", "type": "argument_invalid", "code": 400 } ``` ### Error types {% #error-types %} Appwrite also passes convenient error types in addition to the HTTP response codes to help you get more fine-grained control over what went wrong and allowing you to display relevant error messages in your applications. Error types are convenient to identify the type of error that occurred. For example, a `400` HTTP response code could indicate a Bad Request due to a variety of reasons, and error types can help you pinpoint the exact `400` error. Appwrite currently supports the following error types: #### Platform errors {% #platform-errors %} Platform errors are not related to an individual product, but can occur across the Appwrite platform. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | general_mock | General errors thrown by the mock controller used for testing. | | 400 | general_argument_invalid | The request contains one or more invalid arguments. Please refer to the endpoint documentation. | | 400 | general_query_limit_exceeded | Query limit exceeded for the current column. Usage of more than 100 query values on a single column is prohibited. | | 400 | general_query_invalid | The query's syntax is invalid. Please check the query and try again. | | 400 | general_cursor_not_found | The cursor is invalid. This can happen if the item represented by the cursor has been deleted. | | 400 | general_provider_failure | VCS (Version Control System) provider failed to process the request. We believe this is an error with the VCS provider. Try again, or contact support for more information. | | 400 | project_unknown | The project ID is either missing or not valid. Please check the value of the `X-Appwrite-Project` header to ensure the correct project ID is being used. | | 400 | project_invalid_success_url | Invalid redirect URL for OAuth success. | | 400 | project_invalid_failure_url | Invalid redirect URL for OAuth failure. | | 400 | project_reserved_project | The project ID is reserved. Please choose another project ID. | | 400 | project_smtp_config_invalid | Provided SMTP config is invalid. Please check the configured values and try again. | | 401 | project_key_expired | The project key has expired. Please generate a new key using the Appwrite console. | | 401 | rule_verification_failed | Domain verification failed. Please check if your DNS records are correct and try again. | | 401 | project_template_default_deletion | You can't delete default template. If you are trying to reset your template changes, you can ignore this error as it's already been reset. | | 403 | general_unknown_origin | The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console. | | 401 | general_access_forbidden | Access to this API is forbidden. | | 401 | general_unauthorized_scope | The current user or API key does not have the required scopes to access the requested resource. | | 404 | general_route_not_found | The requested route was not found. Please refer to the API docs and try again. | | 404 | webhook_not_found | Webhook with the requested ID could not be found. | | 404 | rule_resource_not_found | Resource could not be found. Please check if the `resourceId` and `resourceType` are correct, or if the resource actually exists. | | 404 | rule_not_found | Rule with the requested ID could not be found. Please check if the ID provided is correct or if the rule actually exists. | | 404 | key_not_found | Key with the requested ID could not be found. | | 404 | platform_not_found | Platform with the requested ID could not be found. | | 404 | project_not_found | Project with the requested ID could not be found. Please check the value of the `X-Appwrite-Project` header to ensure the correct project ID is being used. | | 404 | router_host_not_found | Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again. | | 405 | general_not_implemented | This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version. | | 409 | project_already_exists | Project with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | | 409 | rule_already_exists | Domain is already used. Please try again with a different domain. | | 412 | project_provider_disabled | The chosen OAuth provider is disabled. You can enable the OAuth provider using the Appwrite console. | | 429 | general_rate_limit_exceeded | Rate limit for the current endpoint has been exceeded. Please try again after some time. | | 500 | general_unknown | An unknown error has occurred. Please check the logs for more information. | | 500 | general_server_error | An internal server error occurred. | | 500 | general_protocol_unsupported | The request cannot be fulfilled with the current protocol. Please check the value of the `_APP_OPTIONS_FORCE_HTTPS` environment variable. | | 500 | general_codes_disabled | Invitation codes are disabled on this server. Please contact the server administrator. | | 500 | router_domain_not_configured | `_APP_DOMAIN`, `_APP_DOMAIN_TARGET`, and `_APP_DOMAIN_FUNCTIONS` environment variables have not been configured. Please configure the domain environment variables before accessing the Appwrite Console via any IP address or hostname other than localhost. This value could be an IP like 203.0.113.0 or a hostname like example.com. | | 501 | general_usage_disabled | Usage stats is not configured. Please check the value of the `_APP_USAGE_STATS` environment variable of your Appwrite server. | | 501 | project_provider_unsupported | The chosen OAuth provider is unsupported. Please check the Create OAuth2 Session docs for the complete list of supported OAuth providers. | | 503 | general_service_disabled | The requested service is disabled. You can enable the service from the Appwrite console. | | 503 | general_smtp_disabled | SMTP is disabled on your Appwrite instance. You can learn more about setting up SMTP in our docs. | | 503 | general_phone_disabled | Phone provider is not configured. Please check the `_APP_SMS_PROVIDER` environment variable of your Appwrite server. | #### Authentication errors {% #authentication-errors %} Errors found when using Appwrite Authentication. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | user_password_mismatch | Passwords do not match. Please check the password and confirm password. | | 400 | password_recently_used | The password you are trying to use is similar to your previous password. For your security, please choose a different password and try again. | | 400 | password_personal_data | The password you are trying to use contains references to your name, email, phone or userID. For your security, please choose a different password and try again. | | 400 | user_phone_not_found | The current user does not have a phone number associated with their account. | | 400 | user_missing_id | Missing ID from OAuth2 provider. | | 400 | user_oauth2_bad_request | OAuth2 provider rejected the bad request. | | 401 | user_jwt_invalid | The JWT token is invalid. Please check the value of the `X-Appwrite-JWT` header to ensure the correct token is being used. | | 401 | user_blocked | The current user has been blocked. You can unblock the user by making a request to the User API's "Update User Status" endpoint or in the Appwrite Console's Auth section. | | 401 | user_invalid_token | Invalid token passed in the request. | | 401 | user_email_not_whitelisted | Console registration is restricted to specific emails. Contact your administrator for more information. | | 401 | user_invalid_code | The specified code is not valid. Contact your administrator for more information. | | 401 | user_ip_not_whitelisted | Console registration is restricted to specific IPs. Contact your administrator for more information. | | 401 | user_invalid_credentials | Invalid credentials. Please check the email and password. | | 401 | user_anonymous_console_prohibited | Anonymous users cannot be created for the console project. | | 401 | user_session_already_exists | Creation of anonymous users is prohibited when a session is active. | | 401 | user_unauthorized | The current user is not authorized to perform the requested action. | | 401 | user_oauth2_unauthorized | OAuth2 provider rejected the unauthorized request. | | 401 | team_invalid_secret | The team invitation secret is invalid. Please request a new invitation and try again. | | 401 | team_invite_mismatch | The invite does not belong to the current user. | | 404 | user_not_found | User with the requested ID could not be found. | | 404 | user_session_not_found | The current user session could not be found. | | 404 | user_identity_not_found | The identity could not be found. Please sign in with OAuth provider to create identity first. | | 404 | team_not_found | Team with the requested ID could not be found. | | 404 | team_invite_not_found | The requested team invitation could not be found. | | 404 | team_membership_mismatch | The membership ID does not belong to the team ID. | | 404 | membership_not_found | Membership with the requested ID could not be found. | | 409 | user_already_exists | A user with the same id, email, or phone already exists in this project. | | 409 | user_email_already_exists | A user with the same email already exists in the current project. | | 409 | user_phone_already_exists | A user with the same phone number already exists in the current project. | | 409 | team_invite_already_exists | User has already been invited or is already a member of this team | | 409 | team_already_exists | Team with requested ID already exists. Please choose a different ID and try again. | | 409 | membership_already_confirmed | Membership is already confirmed. | | 412 | user_password_reset_required | The current user requires a password reset. | | 424 | user_oauth2_provider_error | OAuth2 provider returned some error. | | 501 | user_count_exceeded | The current project has exceeded the maximum number of users. Please check your user limit in the Appwrite console. | | 501 | user_auth_method_unsupported | The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console. | #### Databases errors {% #databases-errors %} Errors found when using Appwrite Databases. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | table_limit_exceeded | The maximum number of tables has been reached. | | 400 | row_invalid_structure | The row structure is invalid. Please ensure the columns match the table definition. | | 400 | row_missing_data | The row data is missing. Try again with row data populated. | | 400 | row_missing_payload | The row data and permissions are missing. You must provide either row data or permissions to be updated. | | 400 | column_unknown | The column required for the index could not be found. Please confirm all your columns are in the available state. | | 400 | column_not_available | The requested column is not yet available. Please try again later. | | 400 | column_format_unsupported | The requested column format is not supported. | | 400 | column_default_unsupported | Default values cannot be set for array or required columns. | | 400 | column_limit_exceeded | The maximum number of columns has been reached. | | 400 | column_value_invalid | The column value is invalid. Please check the type, range and value of the column. | | 400 | column_type_invalid | The column type is invalid. | | 400 | index_limit_exceeded | The maximum number of indexes has been reached. | | 400 | index_invalid | Index invalid. | | 403 | row_delete_restricted | Row cannot be deleted because it is referenced by another row. | | 404 | execution_not_found | Execution with the requested ID could not be found. | | 404 | database_not_found | Database not found | | 404 | table_not_found | Table with the requested ID could not be found. | | 404 | row_not_found | Row with the requested ID could not be found. | | 404 | column_not_found | Column with the requested ID could not be found. | | 404 | index_not_found | Index with the requested ID could not be found. | | 409 | database_already_exists | Database already exists | | 409 | table_already_exists | A table with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | | 409 | row_already_exists | Row with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | | 409 | row_update_conflict | Remote row is newer than local. | | 409 | column_already_exists | Column with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | | 409 | index_already_exists | Index with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | #### Storage errors {% #storage-errors %} Errors found when using Appwrite Storage. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | storage_device_not_found | The requested storage device could not be found. | | 400 | storage_file_empty | Empty file passed to the endpoint. | | 400 | storage_file_type_unsupported | The given file extension is not supported. | | 400 | storage_invalid_file_size | The file size is either not valid or exceeds the maximum allowed size. Please check the file or the value of the `_APP_STORAGE_LIMIT` environment variable. | | 400 | storage_invalid_content_range | The content range is invalid. Please check the value of the `Content-Range` header. | | 400 | storage_invalid_appwrite_id | The value for `x-appwrite-id` header is invalid. Please check the value of the `x-appwrite-id` header is a valid id and not `unique()`. | | 403 | storage_invalid_file | The uploaded file is invalid. Please check the file and try again. | | 404 | storage_file_not_found | The requested file could not be found. | | 404 | storage_bucket_not_found | Storage bucket with the requested ID could not be found. | | 409 | storage_file_already_exists | A storage file with the requested ID already exists. | | 409 | storage_bucket_already_exists | A storage bucket with the requested ID already exists. Try again with a different ID or use `unique()` to generate a unique ID. | | 416 | storage_invalid_range | The requested range is not satisfiable. Please check the value of the `Range` header. | #### Functions errors {% #functions-errors %} Errors found when using Appwrite Functions. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | build_not_ready | Build with the requested ID is building and not ready for execution. | | 400 | build_in_progress | Build with the requested ID is already in progress. Please wait before you can retry. | | 404 | installation_not_found | Installation with the requested ID could not be found. Check to see if the ID is correct, or create the installation. | | 404 | provider_repository_not_found | VCS (Version Control System) repository with the requested ID could not be found. Check to see if the ID is correct, and if it belongs to installationId you provided. | | 404 | repository_not_found | Repository with the requested ID could not be found. Check to see if the ID is correct, or create the repository. | | 404 | function_not_found | Function with the requested ID could not be found. | | 404 | function_runtime_unsupported | The requested runtime is either inactive or unsupported. Please check the value of the `_APP_FUNCTIONS_RUNTIMES` environment variable. | | 404 | function_runtime_unsupported | Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function's "Settings" > "Configuration" > "Entrypoint". | | 404 | build_not_found | Build with the requested ID could not be found. | | 404 | deployment_not_found | Deployment with the requested ID could not be found. | | 404 | variable_not_found | Variable with the requested ID could not be found. | | 409 | provider_contribution_conflict | External contribution is already authorized. | | 409 | variable_already_exists | Variable with the same ID already exists in this project. Try again with a different ID. | #### Migrations errors {% #migrations-errors %} Errors when using Appwrite Migrations. | Code | Type | Description | | ---- | ---- | ----------- | | 404 | migration_not_found | Migration with the requested ID could not be found. Please verify that the provided ID is correct and try again. | | 409 | migration_already_exists | Migration with the requested ID already exists. Try again with a different ID. | | 409 | migration_in_progress | Migration is already in progress. You can check the status of the migration in your Appwrite Console's "Settings" > "Migrations". | #### Avatars errors {% #avatars-errors %} Errors from the Appwrite Avatars API. | Code | Type | Description | | ---- | ---- | ----------- | | 404 | avatar_set_not_found | The requested avatar set could not be found. | | 404 | avatar_not_found | The request avatar could not be found. | | 404 | avatar_image_not_found | The requested image was not found at the URL. | | 404 | avatar_remote_url_failed | Failed to fetch favicon from the requested URL. | | 404 | avatar_icon_not_found | The requested favicon could not be found. | #### GraphQL errors {% #graphql-errors %} Errors from the Appwrite GraphQL API. | Code | Type | Description | | ---- | ---- | ----------- | | 400 | graphql_no_query | Param "query" is not optional. | | 400 | graphql_too_many_queries | Too many queries. | --- ## Roles https://appwrite.io/docs/advanced/platform/roles The Appwrite Console supports granular permissions to improve team collaboration and security. Each member of your Console team can be assigned a specific role that grants them access to certain areas of your organization's projects. Below is a breakdown of the new roles available, detailing their permissions and intended use cases. {% info title="Note" %} This page covers organization member roles for the Appwrite Console. Visit the Auth [roles documentation](https://appwrite.io/docs/products/auth/teams#permissions) if you want to learn more about roles for the [Teams service](https://appwrite.io/docs/references/cloud/client-web/teams). {% /info %} #### Owner {% #owner %} The highest level of access, the Owner role has full control over all aspects of the Console, including team management, billing, and all development resources. Only owners can create new projects. #### Developer {% #developer %} Developers have access to all resources and scopes available to the Owner, with the exception of team management and billing writes. This role is ideal for team members focusing solely on development tasks. #### Editor {% #editor %} Editors can modify most resources but do not have write permissions for critical backend elements like tables, buckets, topics, and others. This role is intended for users who need to modify content or make changes but should not alter key infrastructure elements. This is great if you need to give access for updating your rows, creating messages, or uploading files. #### Analyst {% #analyst %} Analysts are limited to read-only access across all resources. This role is suitable for team members who need to view data, analytics, or reports but do not require editing permissions. #### Billing {% #billing %} Billing users are restricted to billing-related actions, with access to `billing.read` and `billing.write` scopes only. They can view and manage billing details but cannot interact with other parts of the system. #### Custom roles {% #custom-roles %} Custom roles will soon be available on the Appwrite Console. Custom roles will be a Scale and Enterprise plans feature. --- ## Scale https://appwrite.io/docs/advanced/platform/scale Appwrite's Scale plan is designed for growing development teams and agencies with many organizational members and large projects. The plan offers unlimited seats across your organization and dedicated resources per project. Scale plan organizations will receive additional compliance measures, organization roles, and dedicated support. --- ## Keyboard shortcuts https://appwrite.io/docs/advanced/platform/shortcuts The Appwrite Console was designed with a keyboard first approach. The Appwrite Console supports keyboard shortcuts that make it easier to navigate and perform actions quicker. ### Shortcuts {% #shortcuts %} The Appwrite Console supports keyboard shortcuts that make it easier to navigate and perform common actions quicker. The shortcuts use the following pattern: use the first letter from the call to action followed by the resource, product, service, or page you're targeting. For example, the shortcut keys `G` + `S` navigates to the project's Storage screen. Similarly, the shortcut `G` + `F` navigates to the project's Functions screen. However, when there's a conflict with a shortcut key, the following letter is used. For example, since S is already used for Storage, the shortcut for Settings is E. This pattern is used to make usage of shortcuts consistent and predictable. #### Global shortcuts {% #global-shortcuts %} Developers can also utilize global shortcuts anywhere in the Appwrite Console, allowing instant access to these features from any page on the Console. |Shortcut|Action| |----|----| |`T` then `L`|Set theme to light| |`T` then `D`|Set theme to dark| |`T` then `A`|Set theme to dark| |`A` then `I`|Open Appwrite Assistant| |`C` then `O`|Create organization| #### Command center All the Appwrite shortcuts can be found in the [command center](/docs/tooling/command-center). Use the command center to search for commands or the current page's content. |PC (Windows / Linux)|macOS|Action| |----|----|----| |`Ctrl` + `K`|`⌘` + `K`|Access Command Center| #### Project shortcuts {% #project-shortcuts %} Within the context of a project, developers can utilize project shortcuts. These shortcuts are automatically enabled once a project is selected on the Console. These shortcuts allow quick access to the project's pages making it easier and faster to navigate the Console more. |Shortcut|Action| |----|----| |`G` then `P`|Go to projects| |`G` then `O`|Go to overview| |`G` then `A`|Go to auth| |`G` then `D`|Go to databases| |`G` then `F`|Go to functions| |`G` then `M`|Go to messaging| |`G` then `S`|Go to storage| |`G` then `E`|Go to settings| ### Primary Actions Each screen on the console has a concept of a primary action. For example, in the Functions screen, the primary action would be creating a function, in the Databases screen, it would be creating a database and so on. The primary actions are usually triggered using the `C` key as seen in the table below. |Shortcut|Action| |----|----| |`C` then `P`|Create project| |`C` then `U`|Create user| |`C` then `A`|Create database| |`C` then `C`|Create table| |`C` then `D`|Create row| |`C` then `T`|Create column| |`C` then `F`|Create function| |`C` then `S`|Create storage| ### Accessibility {% #accessibility %} Using keyboard shortcuts and other methods, we are committed to making the Appwrite Console accessible to all developers by following the AA-level standards of the [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/WAI/WCAG22/Understanding/conformance#levels). Following these standards ensures that the Console is usable by people with visual, auditory, physical, speech, cognitive, language, learning, and neurological disabilities. Good contrast between text and backgrounds, resizable text without loss of content or functionality, and navigable interfaces via keyboard and screen readers guarantee an inclusive environment and allow developers to perform their tasks efficiently. Adhering to these guidelines ensures that the Appwrite Console complies with legal requirements and promotes a positive developer experience for everyone. --- ## Support SLA https://appwrite.io/docs/advanced/platform/support-sla This Support Service Level Agreement ("SLA") describes the support services provided by APPWRITE ("we," "us," or "our") to users of our products and services ("you" or "user"). By using our services, you agree to the terms of this SLA. ### Scope This SLA outlines our commitments for providing support services via email, including response and resolution processes based on issue severity. The specific response times depend on the support tier associated with your support plan: **Silver**, **Gold**, or **Platinum**. ### Severity levels Support issues are categorized into the following severity levels: - **Critical**: System is down or a critical component is non-functional, causing a complete stoppage of work or significant business impact. - **High**: Major functionality is impaired, but a workaround is available, or a critical component is significantly degraded. - **Medium**: Minor functionality is impaired without significant business impact. - **Low**: Issue has minor impact on business operations; workaround is not necessary. - **Question**: Requests for information, general guidance, or feature requests. ### Response time targets | Severity | Silver | Gold | Platinum | | --- | --- | --- | --- | | Critical | Unsupported | 1 hour (24/7/365) | 15 minutes (24/7/365) | | High | Unsupported | 4 hours (24/7/365) | 1 hour (24/7/365) | | Medium | 2 business days | 1 business day | 12 hours (24/7/365) | | Low | 3 business days | 2 business days | 24 hours (24/7/365) | | Question | 4 business days | 3 business days | 1 business day | ### Business hours and days Our standard business hours are from **9:00 AM to 5:00 PM Pacific Time**, Monday through Friday, excluding public holidays. Platinum and Gold support plan customers receive extended support 24/7/365. ### User responsibilities To ensure effective support, users are expected to: - Provide detailed information about each issue, including screenshots, error messages, logs, and steps to reproduce the problem. - Ensure relevant personnel are available to assist in diagnosing and resolving issues. - Implement reasonable recommendations provided by our support team. ### Limitations and exclusions - This SLA applies only to support requests submitted via the Appwrite Console. - SLA obligations may be affected by factors outside our reasonable control, including but not limited to force majeure events, third-party dependencies, or actions taken by the user. {% partial file="policy-modifications.md" /%} {% partial file="policy-contact-us.md" /%} --- ## Uptime SLA https://appwrite.io/docs/advanced/platform/uptime-sla This Uptime Service Level Agreement ("SLA") describes the uptime commitments and related service credit terms provided by APPWRITE ("we," "us," or "our") to users of our products and services ("you" or "user"). By using our services, you agree to the terms of this SLA. ### Uptime commitments {% #uptime-commitments %} We commit to maintaining the following monthly uptime percentages based on your subscription plan: | Plan | Monthly Uptime Commitment | | --- | --- | | **Free** | N/A | | **Pro** | 99.5% | | **Scale** | 99.9% | | **Enterprise** | 99.95% | ### Allowed downtime {% #allowed-downtime %} Based on the above uptime commitments, permissible downtime durations are: | Plan | Annual Downtime | Monthly Downtime | | --- | --- | --- | | **Free** | N/A | N/A | | **Pro** | ~1.83 days | ~3.6 hours | | **Scale** | ~8.76 hours | ~43.2 minutes | | **Enterprise** | ~4.38 hours | ~21.6 minutes | ### Service credit terms {% #service-credit-terms %} If we fail to meet our uptime commitment, affected users may be eligible for service credits according to the following schedule: | Plan | Uptime < Commitment but ≥ 99% | Uptime < 99% but ≥ 95% | Uptime < 95% | | --- | --- | --- | --- | | **Pro** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee | | **Scale** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee | | **Enterprise** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee | ### Credit request procedure {% #credit-request-procedure %} To receive service credits, you must submit a support ticket within **30 days** of the incident via the Appwrite Console, providing relevant data, logs, or screenshots. ### SLA exclusions {% #sla-exclusions %} Downtime specifically excludes periods caused by: - Scheduled maintenance activities communicated in advance. - Events beyond our reasonable control (e.g., natural disasters, third-party outages, governmental actions). - Customer or third-party actions, including incorrect configurations or misuse of our services. - Minor performance degradations that do not materially impact core functionality. - External network issues beyond our infrastructure. ### Commitment to continuous improvement {% #commitment-to-continuous-improvement %} We continuously strive to exceed the commitments outlined in this SLA by improving infrastructure resilience and service quality. While the maximum attainable uptime commitment is influenced by our upstream service providers, we aim for excellence and transparency in all operational matters. {% partial file="policy-modifications.md" /%} {% partial file="policy-contact-us.md" /%} --- ## Webhooks https://appwrite.io/docs/advanced/platform/webhooks Webhooks allow you to build or set up integrations which subscribe to certain events on Appwrite. When one of those events is triggered, we'll send an HTTP POST payload to the webhook's configured URL. Webhooks can be used to purge cache from CDN, calculate data or send a Slack notification. You're only limited by your imagination. ### Getting started {% #getting-started %} You can set your webhook by adding it from your Appwrite project dashboard. You can access your webhooks settings from your project dashboard or on the left navigation panel. Click the 'Add Webhook' button and choose your webhook name and the events that should trigger it. You can also set an optional basic HTTP authentication username and password to protect your endpoint from unauthorized access. ### Payload {% #payload %} Each event type has a specific payload format with the relevant event information. All event payloads mirror the payloads for the API payload which parallel to the [event types](/docs/advanced/platform/events). ### Headers {% #headers %} HTTP requests made to your webhook's configured URL endpoint will contain several special headers. | Header | Description | |--------|-------------| | X-Appwrite-Webhook-Id | The ID of the Webhook who triggered the event. | | X-Appwrite-Webhook-Events | Names of the events that triggered this delivery. | | X-Appwrite-Webhook-Name | Name of the webhook as specified in your app settings and [events list](/docs/advanced/platform/events). | | X-Appwrite-Webhook-User-Id | The user ID of the user who triggered the event. Returns an empty string if an API key triggered the event. Note that events like `account.create` or `account.sessions.create` are performed by guest users and will not return any user ID. If you still need the user ID for these events, you can find it in the event payload. | | X-Appwrite-Webhook-Project-Id | The ID of the project who owns the Webhook and API call. | | X-Appwrite-Webhook-Signature | The HMAC-SHA1 signature of the payload. This is used to verify the authenticity of the payload. | | User-Agent | Each request made by Appwrite will be 'Appwrite-Server'. | ### Verification {% #verification %} Webhooks can be verified by using the [X-Appwrite-Webhook-Signature](/docs/advanced/platform/webhooks#headers) header. This is the HMAC-SHA1 signature of the payload. You can find the signature key in your webhooks properties in the dashboard. To generate this hash you append the payload to the end of webhook URL (make sure there are no spaces in between) and then use the HMAC-SHA1 algorithm to generate the signature. After you've generated the signature, compare it to the `X-Appwrite-Webhook-Signature` header value. If they match, the payload is valid and you can trust it came from your Appwrite instance. ### Events {% #events %} Appwrite has events that fire when a resource changes. These events cover all Appwrite resources and can reflect create, update, and delete actions. You can specify one or many events to subscribe to with webhooks. {% accordion %} {% accordion_item title="Authentication events" %} {% partial file="auth-events.md" /%} {% /accordion_item %} {% accordion_item title="Databases events" %} {% partial file="databases-events.md" /%} {% /accordion_item %} {% accordion_item title="Storage events" %} {% partial file="storage-events.md" /%} {% /accordion_item %} {% accordion_item title="Functions events" %} {% partial file="functions-events.md" /%} {% /accordion_item %} {% accordion_item title="Messaging events" %} {% partial file="messaging-events.md" /%} {% /accordion_item %} {% /accordion %} [Learn more about events](/docs/advanced/platform/api-keys) --- ## Security https://appwrite.io/docs/advanced/security Appwrite helps you build secure apps by applying various security and compliance measures. Appwrite is compliant with [GDPR](/docs/advanced/security/gdpr), [CCPA](/docs/advanced/security/ccpa), [HIPAA](/docs/advanced/security/hipaa), and [SOC 2](/docs/advanced/security/soc2). Appwrite also employs [enhanced password protection and encryption](/docs/products/auth/security), [rate limits](/docs/advanced/security/abuse-protection), [robust permission systems](/docs/advanced/platform/permissions), and [HTTPS/TLS](/docs/advanced/security/tls) to protect you and your users' data. ### Compliance {% #compliance %} The safeguarding of your and your users' data is taken seriously at Appwrite. Appwrite works to achieve compliance with a variety of standards to protect sensitive data, as well as maintain trust and credibility. {% cards %} {% cards_item href="/docs/advanced/security/gdpr" title="GDPR" %} Appwrite is GDPR compliant. Learn about our measures, privacy policy, and find our data processing agreement. {% /cards_item %} {% cards_item href="/docs/advanced/security/pci" title="PCI" %} Appwrite uses Stripe to handle payment and payment information securely. Learn about Appwrite's PCI compliance. {% /cards_item %} {% cards_item href="/docs/advanced/security/soc2" title="SOC 2" %} Appwrite is SOC2 Type I compliant. Learn about Appwrite's measures to meet SOC 2 standards. {% /cards_item %} {% cards_item href="/docs/advanced/security/hipaa" title="HIPAA" %} Appwrite is HIPAA compliant. Learn about Appwrite's measures to protect personal health information. {% /cards_item %} {% cards_item href="/docs/advanced/security/ccpa" title="CCPA" %} Appwrite is CCPA compliant. Learn about our measures to protect consumer privacy under the California Consumer Privacy Act. {% /cards_item %} {% /cards %} ### Measures {% #measures %} Appwrite employs a variety of measures to help you build secure applications, faster. Learn about the different ways Appwrite protects you and your users' data and privacy. {% cards %} {% cards_item href="/docs/products/auth/security" title="Authentication" %} Secure authentication methods to protect your users and promote better passwords. {% /cards_item %} {% cards_item href="/docs/advanced/security/encryption" title="Encryption" %} Appwrite encrypts sensitive data and files in Appwrite Databases and Storage. {% /cards_item %} {% cards_item href="/docs/advanced/security/https" title="HTTPS" %} Appwrite Cloud enforces HTTPS on all endpoints to prevent on-path attacks like packet sniffing. {% /cards_item %} {% cards_item href="/docs/advanced/security/https" title="TLS" %} Appwrite assigns TLS certificates on all Appwrite and user provided domains connected to Appwrite. {% /cards_item %} {% cards_item href="/docs/advanced/security/backups" title="Backups" %} Appwrite Cloud uses regular backups to prevent data loss and improve resiliency. {% /cards_item %} {% cards_item href="/docs/advanced/security/penetration-tests" title="Penetration tests" %} Appwrite employs regular third-party penetration tests to find vulnerabilities. {% /cards_item %} {% cards_item href="/docs/advanced/security/audit-logs" title="Audit logs" %} Appwrite provides detailed audit logs for each product to track and discover suspicious activity. {% /cards_item %} {% cards_item href="/docs/advanced/security/abuse-protection" title="Abuse protection" %} Appwrite protects against common abuse methods like DoS and brute-force attacks. {% /cards_item %} {% /cards %} ### Reporting vulnerabilities {% #reporting-vulnerabilities %} If you discover security vulnerabilities, please contact us at security@appwrite.io. Please avoid **posting a public issue** on GitHub or elsewhere online to prevent malicious actors from abusing the vulnerabilities before the Appwrite team has chance to patch the issue. --- ## Abuse protection https://appwrite.io/docs/advanced/security/abuse-protection Appwrite comes packaged with tools to protect against various forms of abuse, like brute force attacks, data scraping, and many other common forms of abuse. ### Rate limiting {% #rate-limiting %} Appwrite uses rate limits on some endpoints to avoid abuse or brute-force attacks against Appwrite's REST API. Each Appwrite route documentation has information about any rate limits that might apply to them. Rate limits limit the number of requests a user or IP can make against an API within a period of time. Rate limits help protect against brute force attacks against authentication endpoints and other forms of API abuse like [denial of service attacks](https://en.wikipedia.org/wiki/Denial-of-service_attack). {% arrow_link href="/docs/advanced/platform/rate-limits" %} Learn more about rate limits {% /arrow_link %} ### Cross-origin resource sharing (CORS) {% #CORS %} Appwrite limits who can make requests to Appwrite's APIs by default. This means that unless your app's domain is added to Appwrite as a platform, requests are rejected. By being explicit with the domains that are allowed to make requests to your Appwrite project, requests from JavaScript hosted on unknown domains will not be accepted. You can add new platforms by navigating to **Overview** > **Platforms** > **Add platform**. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} {% arrow_link href="/blog/post/cors-error" %} Learn more about debugging CORS errors {% /arrow_link %} ### DDoS protection {% #ddos-protection %} Appwrite Cloud's infrastructure is protected with always-on DDoS protection. Appwrite's DDoS protection operates across multiple layers, including the Network (layer 3), Transport (layer 4), and Application (layer 7) layers. This comprehensive protection safeguards Appwrite's infrastructure against volumetric attacks such as UDP floods, ICMP floods, TCP floods, and DNS reflection attacks, as well as protocol-layer attacks like SYN floods, BGP attacks, and ping-of-death attacks. Additionally, we have implemented advanced security rules that monitor traffic patterns to detect and block increased suspicious activity, ensuring the security and stability of your applications. --- ## Audit logs https://appwrite.io/docs/advanced/security/audit-logs All Appwrite products, like Authentication, Databases, Storage, Functions, and Messaging, provide detailed audit logs. Audit logs are important in detecting and responding to security incidents. Through audit logs, you can detect incidents through anomalous activities, trace the source of security incidents, and understand the scope of users affected so you can respond more quickly and effectively. ### Access audit logs {% #access-audit-logs %} You can access audit logs for different products under the **Activity** tab where applicable. Logs are available for tables, rows, and individual users. {% only_dark %} ![Project settings screen](/images/docs/advanced/security/dark/activity.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/advanced/security/activity.png) {% /only_light %} ### Format {% #format %} Audit log entries under the **Activity** tab has the following structure. Each entry describes an event. {% table %} - Column - Description --- - User - Name of the user that performed the event. --- - Event - The name of the [event](/docs/advanced/platform/events). --- - Location - The physical of the user when they performed the action. --- - IP - The IP of the user when they performed an action. --- - Date - The date and time of the event. {% /table %} ### Retention {% #retention %} GDPR data retention rules require any personal data that is collected or processed to be kept only for as long as data are required to achieve the purpose for which the information was collected. For this reason, audit logs are retained for only 7 days for Pro organizations and 1 hour for Free organizations. --- ## Authentication https://appwrite.io/docs/advanced/security/authentication Appwrite helps you implement secure authentication in your applications by using password hashing to protect passwords in storage. Appwrite also provides tools to help users pick better passwords, making them harder to break. {% partial file="auth-security.md" /%} --- ## Backups https://appwrite.io/docs/advanced/security/backups Preventing downtime and maintaining data availability is crucial for digital security. Appwrite provides both self-managed backups and automated disaster recovery backups. Self-managed backups are available for Pro plans and above. These backups allow you to: - Configure automatic backup policies - Initiate manual backups through the Console - Recover from accidental data deletion - Restore data to a previous point in time For detailed information about self-managed backup features, configuration options, and restoration procedures, visit our [Backup Documentation](/docs/products/databases/backups). For platform-wide disaster recovery, Appwrite maintains automated internal backups of the underlying infrastructure. Some databases support point-in-time recovery for the past 7 days, while others are automatically backed up every 4 hours and retained for 7 days. --- ## CCPA https://appwrite.io/docs/advanced/security/ccpa Appwrite is compliant with the California Consumer Privacy Act (CCPA). The CCPA is a privacy law that gives California residents more control over their personal information, helping ensure their data privacy rights. To confirm Appwrite's compliance with the CCPA, we have ensured the following rights for users: - **Right to know:** Appwrite users can request information about the personal data that is collected, shared, or sold. - **Right to delete:** Users can request that Appwrite delete their personal data, with certain exceptions when it comes to security. - **Right to opt-out:** Users can opt out of the sale or sharing of their personal data with third parties. - **Right to non-discrimination:** Appwrite does not discriminate against users who exercise their CCPA rights. - **Right to correct:** Users can correct inaccurate personal information held by Appwrite. - **Right to limit:** Users can limit how Appwrite uses and shares their sensitive personal information. Some of the measures that Appwrite has taken for compliance include: - CCPA references in the DPA for both customers and vendors. - A detailed summary of data subject rights under CCPA, with a commitment to assist customers in compliance. - Employee training on handling privacy-related inquiries and Appwrite's adherence to CCPA requirements. - Updates to Appwrite's data retention and deletion policies. - Revisions to the data breach and incident response policies. Please note that while Appwrite Cloud serves as a CCPA-compliant platform to handle data, it is the responsibility of developers to ensure that their application is also compliant with CCPA regulations. You can reach us at `privacy@appwrite.io` for more questions. --- ## Encryption https://appwrite.io/docs/advanced/security/encryption Other than applying encryption in [authentication](/docs/products/auth/security), [enforcing HTTPS](/docs/advanced/security/https), and [generating TLS certificate for domains](/docs/advanced/security/tls), Appwrite also uses encryption for Storage, and Databases to come. Encryption helps secure your files and data in storage. In the event that an attack happens and a malicious actor gains access to files or data, encrypted files and data cannot be deciphered, adding a further layer of protection. #### Storage {% #storage %} For storage, buckets can have its files encrypted. If enabled, files uploaded to the bucket that are smaller than 20MB will be encrypted in the storage provider. You can enable encryption by going to your bucket's **Settings** > **Security settings** > toggle **Encryption**. Files are encrypted with AES-128 in Galois/Counter Mode (GCM). #### Databases {% #databases %} Database columns support encryption for string columns. This feature is available on Pro plans and higher. When creating a string column in the UI, encryption can be enabled using a toggle option. Columns are encrypted with AES-128 in Galois/Counter Mode (GCM). {% info title="Querying encrypted columns" %} Note that encrypted columns cannot be queried. {% /info %} --- ## GDPR https://appwrite.io/docs/advanced/security/gdpr Appwrite is compliant with the European General Data Protection Regulation (GDPR). GDPR is an EU regulation that concerns data privacy and security in the European Union and the European Economic Area. By attesting that Appwrite is GDPR compliant, we have done the following. - Appwrite users will retain access to their personal information including the right to correct and delete it. - Impose the same rules upon the organization's sub-processors who assist in providing Appwrite's services as described in the Terms of Service (“ToS”). - Appwrite will notify users promptly about policy changes and/or data breaches. You can learn more in our [Privacy policy](https://appwrite.io/privacy) and [Cookie policy](https://appwrite.io/cookies). You can also reach us at `privacy@appwrite.io` for more questions. Appwrite has also implemented the following security measures to achieve technical compliance. - Appwrite implements a multi-layered security approach, integrating centralized IAM (Identity and Access Management) to regulate access to production resources. - Cloud security processes are employed for provisioning, configuring, monitoring, and accessing cloud resources. Changes in production environments follow a controlled process using Infrastructure as Code (IaC). - Industry-standard encryption protocols like TLS/SSL safeguard data transmitted over networks. Additionally, data stored in databases and file storage is secured using techniques like AES encryption. Key rotations are performed at regular intervals to ensure data security. - Appwrite performs regular security audits at the application and infrastructure layers to ensure compliance with industry-leading security standards and practices. Periodic vulnerability scans are also conducted on software dependencies and packages to mitigate against CVEs. ### DPA {% #dpa %} A DPA, or Data Processing Agreement, is a contract between a data controller and data processor concerning the rights and obligations of both parties when processing personal data. This agreement describes how Appwrite and sub-processors handle, secure, and transfer data, as well as outline rights and obligations of both Appwrite and you or your company when personal data is processed. You can find and sign a DPA in your organization's **Settings** > **Download DPA document**. {% only_dark %} ![Project settings screen](/images/docs/advanced/security/dark/dpa.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/advanced/security/dpa.png) {% /only_light %} Please note that while Appwrite Cloud serves as a GDPR-compliant platform to handle data, it is the responsibility of developers to ensure that their application is also compliant with CCPA regulations. --- ## HIPAA https://appwrite.io/docs/advanced/security/hipaa Appwrite is compliant with HIPAA (Health Insurance Portability and Accountability Act) regulations. HIPAA is an important regulation that protects patients' health data from being disclosed without consent or knowledge. If you're building apps that handle information that is considered [PHI (Personal Health Information)](https://privacyruleandresearch.nih.gov/pr_07.asp) for an U.S. user base, data must be stored in a HIPAA-compliant environment. To attain HIPAA compliance, we've taken extensive measures, ensuring that our practices align with the highest data protection standards. We have implemented robust measures to safeguard personal information, updating our policies, procedures, and infrastructure to meet the strict requirements of HIPAA regulations. - A strict data backup schedule. - An extended business continuity plan. - Data retention rights for individuals as outlined in our [Privacy Policy](https://appwrite.io/privacy). - Intrusion detection and penetration testing. - Encryption of data transmitted between Appwrite and users using Transport Layer Security (TLS) and HTTP Strict Transport Security, ensuring confidentiality both at rest and during transmission. - Access to environments containing customer data is strictly controlled, requiring authentication and authorization through multi-factor authentication (MFA). Appwrite safeguards personal information to the same extent it protects its own, complying with relevant privacy laws and regulations in the jurisdictions where its services are offered. #### Data retention Appwrite gives you full control over your data lifecycle. By default, Appwrite stores user and project data until you explicitly delete it. There's no automatic purging or TTL unless you configure it that way in your application logic or functions. If you're handling PHI (Protected Health Information), you can implement custom data retention policies using Appwrite Functions or database triggers to meet HIPAA requirements. #### Log access and retrieval Appwrite provides access to different types of logs depending on the context: - **API usage logs**: These are turned off by default, we can give you samples of the data on requests to help debug and troubleshoot issues. If you'd like to have those turned on constantly and transmitted to you or stored on a bucket, this is a separate addon we can provide. - **Function logs**: Each serverless function or hosted sites includes stdout and stderr logs you can access per execution. Those are retained for different periods per plan. - **Audit logs**: For users or teams with compliance needs, we provide structured audit logs covering authentication events, permission changes, and other relevant activities directly on your console, under an activity tab in the different products the platform offers. Those are retained for different periods per plan. Please note that while Appwrite Cloud serves as a HIPAA-compliant platform to handle data, it is the responsibility of developers to ensure that their application is also compliant with HIPAA regulations. --- ## HTTPS https://appwrite.io/docs/advanced/security/https Appwrite Cloud serves all endpoints over an HTTPS connection by default. Requests made through an unsecure HTTP connection will be redirected to. Redirected requests will show a `301` response status. ```http HTTP/1.1 301 Moved Permanently Content-Type: application/json Location: https://.cloud.appwrite.io/v1/ ``` Appwrite Cloud does not support HTTP, which is a common practice in modern development, because unencrypted HTTP traffic is dangerous and exposes sensitive user data to malicious attackers. ### Strict-Transport-Security {% #strict-transport-security %} Appwrite uses the [Strict-Transport-Security header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) to inform browsers that the website should only be accessed using HTTPS, further protecting against man-in-the-middle attacks such as protocol downgrade attacks and cookie hijacking. By enforcing HTTPS, Appwrite Cloud's endpoint will always be served over a secure connection, which helps protect users' data and privacy. ### Custom domains {% #custom-domains %} You can add a [custom domain](/docs/advanced/platform/custom-domains) to your Appwrite project so you can access Appwrite API endpoints on your own domain. Appwrite will [generate TLS certificates](/docs/advanced/security/tls) for your domain and enforce HTTPS communication. ### Function domains {% #function-domains %} Appwrite generates domains for Appwrite Functions so they can be executed through HTTPS requests. Appwrite also [generates TLS certificates](/docs/advanced/security/tls) for these domains to enforce HTTPS communication. --- ## Multi-factor Authentication https://appwrite.io/docs/advanced/security/mfa Multi-factor authentication (MFA) adds multiple layers of authentication to your Appwrite account. When MFA is enabled, a malicious actor needs to compromise multiple authentication factors to gain unauthorized access. Appwrite currently supports MFA using TOTP (Time-based One-Time Password) with an authenticator app. More factors of authentication will be added in the future. {% info title="Looking to add MFA to your app?" %} This page covers MFA for your Appwrite Console account. If you're looking to add MFA to your app, follow the [Multi-factor authentication journey](/docs/products/auth/mfa). {% /info %} ### Enable MFA {% #enable-mfa %} To enable MFA on your Appwrite account, navigate to your Appwrite Console > your account menu on the top right > **Your account** > **Multi-factor authentication**. {% only_dark %} ![Multi-factor authentication settings](/images/docs/advanced/security/dark/mfa.png) {% /only_dark %} {% only_light %} ![Multi-factor authentication settings](/images/docs/advanced/security/mfa.png) {% /only_light %} Toggle **Multi-factor authentication** to enable MFA for your account, then click **Add authentication factor**. {% only_dark %} ![Multi-factor authentication modal](/images/docs/advanced/security/dark/mfa-modal.png) {% /only_dark %} {% only_light %} ![Multi-factor authentication modal](/images/docs/advanced/security/mfa-modal.png) {% /only_light %} Scan the QR code with your authenticator app, then enter the code from your authenticator app to verify the setup. Make sure to save the recovery codes in a safe place, as they are the only way to access your account if you lose access to your authenticator app. --- ## PCI https://appwrite.io/docs/advanced/security/pci The Payment Card Industry Data Security Standard (PCI) is a standard that concerns the handling of credit card information, transactions, and payments. Appwrite uses [Stripe](https://stripe.com/en-se) to securely handle payments for Appwrite Pro and Scale plans. Stripe is a [PCI Service Provider Level 1](https://www.visa.com/splisting/searchGrsp.do?companyNameCriteria=stripe) provider with a strong [commitment to security and privacy](https://stripe.com/docs/security) that matches Appwrite's core values. {% info title="Handling payment information" %} If you're looking to add payment or subscription services to your apps built on Appwrite, we recommend that you **do not store credit card** information directly in Appwrite. You can consider using one of the [function templates](/docs/products/functions/quick-start) to use a third party service, such as Stripe to handle payment. {% /info %} --- ## Penetration tests https://appwrite.io/docs/advanced/security/penetration-tests Appwrite undertakes regular penetration testing and vulnerability assessments conducted by third-party agencies to attest our security standing. These penetration tests and vulnerability assessments are performed periodically. Penetration tests performed by a third-party helps identify vulnerabilities and suggest action plans to constantly improve Appwrite's security. Appwrite has processes for external and internal information security risk management that seek to identify, assess and address risks using a risk treatment plan to implement recommendations and decisions. The risk assessment methodologies utilized include pen-test practices. See the "Data Processing" addendum of [Appwrite's DPA](/docs/advanced/security/gdpr#dpa) for further details. --- ## SOC 2 https://appwrite.io/docs/advanced/security/soc2 SOC 2 refers to the Service Organization Control 2 standards. SOC 2 is a set of standards are designed to ensure that service providers like Appwrite securely manage data to protect the privacy of developers and users. SOC 2 is a set of standards defined by the American Institute of CPAs (AICPA) that assess organizations on the criteria of security, availability, processing integrity, confidentiality, and privacy. While SOC 2 compliance is voluntary, Appwrite is committed to safeguard the data of developers and their users and has implemented measures to achieve SOC 2 compliance. Appwrite's service commitments and system requirements were achieved based on the Trust Services Criteria relevant to security set forth in TSP Section 100, 2017. Outlined below are some of the key measures Appwrite implements to achieve SOC 2 compliance: - Appwrite commits to maintain system availability for access and utilization at a minimum of 99.99%, with exceptions made only for scheduled maintenance. - Any modifications to the IT environment are thoroughly documented, tested, and approved before implementation. - Data backup protocols and disaster recovery strategies are in place to fortify customer data protection and ensure seamless business operations in the face of unforeseen disasters. - Access control mechanisms and privilege management protocols ensure that only authorized personnel have access to systems, data, and resources. - Sensitive data is safeguarded through encryption protocols, both during transit and while at rest, enhancing overall data security. - Incident Response plans are in place to swiftly detect, address, and recover from any security breaches. - Appwrite oversees vendor management processes to ensure the security of third-party vendors and service providers who may access systems or data. Appwrite is committed to maintaining the highest standards of data security and privacy. By implementing these measures, Appwrite ensures that developers and their users's data is protected and secure. --- ## TLS https://appwrite.io/docs/advanced/security/tls Appwrite uses [Let's Encrypt](https://letsencrypt.org/) to auto-generate TLS certificates to ensure your API traffic is appropriately encrypted. Let's Encrypt is an open source and not-for-profit certificate authority provided by the Internet Security Research Group (ISRG) used to encrypt more than 363 million websites. TLS certificates are generated for all of the following. - Appwrite products and endpoints, like Databases, Storage, Authentication, Functions, Messaging, and all other endpoints. - [Custom domains](/docs/advanced/platform/custom-domains) that you configure for your Appwrite projects. - [Domains for Appwrite Functions](/docs/products/functions/domains), generated or user provided. TLS certificates are crucial to ensure all connections between your apps and Appwrite Cloud are encrypted. This protects your users from attack vectors like man-in-the-middle and eavesdropping attacks. --- ## Self-hosting https://appwrite.io/docs/advanced/self-hosting Appwrite was designed from the ground up with self-hosting in mind. You can install and run Appwrite on any operating system that can run a [Docker CLI](https://www.docker.com/products/docker-desktop). Self-hosted Appwrite instances can be configured flexibly with access to the same features found on Appwrite Cloud. {% info title="Upgrading from older versions" %} If you are migrating from an older version of Appwrite, you need to follow the [migration instructions](/docs/advanced/self-hosting/production/updates) {% /info %} ### Cloud vs Self-hosting {% #cloud-vs-self-hosting %} Choose the deployment method that fits your needs. | Feature | Appwrite Cloud | Self-hosting | |---------|---------------|--------------| | Setup | Zero setup | Manual setup required | | Maintenance | Fully managed | You manage updates and scaling | | Data control | Managed infrastructure | Full control over data location | | Compliance | Built-in compliance | Configure for your requirements | | Scaling | Automatic | Manual configuration | ### When to self-host {% #when-to-self-host %} Self-hosting is ideal when you need data control or have specific compliance requirements. **Consider self-hosting if:** - You want to manage your own infrastructure - You're a hobbyist or want to experiment with Appwrite in a playground environment - You need to develop against a local instance of Appwrite **Appwrite Cloud is recommended if:** - You want to focus on building features, not managing infrastructure - Your team lacks extensive DevOps experience - You expect self-hosting to save costs (it often costs more when factoring in time and expertise) {% info title="Enterprise self-hosting" %} For compliance features and dedicated support, explore [enterprise self-hosting solutions](/contact-us/enterprise). {% /info %} ### Quick start {% #quick-start %} The fastest way to get started with Appwrite self-hosting: 1. **Use a one-click deployment** - Choose from [marketplace installations](#one-click-deployments) for instant setup 2. **Or follow the manual installation** - Use our [Docker installation guide](/docs/advanced/self-hosting/installation) for custom setups 3. **Configure services** - Set up [email](/docs/advanced/self-hosting/configuration/email), [storage](/docs/advanced/self-hosting/configuration/storage), and other services ### Deployment options {% #deployment-options %} Choose the deployment method that best fits your needs: #### One-click deployments {% #one-click-deployments %} **Recommended:** Use these pre-configured marketplace apps for instant setup: {% table %} *   {% width=48 %} * Provider * Installation Link --- * {% only_dark %}{% icon_image src="/images/one-click/dark/digitalocean.svg" alt="DigitalOcean logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/one-click/digitalocean.svg" alt="DigitalOcean logo" size="m" /%}{% /only_light %} * DigitalOcean * [Click to install](https://marketplace.digitalocean.com/apps/appwrite) --- * {% only_dark %}{% icon_image src="/images/one-click/dark/gitpod.svg" alt="Gitpod logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/one-click/gitpod.svg" alt="Gitpod logo" size="m" /%}{% /only_light %} * Gitpod * [Click to install](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod) --- * {% only_dark %}{% icon_image src="/images/one-click/dark/akamai.svg" alt="Akamai logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/one-click/akamai.svg" alt="Akamai logo" size="m" /%}{% /only_light %} * Akamai Compute * [Click to install](https://www.linode.com/marketplace/apps/appwrite/appwrite/) --- * {% only_dark %}{% icon_image src="/images/one-click/dark/aws.svg" alt="AWS logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/one-click/aws.svg" alt="AWS logo" size="m" /%}{% /only_light %} * AWS Marketplace * [Click to install](https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6) {% /table %} #### Cloud platforms {% #cloud-platforms %} For custom deployments on major cloud providers: {% cards %} {% cards_item href="/docs/advanced/self-hosting/platforms/aws" title="Amazon Web Services" %} One-click AWS Marketplace deployment with custom configuration options. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/platforms/digitalocean" title="DigitalOcean" %} Marketplace installation with simple Droplet configuration. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/platforms/google-cloud" title="Google Cloud" %} Deploy using Cloud Run, Compute Engine, or other Google Cloud services. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/platforms/azure" title="Microsoft Azure" %} Deploy using Container Apps, Virtual Machines, or other Azure services. {% /cards_item %} {% /cards %} #### Platform-as-a-Service (PaaS) {% #paas-platforms %} Deploy Appwrite on modern PaaS platforms for simplified management: {% cards %} {% cards_item href="/docs/advanced/self-hosting/platforms/coolify" title="Coolify" %} Open-source platform for easy self-hosting with one-click deployments. {% /cards_item %} {% /cards %} ### Configuration {% #configuration %} After deployment, configure Appwrite to enable additional features: {% cards %} {% cards_item href="/docs/advanced/self-hosting/configuration/email" title="Email delivery" %} Set up SMTP providers for user verification, password recovery, and notifications. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/configuration/sms" title="SMS Delivery" %} Configure SMS providers for phone authentication and two-factor authentication. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/configuration/storage" title="Storage Backends" %} Connect external storage providers like AWS S3, Backblaze, or Wasabi. {% /cards_item %} {% cards_item href="/docs/advanced/self-hosting/configuration/functions" title="Functions Runtime" %} Enable serverless functions with custom runtimes and execution environments. {% /cards_item %} {% /cards %} ### Production readiness {% #production-readiness %} Ensure your Appwrite deployment is production-ready: #### Security considerations {% #security %} - **Environment Variables** - Secure sensitive configuration using environment variables - **TLS Certificates** - Enable HTTPS with automated certificate management - **Network Security** - Configure firewalls and security groups appropriately - **Access Control** - Implement proper authentication and authorization #### Performance optimization {% #performance %} - **Resource Scaling** - Monitor and scale CPU, memory, and storage resources - **Database Performance** - Optimize database connections and queries - **CDN Integration** - Use content delivery networks for static assets - **Load Balancing** - Distribute traffic across multiple instances #### Monitoring and maintenance {% #monitoring %} - **Health Checks** - Set up monitoring for service availability and performance - **Log Management** - Centralize and analyze application logs - **Backup Strategy** - Implement regular database and file backups - **Update Management** - Keep Appwrite updated with the latest releases ### Get started {% #get-started %} Ready to self-host Appwrite? Choose your preferred path: **One-Click Deploy** - Use a [marketplace installation](#one-click-deployments) for instant setup (recommended) **Manual Installation** - Follow our [Docker installation guide](/docs/advanced/self-hosting/installation) for custom setups **Cloud Platform** - [Choose a cloud platform](#cloud-platforms) for production hosting with custom configuration **Platform-as-a-Service** - Deploy on [Coolify](/docs/advanced/self-hosting/platforms/coolify) or similar platforms for simplified management --- ## Email delivery https://appwrite.io/docs/advanced/self-hosting/configuration/email Appwrite v0.7 and above come with support for easy integrations with 3rd party SMTP providers. In order for emails to work, you will need to set up proper SMTP configuration as described below. Because email deliverability can be both tricky and hard, it is often easier to delegate this responsibility to a 3rd-party SMTP provider. These providers help you abstract the complexity of passing SPAM filters by doing a lot of the advanced configuration and validation for you. In this document, you will learn how to connect a 3rd party SMTP provider like MailGun or SendGrid with Appwrite to help you get better email deliverability. {% info title="Setting up Appwrite Messaging?" %} This page describes how to setup messaging for your self-hosted Appwrite instance to send email verifications and magic URLs during login. If you are looking to send custom emails for promotions, news letters, and other purposes, view the [documentation for Appwrite Messaging](/docs/products/messaging) documentation. {% /info %} ### Environment variables {% #environment-variables %} At this stage, we assume that you have already installed Appwrite. If not, you can follow our [Self Hosting Guide](/docs/advanced/self-hosting) for the installation. Appwrite offers multiple environment variables to customize your server setup to your needs. To configure Appwrite to use your own SMTP server, you need to set the following environment variables in the hidden .env file that comes with your Appwrite installation. | Environment Variable | Description | Default Value | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------ | | `_APP_SMTP_HOST` | SMTP server host name address. Use an empty string to disable all mail sending from the server. | Empty string | | `_APP_SMTP_PORT` | SMTP server TCP port. | Empty | | `_APP_SMTP_SECURE` | SMTP secure connection protocol. Change to 'tls' if running on a secure connection. Valid values: empty, 'tls', 'ssl'. | Empty | | `_APP_SMTP_USERNAME` | SMTP server user name. | Empty | | `_APP_SMTP_PASSWORD` | SMTP server user password. | Empty | | `_APP_SYSTEM_EMAIL_ADDRESS` | Configured sender email address, seen by recipients. | "team@appwrite.io" | Here's a sample configuration if you're using SendGrid as your SMTP provider: ```sh _APP_SMTP_HOST=smtp.sendgrid.net _APP_SMTP_PORT=587 _APP_SMTP_SECURE=tls _APP_SMTP_USERNAME=YOUR-SMTP-USERNAME _APP_SMTP_PASSWORD=YOUR-SMTP-PASSWORD _APP_SYSTEM_EMAIL_ADDRESS=YOUR-SENDER-EMAIL ``` When using SendGrid, the SMTP username should be set to the literal string "apikey". {% partial file="update-variables.md" /%} ### Debugging {% #debugging %} If you are unable to send emails, there are several common issues to check. Follow these troubleshooting steps: #### Check email worker logs {% #check-logs %} The first place to look for errors is the **Appwrite Emails Worker** logs: ```sh docker compose logs -f appwrite-worker-mails ``` Look for error messages that might indicate authentication failures, network issues, or configuration problems. #### Verify SMTP configuration {% #verify-config %} Check your `.env` file configuration: 1. Ensure all SMTP environment variables are correctly set 2. Verify credentials are valid 3. Test your SMTP credentials independently using your provider's SDK or cURL requests #### Check authorized recipients {% #authorized-recipients %} Some SMTP providers have [authorized recipients](https://help.mailgun.com/hc/en-us/articles/217531258-Authorized-Recipients) restrictions in sandbox/development environments. Make sure the email recipient is added to your provider's authorized recipients list if you're using a sandbox account. #### Verify environment variables are loaded {% #verify-env %} Check if environment variables are properly set in the container: ```sh docker compose exec appwrite-worker-mails vars ``` If environment variables aren't loaded, rebuild your Appwrite stack: ```sh docker compose up -d --build --force-recreate ``` Now you can head over to your Appwrite Console, log out from your account, and try to recover your password or send invites to other team members from your Appwrite Console using your newly configured SMTP provider. --- ## Environment variables https://appwrite.io/docs/advanced/self-hosting/configuration/environment-variables Appwrite environment variables allow you to edit your server setup configuration and customize it. You can easily change the environment variables by changing them when running Appwrite using Docker CLI or Docker Compose. Updating your Appwrite environment variables requires you to edit your Appwrite `.env` file. Your Docker files should be located inside the "appwrite" folder at the location where you first run the Appwrite installation script. It's recommended to use the `.env` file as a central point for updating your Appwrite configuration rather than changing them directly in your `docker-compose.yml` file. After editing your `docker-compose.yml` or `.env` files, you will need to recreate your Appwrite stack by running the following compose command in your terminal: ```bash docker compose up -d ``` You can verify if the changes have been successfully applied by running this command: ```bash docker compose exec appwrite vars ``` ### General {% #general %} | Name | Description | |------|-------------| | `_APP_ENV` | Set your server running environment. By default, the var is set to 'development'. When deploying to production, change it to: 'production'. | | `_APP_LOCALE` | Set your Appwrite's locale. By default, the locale is set to 'en'. | | `_APP_OPTIONS_ABUSE` | Allows you to disable abuse checks and API rate limiting. By default, set to 'enabled'. To cancel the abuse checking, set to 'disabled'. It is not recommended to disable this check-in a production environment. | | `_APP_OPTIONS_ROUTER_FORCE_HTTPS` | **version >= 1.7.0** Allows you to force HTTPS connection to function and site domains. This feature redirects any HTTP call to HTTPS and adds the `Strict-Transport-Security` header to all HTTP responses. By default, set to `enabled`. To disable, set to `disabled`. This feature will work only when your ports are set to default 80 and 443. | | `_APP_OPTIONS_FORCE_HTTPS` | Deprecated since 1.7.0. Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the `Strict-Transport-Security` header to all HTTP responses. By default, set to `enabled`. To disable, set to `disabled`. This feature will work only when your ports are set to default 80 and 443. | | `_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS` | Deprecated since 1.7.0. Allows you to force HTTPS connection to function domains. This feature redirects any HTTP call to HTTPS and adds the `Strict-Transport-Security` header to all HTTP responses. By default, set to `enabled`. To disable, set to `disabled`. This feature will work only when your ports are set to default 80 and 443. | | `_APP_OPENSSL_KEY_V1` | This is your server private secret key that is used to encrypt all sensitive data on your server. Appwrite server encrypts all secret data on your server like webhooks, HTTP passwords, user sessions, and storage files. The var is not set by default, if you wish to take advantage of Appwrite encryption capabilities you should change it and make sure to **keep it a secret and have a backup for it**. | | `_APP_DOMAIN` | Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is 'localhost'. | | `_APP_DOMAIN_TARGET` | Deprecated since 1.7.0. A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite `_APP_DOMAIN` variable. The default value is 'localhost'. | | `_APP_DOMAIN_TARGET_CNAME` | **version >= 1.7.0** A domain that can be used as DNS CNAME record to point to instance of Appwrite server. The default value is 'localhost'. | | `_APP_DOMAIN_TARGET_A` | **version >= 1.7.0** An IPV4 that can be used as DNS A record to point to instance of Appwrite server. The default value is '127.0.0.1'. | | `_APP_DOMAIN_TARGET_AAAA` | **version >= 1.7.0** An IPv6 that can be used as DNS AAAA record to point to instance of Appwrite server. The default value is '::1'. | | `_APP_CONSOLE_WHITELIST_ROOT`| This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled. | | `_APP_CONSOLE_WHITELIST_EMAILS` | This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma. | | `_APP_CONSOLE_WHITELIST_IPS` | This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP. To enable/activate this option, pass a list of allowed IP addresses separated by a comma. | | `_APP_SYSTEM_EMAIL_NAME`| This is the sender name value that will appear on email messages sent to developers from the Appwrite console. The default value is: 'Appwrite'. You can use url encoded strings for spaces and special chars. | | `_APP_SYSTEM_EMAIL_ADDRESS` | This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is 'team@appwrite.io'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users' SPAM folders. | | `_APP_SYSTEM_RESPONSE_FORMAT` | Use this environment variable to set the default Appwrite HTTP response format to support an older version of Appwrite. This option is useful to overcome breaking changes between versions. You can also use the `X-Appwrite-Response-Format` HTTP request header to overwrite the response for a specific request. This variable accepts any valid Appwrite version. To use the current version format, leave the value of the variable empty. | | `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS` | This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload. | | `_APP_USAGE_STATS` | This variable allows you to disable the table and displaying of usage stats. This value is set to 'enabled' by default, to disable the usage stats set the value to 'disabled'. When disabled, it's recommended to turn off the Worker Usage container to reduce resource usage. | | `_APP_LOGGING_PROVIDER` | Deprecated since 1.6.0, use `_APP_LOGGING_CONFIG` with DSN value instead. This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of `sentry`, `raygun`, `appSignal`, `logOwl` to enable the logger. | | `_APP_LOGGING_CONFIG` | This variable allows you to enable logging errors to third party providers. This value is empty by default, set a DSN value to one of the following `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/`, , `logowl://SERVICE_TICKET@SERIVCE_HOST/` `raygun://RAYGUN_API_KEY/`, `appSignal://API_KEY/` to enable the logger. For versions prior `1.5.6` you can use the old syntax. Old syntax: If using Sentry, this should be `SENTRY_API_KEY;SENTRY_APP_ID`. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket. | | `_APP_USAGE_AGGREGATION_INTERVAL` | **(version >= 1.1.0)** Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to Database from TimeSeries data. The default value is 30 seconds. Reintroduced in 1.1.0. | | `_APP_USAGE_TIMESERIES_INTERVAL` | **(version >= 1.0.0)** Deprecated since 1.1.0 uses `_APP_USAGE_AGGREGATION_INTERVAL` instead. | | `_APP_USAGE_DATABASE_INTERVAL` | **(version >= 1.0.0)** Deprecated since 1.1.0 uses `_APP_USAGE_AGGREGATION_INTERVAL` instead. | | `_APP_WORKER_PER_CORE` | **(version >= 0.13.0)** Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance. | ### Redis {% #redis %} Appwrite uses a Redis server for managing cache, queues and scheduled tasks. The Redis env vars are used to allow Appwrite server to connect to the Redis container. | Name | Description | |--------------------------|-------------------------------------------------------------------------------------------------------| | `_APP_REDIS_HOST` | Redis server hostname address. Default value is: `redis`. | | `_APP_REDIS_PORT` | Redis server TCP port. Default value is: `6379`. | | `_APP_REDIS_USER` | Redis server user. This is an optional variable. Default value is an empty string. | | `_APP_REDIS_PASS` | Redis server password. This is an optional variable. Default value is an empty string.| ### Database {% #database %} Appwrite uses MariaDB to persist database data. The DB env vars are used to allow Appwrite server to connect to MariaDB. | Name | Description | |------------------------|--------------------------------------------------------------------------------------------------| | `_APP_DB_HOST` | MariaDB server host name address. Default value is: `mariadb`. | | `_APP_DB_PORT` | MariaDB server TCP port. Default value is: `3306`. | | `_APP_DB_SCHEMA` | MariaDB server database schema. Default value is: `appwrite`. | | `_APP_DB_USER` | MariaDB server user name. Default value is: `user`. | | `_APP_DB_PASS` | MariaDB server user password. Default value is: `password`. | | `_APP_DB_ROOT_PASS` | MariaDB server root password. Default value is: `rootsecretpassword`. | | ### SMTP {% #smtp %} Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container. If running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user's **spam folder**. | Name | Description | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `_APP_SMTP_HOST` | SMTP server host name address. Use an empty string to disable all mail sending from the server. The default value for this variable is an empty string. | | `_APP_SMTP_PORT` | SMTP server TCP port. Empty by default. | | `_APP_SMTP_SECURE` | SMTP secure connection protocol. Empty by default, change to 'tls' if running on a secure connection. | | `_APP_SMTP_USERNAME` | SMTP server user name. Empty by default. | | `_APP_SMTP_PASSWORD` | SMTP server user password. Empty by default. | ### Phone {% #phone %} | Name | Description | |---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `_APP_SMS_PROVIDER` | **version >= 0.15.0** Provider used for delivering SMS for Phone authentication. Use the following format: `sms://:@`. Ensure `` and `` are URL encoded if they contain any non-alphanumeric characters. Available providers are twilio, text-magic, telesign, msg91, and vonage. | | `_APP_SMS_FROM` | **version >= 0.15.0** Phone number used for sending out messages. Must start with a leading '+' and maximum of 15 digits without spaces (+123456789). | ### Storage {% #storage %} | Name | Description | |---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `_APP_STORAGE_LIMIT` | **version >= 0.7.0** Maximum file size allowed for file upload. The default value is 30MB. You should pass your size limit value in bytes. | | `_APP_STORAGE_PREVIEW_LIMIT` | **version >= 0.13.4** Maximum file size allowed for file image preview. The default value is 20MB. You should pass your size limit value in bytes. | | `_APP_STORAGE_ANTIVIRUS` | This variable allows you to disable the internal anti-virus scans. This value is set to 'disabled' by default, to enable the scans set the value to 'enabled'. Before enabling, you must add the ClamAV service and depend on it on main Appwrite service. | | `_APP_STORAGE_ANTIVIRUS_HOST` | **version >= 0.7.0** ClamAV server host name address. Default value is: 'clamav'. | | `_APP_STORAGE_ANTIVIRUS_PORT` | **version >= 0.7.0** ClamAV server TCP port. Default value is: '3310'. | | `_APP_STORAGE_DEVICE` | **version >= 0.13.0** Select default storage device. The default value is 'local'. List of supported adapters are 'local', 's3', 'dospaces', 'backblaze', 'linode' and 'wasabi'. | | `_APP_STORAGE_S3_ACCESS_KEY` | **version >= 0.13.0** AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console. | | `_APP_STORAGE_S3_SECRET` | **version >= 0.13.0** AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console. | | `_APP_STORAGE_S3_REGION` | **version >= 0.13.0** AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console. | | `_APP_STORAGE_S3_BUCKET` | **version >= 0.13.0** AWS S3 storage bucket. Required when storage adapter is set to S3 and using path-style requests (where the bucket is in the path). You can create buckets in your AWS console. If using virtual-hosted-style paths where the bucket is in the endpoint URL, this should be empty. | | `_APP_STORAGE_S3_ENDPOINT` | **version >= 1.7.0** Override the S3 endpoint to use an S3-compatible provider. This should just be the host (without 'https://'). If using virtual-hosted-style paths where the bucket is included in the endpoint (e.g., `bucket-name.s3.amazonaws.com`), `_APP_STORAGE_S3_BUCKET` should be empty. For path-style requests, the endpoint should not include the bucket name and `_APP_STORAGE_S3_BUCKET` should be set. | | `_APP_STORAGE_DO_SPACES_ACCESS_KEY` | **version >= 0.13.0** DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_SECRET` | **version >= 0.13.0** DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_REGION` | **version >= 0.13.0** DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console. | | `_APP_STORAGE_DO_SPACES_BUCKET` | **version >= 0.13.0** DigitalOcean spaces bucket. Required when storage adapter is set to DOSpaces. You can create spaces in your DigitalOcean console. | | `_APP_STORAGE_BACKBLAZE_ACCESS_KEY` | **version >= 0.14.2** Backblaze access key. Required when the storage adapter is set to Backblaze. Your Backblaze keyID will be your access key. You can get your keyID from your Backblaze console. | | `_APP_STORAGE_BACKBLAZE_SECRET` | **version >= 0.14.2** Backblaze secret key. Required when the storage adapter is set to Backblaze. Your Backblaze applicationKey will be your secret key. You can get your applicationKey from your Backblaze console. | | `_APP_STORAGE_BACKBLAZE_REGION` | **version >= 0.14.2** Backblaze region. Required when storage adapter is set to Backblaze. You can find your region info from your Backblaze console. | | `_APP_STORAGE_BACKBLAZE_BUCKET` | **version >= 0.14.2** Backblaze bucket. Required when storage adapter is set to Backblaze. You can create your bucket from your Backblaze console. | | `_APP_STORAGE_LINODE_ACCESS_KEY` | **version >= 0.14.2** Linode object storage access key. Required when the storage adapter is set to Linode. You can get your access key from your Linode console. | | `_APP_STORAGE_LINODE_SECRET` | **version >= 0.14.2** Linode object storage secret key. Required when the storage adapter is set to Linode ### Compute (Functions and Sites) {% #compute %} | **Name** | **Description** | |-------------------------------|-----------------| | `_APP_DOMAIN_FUNCTIONS` | A domain to use for function preview URLs. The default value is 'functions.localhost'. Setting to empty turns off function preview URLs. | | `_APP_DOMAIN_SITES` | **version >= 1.7.0** The domain to use for site preview URLs. The default value is 'sites.localhost'. Setting to empty turns off site URLs. | | `_APP_COMPUTE_SIZE_LIMIT` | **version >= 1.7.0** The maximum size of a function and site deployments in bytes. The default value is 30MB. | | `_APP_FUNCTIONS_SIZE_LIMIT` | Deprecated since 1.7.0. The maximum size deployment in bytes. The default value is 30MB. | | `_APP_FUNCTIONS_TIMEOUT` | **version >= 0.7.0** The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the function's settings or in appwrite.config.json. | | `_APP_COMPUTE_BUILD_TIMEOUT` | **version >= 1.7.0** The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 900 seconds. This is the global limit, timeout for individual functions and sites are configured in the function's or site's settings or in appwrite.config.json. | | `_APP_FUNCTIONS_BUILD_TIMEOUT`| Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds. | | `_APP_FUNCTIONS_CONTAINERS` | **version >= 0.7.0** Deprecated since 1.2.0. Runtimes now timeout by inactivity using `_APP_FUNCTIONS_INACTIVE_THRESHOLD`. | | `_APP_COMPUTE_CPUS` | **version >= 0.7.0** The maximum number of CPU core a single cloud function or site is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it's empty, CPU limit will be disabled. | | `_APP_FUNCTIONS_CPUS` | **version >= 0.7.0** Deprecated since 1.7.0. The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function or site error, which might result in an error. The default value is empty. When it's empty, CPU limit will be disabled. | | `_APP_COMPUTE_MEMORY` | **version >= 1.7.0** The maximum amount of memory a single function or site is allowed to use in megabytes. The default value is empty. When it's empty, memory limit will be disabled. | | `_APP_FUNCTIONS_MEMORY` | **version >= 0.7.0** Deprecated since 1.7.0. The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it's empty, memory limit will be disabled. | | `_APP_FUNCTIONS_MEMORY_SWAP` | **version >= 0.7.0** Deprecated since 1.2.0. High use of swap memory is not recommended to preserve harddrive health. | | `_APP_FUNCTIONS_RUNTIMES` | **version >= 0.8.0** This option allows you to enable or disable runtime environments for cloud functions. Disable unused runtimes to save disk space. To enable cloud function runtimes, pass a list of enabled environments separated by a comma. [Learn more about runtimes](/docs/products/functions/runtimes).| | `_APP_EXECUTOR_SECRET` | **version >= 0.13.0** The secret key used by Appwrite to communicate with the function executor. Make sure to change this! | | `_APP_EXECUTOR_HOST` | **version >= 0.13.0** The host used by Appwrite to communicate with the function executor! | | `_APP_EXECUTOR_RUNTIME_NETWORK`| **version >= 0.13.0** Deprecated with 0.14.0, use `OPEN_RUNTIMES_NETWORK` instead! | | `_APP_FUNCTIONS_ENVS` | **version >= 0.7.0** Deprecated with 0.8.0, use `_APP_FUNCTIONS_RUNTIMES` instead! | | `_APP_COMPUTE_INACTIVE_THRESHOLD` | **version >= 1.7.0** The minimum time a function or site must be inactive before it can be shut down and cleaned up. This feature is intended to clean up unused containers. Containers may remain active for longer than the interval before being shut down, as Appwrite only cleans up unused containers every hour. If no value is provided, the default is 60 seconds. | | `_APP_FUNCTIONS_INACTIVE_THRESHOLD`| **version >= 0.13.0** Deprecated since 1.7.0. The minimum time a function must be inactive before it can be shut down and cleaned up. This feature is intended to clean up unused containers. Containers may remain active for longer than the interval before being shut down, as Appwrite only cleans up unused containers every hour. If no value is provided, the default is 60 seconds. | | `DOCKERHUB_PULL_USERNAME` | **version >= 0.10.0** Deprecated with 1.2.0, use `_APP_DOCKER_HUB_USERNAME` instead! | | `DOCKERHUB_PULL_PASSWORD` | **version >= 0.10.0** Deprecated with 1.2.0, use `_APP_DOCKER_HUB_PASSWORD` instead! | | `DOCKERHUB_PULL_EMAIL` | **version >= 0.10.0** Deprecated since 1.2.0. Email is no longer needed. | | `OPEN_RUNTIMES_NETWORK` | **version >= 0.13.0** Deprecated with 1.2.0, use `_APP_FUNCTIONS_RUNTIMES_NETWORK` instead! | | `_APP_COMPUTE_RUNTIMES_NETWORK` | **version >= 1.7.0** The docker network used for communication between the executor and runtimes for sites and functions. | | `_APP_FUNCTIONS_RUNTIMES_NETWORK`| **version >= 1.2.0** Deprecated since 1.7.0. The docker network used for communication between the executor and runtimes. | | `_APP_DOCKER_HUB_USERNAME` | **version >= 1.2.0** The username for hub.docker.com. This variable is used to pull images from hub.docker.com. | | `_APP_DOCKER_HUB_PASSWORD` | **version >= 1.2.0** The password for hub.docker.com. This variable is used to pull images from hub.docker.com. | | `_APP_COMPUTE_MAINTENANCE_INTERVAL` | **version >= 1.7.0** Interval value containing the number of seconds that the executor should wait before checking for inactive runtimes of functions and sites. The default value is 3600 seconds (1 hour). | | `_APP_FUNCTIONS_MAINTENANCE_INTERVAL`| **version >= 1.4.0** Deprecated since 1.7.0. Interval value containing the number of seconds that the executor should wait before checking for inactive runtimes. The default value is 3600 seconds (1 hour). | | `_APP_SITES_TIMEOUT` | **version >= 1.7.0** The maximum number of seconds allowed as a timeout value when creating a new site. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the sites's settings or in appwrite.config.json. | | `_APP_SITES_RUNTIMES` | **version >= 1.7.0** This option allows you to enable or disable runtime environments for Sites. Disable unused runtimes to save disk space. To enable cloud site runtimes, pass a list of enabled environments separated by a comma. [Learn more about runtimes](/docs/advanced/self-hosting/sites#sites) | ### VCS (Version Control System) {% #vcs %} | **Name** | **Description** | |---------------------------------|-----------------------------------------------------------------------------------------------------------------| | `_APP_VCS_GITHUB_APP_NAME` | **version >= 1.4.0** - Name of your GitHub app. This value should be set to your GitHub application's URL. | | `_APP_VCS_GITHUB_PRIVATE_KEY` | **version >= 1.4.0** - GitHub app RSA private key. You can generate private keys from GitHub application settings. | | `_APP_VCS_GITHUB_APP_ID` | **version >= 1.4.0** - GitHub application ID. You can find it in your GitHub application details. | | `_APP_VCS_GITHUB_CLIENT_ID` | **version >= 1.4.0** - GitHub client ID. You can find it in your GitHub application details. | | `_APP_VCS_GITHUB_CLIENT_SECRET` | **version >= 1.4.0** - GitHub client secret. You can generate secrets in your GitHub application settings. | | `_APP_VCS_GITHUB_WEBHOOK_SECRET`| **version >= 1.4.0** - GitHub webhook secret. You can configure it in your GitHub application settings under webhook section. | ### Maintenance {% #maintenance %} | **Name** | **Description** | |-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `_APP_MAINTENANCE_INTERVAL` | **version >= 0.7.0** - Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is `86400` seconds (1 day). | | `_APP_MAINTENANCE_RETENTION_CACHE` | **version >= 1.0.0** - The maximum duration (in seconds) upto which to retain cached files. The default value is `2592000` seconds (30 days). | | `_APP_MAINTENANCE_RETENTION_EXECUTION` | **version >= 0.7.0** - The maximum duration (in seconds) upto which to retain execution logs. The default value is `1209600` seconds (14 days). | | `_APP_MAINTENANCE_RETENTION_AUDIT` | **version >= 0.7.0** - The maximum duration (in seconds) upto which to retain audit logs. The default value is `1209600` seconds (14 days). | | `_APP_MAINTENANCE_RETENTION_ABUSE` | **version >= 0.7.0** - The maximum duration (in seconds) upto which to retain abuse logs. The default value is `86400` seconds (1 day). | | `_APP_MAINTENANCE_RETENTION_USAGE_HOURLY` | The maximum duration (in seconds) upto which to retain hourly usage metrics. The default value is `8640000` seconds (100 days). | | `_APP_MAINTENANCE_RETENTION_SCHEDULES` | Schedules deletion interval (in seconds). | ### GraphQL {% #graphql %} | **Name** | **Description** | |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `_APP_GRAPHQL_MAX_BATCH_SIZE` | **version >= 1.2.0** - Maximum number of batched queries per request. The default value is 10. | | `_APP_GRAPHQL_MAX_COMPLEXITY` | **version >= 1.2.0** - Maximum complexity of a GraphQL query. One field adds one to query complexity. Lists multiply the complexity by the number of items requested. The default value is 250. | | `_APP_GRAPHQL_MAX_DEPTH` | **version >= 1.2.0** - Maximum depth of a GraphQL query. One nested field level adds one to query depth. The default value is 3. | ### Migrations {% #migrations %} | **Name** | **Description** | |-------------------------------------------------|---------------------------------------------------------------------------------------------------------| | `_APP_MIGRATIONS_FIREBASE_CLIENT_ID` | **version >= 1.4.0** - Google OAuth client ID. You can find it in your GCP application settings. | | `_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET` | **version >= 1.4.0** - Google OAuth client secret. You can generate secrets in your GCP application settings. | ### Assistant {% #assistant %} | **Name** | **Description** | |----------------------------------------|-------------------------------------------------------------------------| | `_APP_ASSISTANT_OPENAI_API_KEY` | **version >= 1.4.0** - OpenAI API key. You can find it in your OpenAI application settings. | --- ## Functions https://appwrite.io/docs/advanced/self-hosting/configuration/functions This guide covers how to configure functions in your self-hosted Appwrite instance. For GitHub repository integration with functions, see the [version control configuration](/docs/advanced/self-hosting/configuration/version-control). ### Configure function runtimes {% #functions %} Not all function runtimes are enabled by default. Enable the runtimes that you need and disable unused runtimes to save disk space on your server. To enable a runtime, add it to the `_APP_FUNCTIONS_RUNTIMES` environment variable as a comma-separated list. The example below would enable Dart 2.15, .NET 6.0, and Java 18 runtimes. ```bash _APP_FUNCTIONS_RUNTIMES=dart-2.15,dotnet-6.0,java-18.0 ``` You can find a full list of supported runtimes [here](/docs/products/functions/runtimes#available-runtimes). You can also configure the maximum timeout that can be set on individual Appwrite Functions. The maximum configurable timeout can be increased by changing the `_APP_FUNCTIONS_TIMEOUT` environment variable. This environment variable changes the configurable maximum but does not alter existing configurations of individual functions. {% partial file="update-variables.md" /%} ### SSL certificates for function domains {% #ssl-certificates %} Before setting up SSL certificates, ensure you have configured your DNS settings properly. You'll need to create a CNAME record that points your wildcard function domain (e.g. `*.functions.appwrite.myapp.com`) to your Appwrite domain. Appwrite does not handle certificates for function domains (e.g. `6772722a00331315adc3.functions.appwrite.myapp.com`) out of the box, since they require wildcard certificates. There are two ways to handle certificate generation. #### Manual certificate generation The simplest way to generate certificates for function domains is to use the Appwrite SSL command. ```bash docker compose exec appwrite ssl --domain="6772722a00331315adc3.functions.appwrite.myapp.com" ``` The certificate should be generated within a few seconds. If you encounter any issues, you can check the certificate worker logs. ```bash docker compose logs appwrite-worker-certificates ``` Note that you'll need to run this command for each function domain, and repeat it every time you create a new function. If you have many functions or frequently create new ones, consider using the automated certificate generation method below. #### Automated certificate generation For automated certificate generation, Appwrite uses Traefik's DNS Challenge feature. This is required for wildcard certificates (like `*.functions.appwrite.myapp.com`) because Let's Encrypt uses the DNS-01 challenge to validate wildcard domain ownership. ##### Using DNS challenge with DigitalOcean To configure Traefik for automated certificate generation with DigitalOcean, you need to modify your `docker-compose.yml`: 1. Add the following under the `traefik` service's `command` section. ```yaml command: # ... existing commands ... - --certificatesresolvers.digitalocean.acme.dnschallenge=true - --certificatesresolvers.digitalocean.acme.dnschallenge.provider=digitalocean - --certificatesresolvers.digitalocean.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS - --certificatesresolvers.digitalocean.acme.storage=/storage/certificates/digitalocean.json ``` 2. Add environment variables under the `traefik` service. ```yaml environment: - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN ``` 3. Add the following `labels` under the `appwrite` service. ```yaml labels: # ... existing labels ... - traefik.http.routers.appwrite_api_https.tls.certresolver=digitalocean - traefik.http.routers.appwrite_api_https.tls.domains[0].main=$_APP_DOMAIN_FUNCTIONS - traefik.http.routers.appwrite_api_https.tls.domains[0].sans=*.$_APP_DOMAIN_FUNCTIONS ``` 4. Ensure these environment variables are properly configured in your `.env` file before proceeding: - `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS` must be set to a valid email for Let's Encrypt notifications - `_APP_DOMAIN_FUNCTIONS` must be correctly set to your function domain (e.g., `functions.example.com`) - `_APP_DOMAIN_DO_TOKEN` must be set to a valid DigitalOcean API token (generate this in the DigitalOcean Console) 5. Apply the changes. ```bash docker compose up -d --force-recreate ``` ##### Troubleshooting DNS propagation If certificate generation fails, first check the Traefik logs to identify the specific issue. ```bash docker compose logs traefik ``` A common issue is DNS propagation delays. If the logs show DNS verification failures, you can configure longer timeouts in your `docker-compose.yml` under the `traefik` service. ```yaml environment: - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN - DO_POLLING_INTERVAL=1m - DO_PROPAGATION_TIMEOUT=1h ``` {% info title="Rate limits" %} Let's Encrypt has strict rate limits for certificate requests. If you encounter rate limit errors in the logs, you may need to wait a few hours before trying again. {% /info %} For other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers). --- ## Sites https://appwrite.io/docs/advanced/self-hosting/configuration/sites This guide covers how to configure sites in your self-hosted Appwrite instance. For GitHub repository integration with sites, see the [version control configuration](/docs/advanced/self-hosting/configuration/version-control). ### Configure sites runtimes {% #sites %} Not all site runtimes are enabled by default. Enable the runtimes that you need and disable unused runtimes to save disk space on your server. To enable a runtime, add it to the `_APP_SITES_RUNTIMES` environment variable as a comma-separated list. The three runtimes currently available for Sites are the Static, Node.js 22, and Flutter 3.29 runtimes. ```bash _APP_SITES_RUNTIMES=static-1,node-22,flutter-3.29 ``` You can also configure the maximum timeout that can be set on individual Appwrite Sites. The maximum configurable timeout can be increased by changing the `_APP_SITES_TIMEOUT` environment variable. This environment variable changes the configurable maximum but does not alter existing configurations of individual sites. {% partial file="update-variables.md" /%} ### Add an apex domain for sites {% #root-domain %} Appwrite allows you to add an apex domain to your instance's configuration that you can publicly expose your Site on. {% info title="What is an apex domain?" %} An apex domain, also known as a root domain, is the highest level of a domain name without any subdomains. For example, `myapp.com` is an apex domain, while `appwrite.myapp.com` is a subdomain of the apex domain. {% /info %} To add an apex domain, you must first configure either one of the following environment variables in your `.env` file: - `_APP_DOMAIN_TARGET_A`: Set this to the IPv4 address of your server. - `_APP_DOMAIN_TARGET_AAAA`: Set this to the IPv6 address of your server. Head to the **Domains** tab of your site, and add your apex domain (e.g., `myapp.com`) as a new domain pointed to the active deployment. Next, head to your DNS provider and create an A or AAAA record for your root domain pointing to the server's IP address. This will allow Appwrite to serve your sites from the apex domain. {% info title="DNS conflicts" %} Make sure to remove any pre-existing `A` and `AAAA` records in the DNS settings of your domain, as these can conflict with your current configuration. {% /info %} ### SSL certificates for sites domains {% #ssl-certificates %} Before setting up SSL certificates, ensure you have configured your DNS settings properly. You'll need to create a CNAME, A, or AAAA record that points your wildcard domain (e.g. `*.sites.appwrite.myapp.com`) to your Appwrite domain. Appwrite does not handle certificates for sites domains (e.g. `6772722a00331315adc3.sites.appwrite.myapp.com`) out of the box, since they require wildcard certificates. There are two ways to handle certificate generation. #### Manual certificate generation The simplest way to generate certificates for domains is to use the Appwrite SSL command. ```bash docker compose exec appwrite ssl --domain="6772722a00331315adc3.sites.appwrite.myapp.com" ``` The certificate should be generated within a few seconds. If you encounter any issues, you can check the certificate worker logs. ```bash docker compose logs appwrite-worker-certificates ``` Note that you'll need to run this command for each domain, and repeat it every time you create a new site. If you have many sites or frequently create new ones, consider using the automated certificate generation method below. #### Automated certificate generation For automated certificate generation, Appwrite uses Traefik's DNS Challenge feature. This is required for wildcard certificates (like `*.appwrite.myapp.com`) because Let's Encrypt uses the DNS-01 challenge to validate wildcard domain ownership. ##### Using DNS challenge with DigitalOcean To configure Traefik for automated certificate generation with DigitalOcean, you need to modify your `docker-compose.yml`: 1. Add the following under the `traefik` service's `command` section. ```yaml command: # ... existing commands ... - --certificatesresolvers.digitalocean.acme.dnschallenge=true - --certificatesresolvers.digitalocean.acme.dnschallenge.provider=digitalocean - --certificatesresolvers.digitalocean.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS - --certificatesresolvers.digitalocean.acme.storage=/storage/certificates/digitalocean.json ``` 2. Add environment variables under the `traefik` service. ```yaml environment: - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN ``` 3. Add the following `labels` under the `appwrite` service. ```yaml labels: # ... existing labels ... - traefik.http.routers.appwrite_api_https.tls.certresolver=digitalocean - traefik.http.routers.appwrite_api_https.tls.domains[0].main=$_APP_DOMAIN_TARGET_CNAME - traefik.http.routers.appwrite_api_https.tls.domains[0].sans=*.$_APP_DOMAIN_TARGET_CNAME ``` 4. Ensure these environment variables are properly configured in your `.env` file before proceeding: - `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS` must be set to a valid email for Let's Encrypt notifications - `_APP_DOMAIN_SITES` must be correctly set to your site domain (e.g., `sites.example.com`) - `_APP_DOMAIN_DO_TOKEN` must be set to a valid DigitalOcean API token (generate this in the DigitalOcean Console) 5. Apply the changes. ```bash docker compose up -d --force-recreate ``` ##### Troubleshooting DNS propagation If certificate generation fails, first check the Traefik logs to identify the specific issue. ```bash docker compose logs traefik ``` A common issue is DNS propagation delays. If the logs show DNS verification failures, you can configure longer timeouts in your `docker-compose.yml` under the `traefik` service. ```yaml environment: - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN - DO_POLLING_INTERVAL=1m - DO_PROPAGATION_TIMEOUT=1h ``` {% info title="Rate limits" %} Let's Encrypt has strict rate limits for certificate requests. If you encounter rate limit errors in the logs, you may need to wait a few hours before trying again. {% /info %} For other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers). --- ## SMS delivery https://appwrite.io/docs/advanced/self-hosting/configuration/sms Appwrite supports phone authentication, which allows users to create accounts and log in using SMS messages. Appwrite requires an SMS provider to be set up before using Phone authentication. {% info title="Setting up Appwrite Messaging?" %} This page describes how to setup messaging for your self-hosted Appwrite instance to send one-time passwords during phone login. If you are looking to send custom messages for promotions, reminders, and other purposes, view the [documentation for Appwrite Messaging](/docs/products/messaging) documentation. {% /info %} ### SMS providers {% #sms-providers %} Appwrite supports a growing list of SMS providers that you can choose from. Choose one from the list below and set up an account. {% table %} *   {% width=80 %} * SMS provider * Create account * Get credentials --- * {% icon icon="twilio" size="l" /%} * Twilio * [Website](https://www.twilio.com) * [Documentation](https://www.twilio.com/docs/iam/access-tokens#step-2-api-key) --- * {% icon icon="textmagic" size="l" /%} * Textmagic * [Website](https://www.textmagic.com) * [Documentation](https://www.textmagic.com/docs/api/start/#How-to-obtain-the-API-credentials) --- * {% icon icon="telesign" size="l" /%} * Telesign * [Website](https://www.telesign.com) * [Documentation](https://support.telesign.com/s/article/Find-Customer-ID-and-API-Key) --- * {% icon icon="msg91" size="l" /%} * MSG91 * [Website](https://msg91.com) * [Documentation](https://msg91.com/help/where-can-i-find-my-authentication-key) --- * {% icon icon="vonage" size="l" /%} * Vonage * [Website](https://www.vonage.ca/) * [Documentation](https://developer.vonage.com/en/account/secret-management) {% /table %} ### Environment variables {% #environment-variables %} You will need to configure these [environment variables](https://appwrite.io/docs/environment-variables#phone) and restart your Appwrite containers before you can use phone authentication. {% info title="URL encode" %} Ensure the values you insert in the `_APP_SMS_PROVIDER` placeholders are [URL encoded](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding) if they contain any non-alphanumeric characters. {% /info %} | Provider | \_APP\_SMS\_PROVIDER | \_APP\_SMS\_FROM | | -------- | -------------------------------------------------- | ----------------------- | | Twilio | `sms://:@twilio` | `` | | Textmagic| `sms://:@text-magic` | `` | | TeleSign | `sms://:@telesign` | `` | | MSG91 | `sms://:@msg91` | `` | | Vonage | `sms://:@vonage` | `` | {% partial file="update-variables.md" /%} --- ## Storage https://appwrite.io/docs/advanced/self-hosting/configuration/storage Appwrite's Storage Service can be configured to store files locally, or with self-hosted and cloud storage services. By default, Appwrite's Storage Service **stores files on your server's local storage**. If you expect large volumes of data or the need to have scalable data storage, you may choose to use a separate storage service. ### Available adapters {% #available-adapters %} Appwrite supports AWS S3, Digital Ocean Spaces, Backblaze, Akamai Object Storage, and Wasabi as storage adapters. Some of these services can be self-hosted, just like Appwrite. You can select which storage adapter to use by setting the `_APP_STORAGE_DEVICE` environment variable. Valid values are `local`, `s3`, `dospaces`, `backblaze`, `linode`, and `wasabi`. Each storage adapter requires its own set of additional environment variables to configure. {% arrow_link href="/docs/advanced/self-hosting/environment-variables" %} Learn more about storage environment variables {% /arrow_link %} ### Maximum file size {% #maximum-file-size %} The maximum size for a single file upload is controlled by the `_APP_STORAGE_LIMIT` environment variable, which defaults to 30 MB. [Learn more about environment variables](/docs/advanced/self-hosting/environment-variables). {% partial file="update-variables.md" /%} --- ## TLS Certificates https://appwrite.io/docs/advanced/self-hosting/configuration/tls-certificates Appwrite uses Let's Encrypt to auto-generate TLS certificates for your Appwrite instance to ensure your API traffic is appropriately encrypted. For Appwrite to properly generate certificates, a few conditions need to be met. 1. You need to use a public-facing domain with a known TLD pointing to your Appwrite instance. 2. Your `_APP_ENV` [environment variable](https://appwrite.io/docs/environment-variables) should be set for production mode. The default Appwrite setup comes with this predefined setting, so you should be OK unless you change it. 3. You need to ensure you have a valid email address set on `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS`. The default setup comes with `certs@appwrite.io` as the default value. While this address will work, it's recommended to change it to your own email. 4. Currently, Appwrite is using the [ACME](https://letsencrypt.org/docs/client-options/) HTTP challenge to issue an TLS certificate. This forces us to generate certificates for port 443 when the challenge itself is performed on port 80. At this point, other ports will not work. To overcome this limit, you can set Appwrite on a separate sub-domain or use your own certificate or proxy server in front of Appwrite. ### Debugging {% #debugging %} If you're still struggling with your certificates, check the Appwrite certificates worker log. You can do that with the following command: ```bash docker compose logs appwrite-worker-certificates ``` ### Generation cycle {% #generation-cycle %} Appwrite auto-generates a certificate for your main domain when you first visit it. If your browser shows an insecure connection warning, you must proceed to trigger certificate generation. The domain in environment variable `_APP_DOMAIN` is considered your main domain. If you didn't set this variable, the first domain you visit would be marked as the main domain for your Appwrite instance. Appwrite follows this concept of the main domain to prevent generating certificates for domains you don't own. Keep in mind that you can always add additional domains as **Custom Domains** in your project settings to enable certificate generation for any domain. Certificate renewal is done as a part of the Appwrite maintenance task. Unless modified with environment variable `_APP_MAINTENANCE_INTERVAL`, this task runs every 24 hours. During this task, Appwrite looks for certificates due for renewal and renews them. One maintenance cycle only attempts to renew up to 200 certificates to respect the Let's Encrypt API limit. Every Let's Encrypt certificate is valid for 90 days, but Appwrite starts to renew them 30 days before the expiration. ### Manual generation {% #manual-generation %} Since Appwrite generates and renews certificates automatically, a manual generation is seldom required. A manual generation can be useful when you hit the API limit and don't want to wait for the next maintenance cycle to renew the certificate. Use the following command to generate a certificate for your main domain: ```bash docker compose exec appwrite ssl ``` If you want to generate a certificate for a specific domain, pass it as a parameter into the command: ```bash docker compose exec appwrite ssl domain="api.myapp.com" ``` ### Development and localhost {% #development-and-localhost %} You can't issue a [signed certificate for localhost](https://letsencrypt.org/docs/certificates-for-localhost/). This is because nobody uniquely owns that hostname and not an Appwrite specific limitation, just the way the internet works. By default, Appwrite will issue a self-signed certificate that is good enough for development. When using a self-signed certificate, you should enable `client.setSelfSigned()` method in your SDK of choice. This will allow your application to trust and connect with your local Appwrite server. {% partial file="update-variables.md" /%} --- ## Version control https://appwrite.io/docs/advanced/self-hosting/configuration/version-control {% partial file="configure-github-app.md" /%} #### Apply configuration After creating your GitHub App, restart your Appwrite services to apply the configuration: ```bash docker compose up -d ``` #### Verify configuration To verify that your GitHub App is correctly configured: 1. Open the Appwrite Console and navigate to a project. 2. Go to either the Functions or Sites section. 3. Try creating a new Function or Site using GitHub as the source. 4. You should be prompted to install your GitHub App on your repositories. #### Troubleshooting If you encounter issues with your GitHub App integration: - Ensure your Appwrite instance is publicly accessible, as GitHub needs to send webhook events. - Check that the webhook URL is correctly formatted with your domain. - Verify the permissions granted to the GitHub App are correct. - Check the Appwrite logs for any errors related to GitHub integration: ```bash docker compose logs appwrite ``` {% partial file="update-variables.md" /%} --- ## Installation https://appwrite.io/docs/advanced/self-hosting/installation This guide will walk you through installing Appwrite on your server using Docker. Appwrite is designed to run on any operating system that supports Docker. ### System requirements {% #system-requirements %} Before installing Appwrite, ensure your system meets these minimum requirements: - **2 CPU cores** - **4GB of RAM** - **2GB of swap memory** - **Operating system** that supports Docker - **Docker Compose Version 2** ### Install with Docker {% #install-with-docker %} The easiest way to install Appwrite is using our Docker installer tool. This automated installer will guide you through the setup process. Before running the installation command, ensure you have [Docker CLI](https://www.docker.com/products/docker-desktop) installed on your host machine. #### Installation prompts {% #installation-prompts %} During setup, you'll be prompted to configure: 1. **HTTP and HTTPS ports** - Your Appwrite instance's HTTP and HTTPS ports. 2. **Secret key** - Your Appwrite instance's secret key used to encrypt sensitive data. 3. **Main hostname** - Your Appwrite instance's main hostname. Appwrite will generate a certificate using this hostname. 4. **DNS A record hostname** - Usually the same as your main hostname. #### Installation commands {% #installation-commands %} {% tabs %} {% tabsitem #unix title="macOS and Linux" %} Run the following command in your terminal: ```bash 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:1.8.1 ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} #### CMD ```cmd docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ appwrite/appwrite:1.8.1 ``` #### PowerShell ```powershell 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:1.8.1 ``` {% /tabsitem %} {% /tabs %} ### Manual installation {% #manual-installation %} For advanced users who prefer manual setup, you can install Appwrite using Docker Compose directly. #### Download configuration files {% #download-configuration %} Download the required configuration files: 1. Download [`docker-compose.yml`](/install/compose) 2. Download [`.env`](/install/env) 3. Create a directory named `appwrite` and move both files inside #### Configure environment {% #configure-environment %} Edit the `.env` file to customize your installation: - Update environment variables as needed - Set your desired configuration options - Ensure all required values are properly set #### Start Appwrite {% #start-appwrite %} Once configured, start your Appwrite stack: ```bash docker compose up -d --remove-orphans ``` ### Post-installation {% #post-installation %} After installation completes: 1. **Access the Console** - Navigate to your machine's hostname or IP address in your browser 2. **Create an account** - Sign up for your Appwrite account 3. **Create your first project** - Set up your development environment {% info title="Startup time" %} On non-Linux hosts, the server might take a few minutes to start after installation completes. This is normal behavior. {% /info %} ### Managing your installation {% #managing-installation %} #### Stop Appwrite {% #stop %} To stop your Appwrite containers: ```bash docker compose stop ``` #### Restart Appwrite {% #restart %} To restart your Appwrite containers: ```bash docker compose start ``` #### Uninstall Appwrite {% #uninstall %} To completely remove Appwrite and all its data: ```bash docker compose down -v ``` {% info title="Data loss warning" %} The uninstall command will permanently delete all your Appwrite data. Make sure to backup any important information before running this command. {% /info %} ### Next steps {% #next-steps %} After successfully installing Appwrite, you can: [Deploy on cloud platforms](/docs/advanced/self-hosting/platforms/aws) - Learn how to deploy on AWS, DigitalOcean, and other cloud providers [Configure services](/docs/advanced/self-hosting/configuration/email) - Set up email, SMS, storage, and other services [Production setup](/docs/advanced/self-hosting/production) - Prepare your installation for production use [Update Appwrite](/docs/advanced/self-hosting/production/updates) - Keep your installation up to date --- ## AWS deployment https://appwrite.io/docs/advanced/self-hosting/platforms/aws Deploy Appwrite on AWS using the pre-configured Marketplace app. ### One-click installation {% #one-click %} {% section #marketplace-install step=1 title="Launch from AWS Marketplace" %} 1. Visit the [Appwrite AWS Marketplace page](https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6) 2. Click **Continue to Subscribe** 3. Review and accept the subscription terms 4. Click **Continue to Configuration** 5. Choose your preferred region and software version 6. Click **Continue to Launch** {% /section %} {% section #configure-instance step=2 title="Configure your instance" %} 1. Choose **Launch through EC2** action 2. Select instance type: - **t3.medium** minimum (2 vCPU, 4 GB RAM) - **t3.large** or larger for production 3. Configure security group to allow: - HTTP (80) - HTTPS (443) - SSH (22) from your IP 4. Select your EC2 key pair 5. Click **Launch** {% /section %} {% section #complete-setup step=3 title="Complete setup" %} 1. Wait for instance to launch and pass status checks 2. Navigate to your instance's public IP address in a web browser 3. Complete the initial Appwrite setup wizard {% /section %} ### Custom installations {% #custom-installations %} For manual installations on AWS EC2, ECS, or other configurations, follow the [general installation guide](/docs/advanced/self-hosting/installation) which covers Docker setup and configuration for any Linux server. ### Production considerations {% #production %} For production deployments, optimization, and advanced AWS configuration, see the [production deployment guide](/docs/advanced/self-hosting/production). ### Next steps {% #next-steps %} After successful deployment: - [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services - [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads - [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date --- ## Azure deployment https://appwrite.io/docs/advanced/self-hosting/platforms/azure Deploy Appwrite on Microsoft Azure using Virtual Machines. ### Virtual Machines deployment {% #virtual-machines %} Azure Virtual Machines provide full control over your infrastructure where you can deploy Appwrite. Create a VM with at least 2 vCPU and 4 GB RAM, configure network security groups to allow HTTP/HTTPS traffic, then follow the [general installation guide](/docs/advanced/self-hosting/installation) for Docker setup. ### Custom installations {% #custom-installations %} For manual installations on Azure Virtual Machines or other configurations, follow the [general installation guide](/docs/advanced/self-hosting/installation) which covers Docker setup and configuration for any Linux server. ### Production considerations {% #production %} For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production). ### Next steps {% #next-steps %} After successful deployment: - [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services - [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads - [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date --- ## Coolify https://appwrite.io/docs/advanced/self-hosting/platforms/coolify Coolify is an open-source, self-hosted platform that simplifies application deployment through an intuitive interface and automated workflows. With its one-click deployment feature, you can quickly deploy various services, including Appwrite's comprehensive backend solution. To explore the full range of supported services, visit the [Coolify Docs](https://coolify.io/docs/services/appwrite). This guide will walk you through setting up Appwrite on your Coolify instance and provide necessary troubleshooting tips. ### Prerequisites {% #prerequisites %} Before starting, ensure your server meets the [minimum requirements](/docs/advanced/self-hosting#system-requirements) for hosting Appwrite with Coolify. ### Installation {% #installation %} Install Coolify on your server using the command below: ```bash curl -sSL https://coolify.io/install | bash ``` Once the installation is complete, open the Coolify dashboard in your web browser. 1. Sign up for a new account. 2. Click the **Create Project** button to start a new project. 3. Select or create an environment type. By default, a **production** environment is already available. 4. Click **Add new resource**. 5. Search for and select **Appwrite** from the list of services. The configuration fields will be pre-filled with the recommended settings, but you can customize them. When ready, click the **Deploy** button to initiate the deployment process. After deployment, access your Appwrite instance's console by clicking the console link in the **Links** section of the Appwrite service. ### Configuration {% #configuration %} Coolify automatically handles most configurations, like the environment variables. You can modify these variables and redeploy the service to apply the changes. However, to enable additional features, you may need to configure some environment variables manually. **Assistant** To enable the assistant, which allows you to generate code snippets and assist with documentation for your Appwrite project, set your OpenAI API key: ```bash _APP_ASSISTANT_OPENAI_API_KEY=sk-1234567890 ``` **SMS Notifications** To enable SMS-based OTP authentication, configure the following environment variables: ```bash _APP_SMS_FROM=123456789 _APP_SMS_PROVIDER=sms://username:password@mock ``` **Email Notifications** To enable email notifications, configure these environment variables: ```bash _APP_SMTP_HOST=smtp.example.com _APP_SMTP_PASSWORD=password _APP_SMTP_PORT=587 _APP_SMTP_SECURE=true _APP_SMTP_USERNAME=username ``` **GitHub Integration** To enable GitHub authentication for your Appwrite console, set these environment variables: ```bash _APP_VCS_GITHUB_APP_ID _APP_VCS_GITHUB_APP_NAME _APP_VCS_GITHUB_CLIENT_ID _APP_VCS_GITHUB_CLIENT_SECRET _APP_VCS_GITHUB_PRIVATE_KEY ``` The [Github Docs](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) provide more information on configuring your Github app. ### Troubleshooting {% #troubleshooting %} 1. **Site redirected you too many times** If you encounter the error `ERR_TOO_MANY_REDIRECTS` with the default Coolify configuration, turn off the **Strip Prefixes** option in the **Settings** page for the Appwrite console and Appwrite Realtime service. If you're using Cloudflare and the issue persists, update the SSL/TLS encryption setting to Full in the Cloudflare dashboard. From the dashboard, navigate to `Your Domain` -> `SSL/TLS` -> `Overview` and change the setting to Full. --- ## DigitalOcean deployment https://appwrite.io/docs/advanced/self-hosting/platforms/digitalocean Deploy Appwrite on DigitalOcean using the pre-configured Marketplace app. ### One-click installation {% #one-click %} {% section #marketplace-install step=1 title="Create marketplace Droplet" %} 1. Visit the [Appwrite Marketplace page](https://marketplace.digitalocean.com/apps/appwrite) 2. Click **Create Appwrite Droplet** 3. Choose your configuration: - **Plan**: Minimum 4GB RAM recommended - **Region**: Select closest to your users - **SSH keys**: Add your SSH key for access 4. Click **Create Droplet** {% /section %} {% section #marketplace-setup step=2 title="Complete setup" %} 1. Wait for Droplet provisioning to complete 2. Navigate to your Droplet's IP address in a web browser 3. Complete the initial setup wizard following the prompts {% /section %} ### Custom installations {% #custom-installations %} For manual installations on DigitalOcean Droplets or other configurations, follow the [general installation guide](/docs/advanced/self-hosting/installation) which covers Docker setup and configuration for any Linux server. ### Production considerations {% #production %} For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production). ### Next steps {% #next-steps %} After successful deployment: - [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services - [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads - [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date --- ## Google Cloud deployment https://appwrite.io/docs/advanced/self-hosting/platforms/google-cloud Deploy Appwrite on Google Cloud Platform using Compute Engine virtual machines. ### Compute Engine deployment {% #compute-engine %} Google Cloud Compute Engine provides virtual machines where you can deploy Appwrite with full control over the infrastructure. Create a VM instance with at least 2 vCPU and 4 GB RAM, configure firewall rules to allow HTTP/HTTPS traffic, then follow the [general installation guide](/docs/advanced/self-hosting/installation) for Docker setup. ### Custom installations {% #custom-installations %} For manual installations on Compute Engine VMs or other Google Cloud services, follow the [general installation guide](/docs/advanced/self-hosting/installation) which covers Docker setup and configuration for any Linux server. ### Production considerations {% #production %} For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production). ### Next steps {% #next-steps %} After successful deployment: - [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services - [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads - [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date --- ## Preparation https://appwrite.io/docs/advanced/self-hosting/production Appwrite's default setup is designed to help you start building quickly. To succeed with Appwrite in a production environment, you should follow key concepts and best practices outlined in this section. This guide assumes you have some basic understanding of Docker and Docker Compose command-line tools. ### Production checklist {% #production-checklist %} Before deploying Appwrite to production, ensure you have configured: - **[Security](/docs/advanced/self-hosting/production/security)** - Implement essential security practices - **[Scaling](/docs/advanced/self-hosting/production/scaling)** - Configure horizontal and vertical scaling for your containers - **[Rate limits](/docs/advanced/self-hosting/production/rate-limits)** - Enable rate limiting to protect against abuse - **[Email delivery](/docs/advanced/self-hosting/production/emails)** - Set up reliable SMTP for production email delivery - **[Error monitoring](/docs/advanced/self-hosting/production/errors)** - Configure error tracking and logging - **[Backups](/docs/advanced/self-hosting/production/backups)** - Set up regular database and storage backups - **[Updates](/docs/advanced/self-hosting/production/updates)** - Plan for version updates and migrations - **[Debugging](/docs/advanced/self-hosting/production/debugging)** - Set up monitoring and debugging tools ### Key principles {% #key-principles %} When deploying Appwrite in production: - **Security first** - Always use HTTPS, secure your console access, and implement proper authentication - **Monitor everything** - Set up logging, error tracking, and performance monitoring - **Plan for scale** - Design your infrastructure to handle growth - **Backup regularly** - Implement automated backup strategies for data protection - **Stay updated** - Keep Appwrite and dependencies up to date with security patches {% arrow_link href="/docs/advanced/self-hosting/production/scaling" %} Start with scaling configuration {% /arrow_link %} --- ## Backups https://appwrite.io/docs/advanced/self-hosting/production/backups {% info title="Looking for automated backups?" %} Appwrite Cloud offers automated [Backups as a Service](/docs/products/databases/backups) with scheduling and one-click restore. For self-hosted instances, you'll need to implement manual backup procedures as outlined on this page. {% /info %} Self-hosted Appwrite requires manual backup procedures to protect your data. ### What to back up {% #what-to-backup %} Your Appwrite installation has several components that need backing up: 1. **Database** - User data, documents, and configuration 2. **Storage volumes** - Uploaded files and function code 3. **Environment variables** - Configuration in `.env` 4. **System snapshots** - Complete server state (alternative approach) ### Database backups {% #database-backup %} Appwrite uses MariaDB. Use `mysqldump` for most installations: ```bash ### Create database backup (all databases) docker compose exec mariadb sh -c 'exec mysqldump --all-databases --add-drop-database --single-transaction --routines --triggers -uroot -p"$MYSQL_ROOT_PASSWORD"' > ./dump.sql ### Restore (fresh installation only) docker compose exec -T mariadb sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < dump.sql ``` {% info title="Fresh installation only" %} Only restore to fresh Appwrite installations to avoid data corruption. {% /info %} For large databases, consider `mariabackup` for physical backups. ### Storage volume backups {% #storage-backup %} Shut down Appwrite before backing up volumes to avoid data inconsistency. Appwrite uses these Docker volumes: - `appwrite-uploads` - User files - `appwrite-functions` - Function code - `appwrite-builds` - Build artifacts - `appwrite-sites` - Static sites - `appwrite-certificates` - SSL certificates - `appwrite-config` - Configuration - `appwrite-cache` and `appwrite-redis` - Cache data - `appwrite-mariadb` - Database files #### Backup methods **Docker volume backup:** ```bash ### Backup volume docker run --rm -v volume_name:/data -v $(pwd)/backup:/backup ubuntu tar czf "/backup/volume_name.tar.gz" -C /data . ### Restore volume docker run --rm -v volume_name:/data -v $(pwd)/backup:/backup ubuntu tar xzf "/backup/volume_name.tar.gz" -C /data ``` **Direct copy:** ```bash docker volume inspect volume_name sudo cp -a /var/lib/docker/volumes/volume_name/_data /backup/volume_name_backup ``` {% info title="External storage" %} For S3/GCS/Azure storage, use your provider's native backup tools. {% /info %} ### Environment variables {% #env-backup %} Back up your `.env` file containing configuration and secrets: ```bash cp .env .env.backup.$(date +"%Y%m%d") ``` {% info title="Critical variable" %} The `_APP_OPENSSL_KEY_V1` encrypts your data. Copy this exact value when restoring, or encrypted data becomes inaccessible. {% /info %} Store `.env` backups securely due to sensitive data. ### System snapshots {% #system-snapshots %} As an alternative to individual backups, snapshot your entire server: - **AWS EC2:** Actions > Image > Create Image - **GCP/Azure/DigitalOcean:** Use provider snapshot features System snapshots capture complete server state and enable fast recovery, but use more storage than selective backups. ### Best practices {% #best-practices %} #### Automation **Schedule backups** with cron jobs or cloud automation: ```bash ### Daily database backup at 2 AM 0 2 * * * /path/to/backup-script.sh ``` **Follow 3-2-1 rule:** 3 copies, 2 different media, 1 offsite. **Monitor backup jobs** and set alerts for failures. #### Third-party tools For production environments: - **Restic** - Cross-platform backup with encryption - **Borg** - Deduplicating backup program - **Cloud provider tools** - AWS/Azure/GCP backup services - **Third-party backup services** - Automated backup solutions #### Disaster recovery Define your requirements: - **RPO (Recovery Point Objective)** - Acceptable data loss window - **RTO (Recovery Time Objective)** - Acceptable downtime window **Test restores quarterly** to verify backup integrity. Keep backups **offsite** and **encrypted**. Document recovery procedures and update contact information. #### Security - Encrypt backup files - Restrict backup storage access - Audit backup systems regularly - Meet compliance requirements for your industry --- ## Debug https://appwrite.io/docs/advanced/self-hosting/production/debugging Appwrite comes with a few built-in tools and methods that easily debug and investigate issues on your Appwrite stack environment. ### Doctor CLI {% #doctor-cli %} The doctor CLI helps you validate your server health and best practices. Using the Doctor CLI, you can verify your server configuration for best practices, validate your Appwrite stack connectivity and storage read and write access, and available storage space. To run the Doctor check, simply run the following command from your terminal. You might need to replace 'appwrite' with your Appwrite Docker container ID. To find out what's your container ID, you can run `docker ps` command (more on that, in the next section). ```bash docker exec appwrite doctor ``` ### Logs {% #logs %} Checking your Appwrite containers can be a great way to pinpoint where and what exactly happens inside your Appwrite services. You can list your Appwrite containers using the following command in your terminal: ```bash docker ps ``` The output of this command will show you a list of all your running Docker containers, their ID's, uptime, and open ports. You can use each container ID to get a list of all the container `stdout` and `stderr` logs by using the following command: ```bash docker logs [CONTAINER-ID] ``` ### Status codes {% #status-codes %} Appwrite uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, invalid input, etc.). Codes in the 5xx range indicate an error with the Appwrite server, but these are rare. {% arrow_link href="/docs/advanced/platform/response-codes" %} Learn more about Appwrite status codes {% /arrow_link %} ### Development mode {% #development-mode%} When moving to dev mode, your server will produce much more verbose error messages. Instead of getting a general 500 error, you'll be able to view the exact error that happened on the server, debug the issue further or [report it to the Appwrite team](https://github.com/appwrite/appwrite/issues/new?body=500%20Error). To change your dev environment, edit your server `_APP_ENV` environment variable from 'production' to 'development' in your `.env` file located in the `appwrite` directory in the location where you first installed Appwrite. ```text _APP_ENV=development _APP_OPENSSL_KEY_V1=your-secret-key _APP_DOMAIN=localhost ``` {% partial file="update-variables.md" /%} --- ## Email delivery https://appwrite.io/docs/advanced/self-hosting/production/emails Sending emails is hard. There are a lot of spam rules and configurations to master in order to set up a functional SMTP server. While it is okay to use a self-hosted SMTP server during development, you should use a third-party SMTP provider for production so your email doesn't get labeled as spam. You can [change Appwrite's SMTP settings](/docs/advanced/self-hosting/configuration/email) and credentials to any 3rd party provider you like that supports SMTP integration using our Docker environment variables. Most SMTP providers offer a decent free tier to get started with. --- ## Error monitoring https://appwrite.io/docs/advanced/self-hosting/production/errors By default, your Appwrite installation comes with error reporting turned off. You can [enable dev mode](/docs/advanced/self-hosting/production/debugging#development-mode) to get access to more verbose error logs and stack traces. In production, it is highly recommended to turn error reporting off. To do so, make sure the Appwrite container environment variable `_APP_ENV` is set to `production` and not `development`. To monitor errors in production, configure the `_APP_LOGGING_CONFIG` environment variable with your provider's DSN. The supported DSN formats are: - Sentry: `sentry://PUBLIC_KEY@HOST:PORT/PROJECT_ID` - LogOwl: `logowl://SERVICE_TICKET@SERVICE_HOST/` - Raygun: `raygun://RAYGUN_API_KEY/` - AppSignal: `appsignal://API_KEY/` --- ## Rate limits https://appwrite.io/docs/advanced/self-hosting/production/rate-limits If you disabled rate limits during development, make sure you re-enable them when moving to production environments. Rate limiting can be enabled by setting the `_APP_OPTIONS_ABUSE` environment variable to `enabled`. Rate limits are an important mechanism to protect your app. Without rate limits, malicious actors can spam your APIs to perform [denial-of-service type attacks](https://en.wikipedia.org/wiki/Denial-of-service_attack) or brute-force user passwords. ### How rate limits work {% #how-rate-limits-work %} Rate limits in self-hosted Appwrite apply differently depending on how you're accessing the API: - **Client SDKs**: Rate limits apply to all requests from client applications - **Server SDKs with API keys**: Rate limits do not apply when using API keys {% arrow_link href="/docs/advanced/platform/rate-limits" %} Learn more about how rate limits work {% /arrow_link %} ### Abuse log retention {% #abuse-logs %} Configure how long abuse attempt logs are retained using the `_APP_MAINTENANCE_RETENTION_ABUSE` environment variable. The default value is `86400` seconds (1 day). ```bash _APP_MAINTENANCE_RETENTION_ABUSE=86400 ``` Shorter retention periods reduce storage usage, while longer periods provide better security audit trails. ### Development vs production {% #development-vs-production %} For development environments, you can temporarily disable rate limits to avoid interruptions during testing: ```bash _APP_OPTIONS_ABUSE=disabled ``` **Important**: Always re-enable rate limits before deploying to production by setting: ```bash _APP_OPTIONS_ABUSE=enabled ``` {% arrow_link href="/docs/advanced/self-hosting/configuration/environment-variables" %} Learn more about environment variables {% /arrow_link %} --- ## Scaling https://appwrite.io/docs/advanced/self-hosting/production/scaling Appwrite is built with scalability in mind. Appwrite can scale both horizontally and vertically. Each Appwrite instance is composed of many containers, each with its unique job. Appwrite's functions and worker containers are stateless. To scale them, all you need is to replicate them and set up a load balancer to distribute their load. If you decide to set up a load balancer to scale a container, make sure **all** communication are routed through the load balancer and not directly to the replicated containers. You can configure communicating between Appwrite containers using Docker environment variables. Two Appwrite containers are stateful. The MariaDB and Redis containers are used for storing data, cache and pub/sub messaging, and usage stats, respectively. To scale these containers, set up a standard cluster (same as you would with any other app using these technologies) according to your needs and performance. ### Performance considerations {% #performance-considerations %} You may want to adjust the `_APP_WORKER_PER_CORE` environment variable to optimize worker processes per CPU core based on your hardware: ```bash _APP_WORKER_PER_CORE= ``` This setting affects the API, Realtime, and Executor containers and can be tuned according to your specific hardware specifications and workload requirements. The default value is 6. ### Resource management considerations {% #resource-management %} When scaling Docker containers, consider implementing resource limits and monitoring: #### Log rotation Consider configuring log rotation to prevent disk space issues: ```yaml x-logging: &x-logging logging: driver: 'json-file' options: max-file: '' max-size: '' ``` #### Redis memory management You may want to set memory limits for Redis to prevent out-of-memory issues: ### Monitoring considerations {% #monitoring %} - **Health checks**: Consider implementing health check endpoints for services - **Resource monitoring**: Monitor CPU, memory, and disk usage as needed - **Log aggregation**: Centralized logging can help with debugging scaled deployments - **Alert thresholds**: Consider setting alerts for high resource usage --- ## Security https://appwrite.io/docs/advanced/self-hosting/production/security Securing your self-hosted Appwrite instance is crucial to protect your data and infrastructure. This guide covers the essential security configurations and requirements for production Appwrite deployments. ### Encryption {% #encryption %} Appwrite does not generate a unique encryption key during a default setup. This key encrypts your files and sensitive data like webhook passwords or API keys to keep them secure. To take advantage of this feature, you must generate a unique key and set it as the value of the `_APP_OPENSSL_KEY_V1` environment variable. You **must** set `_APP_OPENSSL_KEY_V1` immediately after installation of a production Appwrite instance. Changing the `_APP_OPENSSL_KEY_V1` variable will cause the loss of existing passwords, OAuth secrets, and API keys. Make sure to keep this key in a safe place and never make it publicly accessible. {% info title="Best practice" %} You should always prefer **HTTPS** over HTTP in production environments. This keeps your APIs secure and prevents any redirects from interfering with your requests. You can force the use of HTTPS with the [_APP_OPTIONS_FORCE_HTTPS](/docs/advanced/self-hosting/environment-variables) environment variable. {% /info %} ### Console access {% #console-access %} Appwrite provides three different methods to limit access to your Appwrite Console. 1. Whitelist a group of developers by IP using the `_APP_CONSOLE_WHITELIST_IPS` environment variable. 2. Whitelist a group of developers by email using the `_APP_CONSOLE_WHITELIST_EMAILS` environment variable. 3. Only the root user can signup. All other developers must be added through invitations. This is configured using the `_APP_CONSOLE_WHITELIST_ROOT` environment variable. By default, only the first user can sign up on the Appwrite instance's dashboard. All other users must be added to the dashboard through invitation. {% arrow_link href="/docs/advanced/self-hosting/environment-variables" %} Learn more about environment variables {% /arrow_link %} ### Security auditing {% #security-auditing %} In addition to the security practices mentioned, it is highly recommended to do regular audits to identify and fix potential security vulnerabilities and performance issues. You can use third-party tools and services that specialize in these areas. These tools can automatically check for vulnerabilities and even offer real-time monitoring. {% partial file="update-variables.md" /%} --- ## Updates and migrations https://appwrite.io/docs/advanced/self-hosting/production/updates To upgrade your Appwrite server from an older version, you should use the Appwrite migration tool *after you have installed the new version*. The migration tool will adjust your Appwrite data to the new version's structure to make sure your Appwrite data is compatible with any internal changes. You can upgrade to a newer patch version without running the migration unless the [release notes](https://github.com/appwrite/appwrite/releases) indicate a migration is required. For example, you can upgrade from [`1.6.0`](https://github.com/appwrite/appwrite/releases/tag/1.6.0) to [`1.6.1`](https://github.com/appwrite/appwrite/releases/tag/1.6.1) without running the migrate command, but upgrading from `1.6.0` to `1.6.2` or later will require the migrate command because [`1.6.2`](https://github.com/appwrite/appwrite/releases/tag/1.6.2) requires a migration. If you're trying to migrate to a newer minor version, you should upgrade to each minor version's latest patch. For example, if you're upgrading from `1.5.1` to `1.7.4` you should upgrade to: 1. `1.5.11` 1. `1.6.2` 1. `1.7.4` Before upgrading, be sure to: 1. [back up your server](/docs/advanced/self-hosting/production/backups) data before running the migration 1. review the [changelog](https://github.com/appwrite/appwrite/releases) for any breaking changes 1. test the migration process on a non-production instance to make sure your application is working well ### Installing the next version {% #install-next-version %} The first step is to install the latest version of Appwrite. Head to the directory where you ran your previous Appwrite install command. ```text parent_directory <= you run the command in this directory └── appwrite └── docker-compose.yml ``` The parent directory is where you will find the appwrite directory, inside which there are `docker-compose.yml` and `.env` files. {% info title="Parent directory naming" %} Your Appwrite installation's parent directory name is expected to be `appwrite`. Changing the directory name will result in mismatched Docker project names. {% /info %} {% info title="Choose an image tag" %} Replace `` below with the specific Appwrite image tag you intend to run (for example, `1.7.4`). Avoid using `latest` in production. {% /info %} #### Unix ```sh docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="upgrade" \ appwrite/appwrite: ``` #### CMD ```cmd docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="upgrade" ^ appwrite/appwrite: ``` #### PowerShell ```powershell docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="upgrade" ` appwrite/appwrite: ``` This will pull the `docker-compose.yml` for the selected version/tag and perform the upgrade steps. Once the setup completes, verify that you have the latest version of Appwrite. ```sh docker ps | grep appwrite/appwrite ``` Verify that the `STATUS` doesn't have any errors and all the `appwrite/appwrite` containers have the same version. ### Running the Migration {% #running-the-migration %} We can now start the migration. Navigate to the `appwrite` directory where your `docker-compose.yml` is present and run the following command. ```sh cd appwrite/ docker compose exec appwrite migrate ``` The data migration can take longer depending on the amount of data your Appwrite instance contains. The Appwrite migration command uses multi-threading to speed up the process, meaning that adding more CPU cores can help speed up the process. Once the migration process has been completed successfully, you're all set to use the latest version of Appwrite! --- ## GraphQL https://appwrite.io/docs/apis/graphql Appwrite supports multiple protocols for accessing the platform, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and [Realtime](/docs/apis/realtime). The GraphQL API allows you to query and mutate any resource type on the Appwrite platform through the endpoint `/v1/graphql`. Every endpoint available through REST is available through GraphQL, except for OAuth. ### Requests {% #requests %} Although every query executes through the same endpoint, there are multiple ways to make a GraphQL request. All requests, however, share a common structure. | Name | Type | Description | |----------------|--------|---------------------------------------------------------------------------| | query | string | **Required**, the GraphQL query to execute. | | operationName | string | **Optional**, if the query contains several named operations, controls which one to execute. | | variables | object | **Optional**, an object containing variable names and values for the query. Variables are made available to your query with the `$` prefix. | #### GraphQL model parameters {% #graphql-model-parameters %} In Appwrite's GraphQL API, all internal model parameters are prefixed with `_` instead of `$` because `$` is reserved by GraphQL. For example, `$tableId` in the REST API would be referenced as `_tableId` in the GraphQL API. #### GET requests {% #get-resquest %} You can execute a GraphQL query via a GET request, passing a `query` and optionally `operationName` and `variables` as query parameters. #### POST requests {% #post-request %} There are multiple ways to make a GraphQL POST request, differentiated by content type. {% tabs %} {% tabsitem #json title="JSON" %} There are two ways to make requests with the `application/json` content type. You can send a JSON object containing a `query` and optionally `operationName` and `variables`, or an array of objects with the same structure. ##### Object ```json { "query": "", "operationName": "", "variables": {} } ``` ##### Array ```json [ { "query": "", "operationName": "", "variables": {} } ] ``` {% /tabsitem %} {% tabsitem #graphql title="GraphQL" %} The `application/graphql` content type can be used to send a query as the raw POST body. ```graphql query GetAccount { accountGet { _id email } } ``` {% /tabsitem %} {% /tabs %} #### Multipart form data {% #multipart-form-data %} The `multipart/form-data` content type can be used to upload files via GraphQL. In this case, the form data must include the following parts in addition to the files to upload. | Name | Type | Description | |-------------|--------|---------------------------------------------------------------------------------------------------------------------------| | operations |string | **Required**, JSON encoded GraphQL query and optionally operation name and variables. File variables should contain null values. | | map | string | **Required**, JSON encoded map of form-data filenames to the operations dot-path to inject the file to, e.g. `variables.file`. | ### Responses {% #responses %} A response to a GraphQL request will have the following structure: | Name | Type | Description | |--------|----------|--------------------------------------------------------------------------------| | data | object | The data returned by the query, maps requested field names to their results. | | errors | object[] | An array of errors that occurred during the request. | The data object will contain a map of requested field names to their results. If no data is returned, the object will not be present in the response. The errors array will contain error objects, each with their own **message** and **path**. The path will contain the field key that is null due to the error. If no errors occur, the array will not be present in the response. ### Authentication {% #authentication %} GraphQL authenticates using Appwrite accounts and sessions. Both accounts and sessions can be created with GraphQL using the `accountCreate`, `accountCreateEmailPasswordSession`, `accountCreateAnonymousSession`, or `accountCreatePhoneToken` mutations. More information and examples of authenticating users can be found in the dedicated [authentication guide](/docs/products/auth). ### Database queries {% #database-queries %} The GraphQL API can be used to query and manipulate database rows. For detailed examples of how to create, list, update, and delete rows using GraphQL, refer to the [Rows documentation](/docs/products/databases/rows). ### GraphQL vs REST {% #graphql-vs-rest %} There are two main features that make GraphQL appealing when compared to the REST API: **selection sets** and **query batching**. #### Selection sets {% #selection-sets %} Selection sets can be used to tell a GraphQL API exactly which fields of a particular resource you would like to receive in the response. The server will respond with only those fields, nothing more, nothing less. This gives you full control over what data comes into your application. For example, to retrieve only the email of a currently authenticated user, you could query the `accountGet` field, passing only email as the **field selection set**. ```graphql query GetAccount { accountGet { _id email } } ``` Given this query, the GraphQL API will respond with: ```json { "data": { "accountGet": { "_id": "...", "email": "..." } } } ``` This can be a useful feature for performance, network efficiency, and app responsiveness. As the processing happens on the server, the bandwidth consumed for the request can be dramatically reduced. ### Query batching {% #query-batching %} GraphQL allows sending multiple queries or mutations in the same request. There are two different ways to batch queries. The simplest way is to include multiple fields in a single query **or** mutation. ```graphql query GetAccountAndLocale { accountGet { _id email } localeGet { ip } } ``` If both field executions succeed, the response will contain a data key for each field, containing the values of the selected fields. ```json { "data": { "accountGet": { "_id": "...", "email": "..." }, "localeGet": { "ip": "..." } } } ``` If there was no authenticated user, the `accountGet` field would fail to resolve. In such a case the value of the data key for that field will be null, and an object will be added to the errors array instead. ```json { "data": { "accountGet": null, "localeGet": { "ip": "...", "country": "..." } }, "errors": [ { "message": "User (role: guest) missing scope (account)", "path": ["accountGet"] } ] } ``` Batching with a single query or mutation has some down-sides. You cannot mix and match queries and mutations within the same request unless you provide an operationName, in which case you can only execute one query per request. Additionally, all **variables** must be passed in the same object, which can be cumbersome and hard to maintain. The second way to batch is to pass an array of queries or mutations in the request. In this way, you can execute queries **and** mutations and keep variables separated for each. ```json [ { "query": "query GetAccount { accountGet{ email } }", }, { "query": "query GetLocale { localeGet { ip } }" } ] ``` This allows you to execute complex actions in a single network request. ### SDK usage {% #sdk-usage %} Appwrite SDKs also support GraphQL in addition to the REST services. {% multicode %} ```client-web import { Client, Graphql } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint .setProject(''); // Your project ID const graphql = new Graphql(client); const mutation = graphql.mutation({ query: `mutation CreateAccount( $email: String!, $password: String!, $name: String ) { accountCreate( email: $email, password: $password, name: $name, userId: "unique()" ) { _id } }`, variables: { email: '...', password: '...', name: '...' } }); mutation.then(response => { console.log(response); }).catch(error => { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint .setProject(''); // Your project ID final graphql = Graphql(client); Future mutation = graphql.mutation({ 'query': '''mutation CreateAccount( \$email: String!, \$password: String!, \$name: String ) { accountCreate( email: \$email, password: \$password, name: \$name, userId: "unique()" ) { _id } }''', 'variables': { 'email': '...', 'password': '...', 'name': '...' } }); mutation.then((response) { print(response); }).catchError((error) { print(error.message); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let graphql = Graphql(client) do { let response = try await graphql.mutation([ "query": """ mutation CreateAccount( $email: String!, $password: String!, $name: String ) { accountCreate( email: $email, password: $password, name: $name, userId: "unique()" ) { _id } } """, "variables": [ "email": "...", "password": "...", "name": "..." ] ]) print(String(describing: response)) } catch { print(error.localizedDescription) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Graphql val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val graphql = Graphql(client) try { val response = graphql.mutation(mapOf( "query" to """mutation CreateAccount( ${'$'}email: String!, ${'$'}password: String!, ${'$'}name: String ) { accountCreate( email: ${'$'}email, password: ${'$'}password, name: ${'$'}name, userId: "unique()" ) { _id } }""", "variables" to mapOf( "email" to "...", "password" to "...", "name" to "..." ) )) Log.d(javaClass.name, response) } catch (ex: AppwriteException) { ex.printStackTrace() } ``` {% /multicode %} --- ## Realtime https://appwrite.io/docs/apis/realtime Appwrite supports multiple protocols for accessing the server, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and [Realtime](/docs/apis/realtime). The Appwrite Realtime allows you to listen to any Appwrite events in realtime using the `subscribe` method. Instead of requesting new data via HTTP, the subscription will receive new data every time it changes, any connected client receives that update within milliseconds via a WebSocket connection. This lets you build an interactive and responsive user experience by providing information from all of Appwrite's services in realtime. The example below shows subscribing to realtime events for file uploads. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Subscribe to files channel client.subscribe('files', response => { if(response.events.includes('buckets.*.files.*.create')) { // Log when a new file is uploaded console.log(response.payload); } }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final realtime = Realtime(client); // Subscribe to files channel final subscription = realtime.subscribe(['files']); subscription.stream.listen((response) { if(response.events.contains('buckets.*.files.*.create')) { // Log when a new file is uploaded print(response.payload); } }); ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let realtime = Realtime(client) // Subscribe to files channel let subscription = realtime.subscribe(channels: ["files"]) { response in if (response.events!.contains("buckets.*.files.*.create")) { // Log when a new file is uploaded print(String(describing: response)) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Realtime val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val realtime = Realtime(client) // Subscribe to files channel val subscription = realtime.subscribe("files") { if(it.events.contains("buckets.*.files.*.create")) { // Log when a new file is uploaded print(it.payload.toString()); } } ``` {% /multicode %} To subscribe to updates from different Appwrite resources, you need to specify one or more [channels](/docs/apis/realtime#channels). The channels offer a wide and powerful selection that will allow you to listen to all possible resources. This allows you to receive updates not only from the database, but from _all_ the services that Appwrite offers. If you subscribe to a channel, you will receive callbacks for a variety of events related to the channel. The events column in the callback can be used to filter and respond to specific events in a channel. [View a list of all available events](/docs/advanced/platform/events). {% info title="Permissions" %} All subscriptions are secured by the [permissions system](/docs/advanced/platform/permissions) offered by Appwrite, meaning a user will only receive updates to resources they have permission to access. Using `Role.any()` on read permissions will allow any client to receive updates. {% /info %} ### Authentication {% #authentication %} Realtime authenticates using an existing user session. If you authenticate **after** creating a subscription, the subscription will not receive updates for the newly authenticated user. You will need to re-create the subscription to work with the new user. More information and examples of authenticating users can be found in the dedicated [authentication docs](/docs/products/auth). ### Examples {% #examples %} The examples below will show you how you can use Realtime in various ways. #### Subscribe to a Channel {% #subscribe-to-a-channel %} In this example we are subscribing to all updates related to our account by using the `account` channel. This will be triggered by any update related to the authenticated user, like updating the user's name or e-mail address. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); client.subscribe('account', response => { // Callback will be executed on all account events. console.log(response); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final realtime = Realtime(client); final subscription = realtime.subscribe(['account']); subscription.stream.listen((response) { // Callback will be executed on all account events. print(response); }) ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let realtime = Realtime(client) let subscription = realtime.subscribe(channel: "account") { response in // Callback will be executed on all account events. print(String(describing: response)) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Realtime val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val realtime = Realtime(client) val subscription = realtime.subscribe("account") { // Callback will be executed on all account events. print(it.payload.toString()) } ``` {% /multicode %} #### Subscribe to Multiple Channels {% #subscribe-to-multiple-channel %} You can also listen to multiple channels at once by passing an array of channels. This will trigger the callback for any events for all channels passed. In this example we are listening to a specific row and all files by subscribing to the `databases..tables..rows.` and `files` channels. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); client.subscribe(['databases..tables..rows.', 'files'], response => { // Callback will be executed on changes for the specific row and all files. console.log(response); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final realtime = Realtime(client); final subscription = realtime.subscribe(['databases..tables..rows.', 'files']); subscription.stream.listen((response) { // Callback will be executed on changes for the specific row and all files. print(response); }) ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let realtime = Realtime(client) realtime.subscribe(channels: ["databases..tables..rows.", "files"]) { response in // Callback will be executed on changes for the specific row and all files. print(String(describing: response)) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Realtime val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val realtime = Realtime(client) realtime.subscribe(listOf("databases..tables..rows.", "files")) { // Callback will be executed on changes for the specific row and all files. print(it.toString()) } ``` {% /multicode %} #### Unsubscribe {% #unsubscribe %} If you no longer want to receive updates from a subscription, you can unsubscribe so that your callbacks are no longer called. Leaving old subscriptions alive and resubscribing can result in duplicate subscriptions and cause race conditions. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const unsubscribe = client.subscribe('files', response => { // Callback will be executed on changes for all files. console.log(response); }); // Closes the subscription. unsubscribe(); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final realtime = Realtime(client); final subscription = realtime.subscribe(['files']); subscription.stream.listen((response) { // Callback will be executed on changes for all files. print(response); }) // Closes the subscription. subscription.close(); ``` ```client-apple import Appwrite let client = Client() let realtime = Realtime(client) let subscription = realtime.subscribe(channel: "files") { response in // Callback will be executed on changes for all files. print(response.toString()) } // Closes the subscription. subscription.close() ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Realtime val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val realtime = Realtime(client) val subscription = realtime.subscribe("files") { // Callback will be executed on changes for all files. print(it.toString()) } // Closes the subscription. subscription.close() ``` {% /multicode %} ### Payload {% #payload %} The payload from the subscription will contain following properties: {% table %} * Name * Type * Description --- * events * string[] * The [Appwrite events](/docs/advanced/platform/events) that triggered this update. --- * channels * string[] * An array of [channels](/docs/apis/realtime#channels) that can receive this message. --- * timestamp * string * The [ISO 8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) in UTC timezone from the server --- * payload * object * Payload contains the data equal to the response model. {% /table %} If you subscribe to the `rows` channel and a row the user is allowed to read is updated, you will receive an object containing information about the event and the updated row. The response will look like this: ```json { "events": [ "databases.default.tables.sample.rows.63c98b9baea0938e1206.update", "databases.*.tables.*.rows.*.update", "databases.default.tables.*.rows.63c98b9baea0938e1206.update", "databases.*.tables.*.rows.63c98b9baea0938e1206.update", "databases.*.tables.sample.rows.63c98b9baea0938e1206.update", "databases.default.tables.sample.rows.*.update", "databases.*.tables.sample.rows.*.update", "databases.default.tables.*.rows.*.update", "databases.default.tables.sample.rows.63c98b9baea0938e1206", "databases.*.tables.*.rows.*", "databases.default.tables.*.rows.63c98b9baea0938e1206", "databases.*.tables.*.rows.63c98b9baea0938e1206", "databases.*.tables.sample.rows.63c98b9baea0938e1206", "databases.default.tables.sample.rows.*", "databases.*.tables.sample.rows.*", "databases.default.tables.*.rows.*", "databases.default.tables.sample", "databases.*.tables.*", "databases.default.tables.*", "databases.*.tables.sample", "databases.default", "databases.*" ], "channels": [ "rows", "databases.default.tables.sample.rows", "databases.default.tables.sample.rows.63c98b9baea0938e1206" ], "timestamp": "2023-01-19 18:30:04.051", "payload": { "ip": "127.0.0.1", "stringArray": [ "sss" ], "email": "joe@example.com", "stringRequired": "req", "float": 3.3, "boolean": false, "integer": 3, "enum": "apple", "stringDefault": "default", "datetime": "2023-01-19T10:27:09.428+00:00", "url": "https://appwrite.io", "$id": "63c98b9baea0938e1206", "$createdAt": "2023-01-19T18:27:39.715+00:00", "$updatedAt": "2023-01-19T18:30:04.040+00:00", "$permissions": [], "$tableId": "sample", "$databaseId": "default" } } ``` ### Channels {% #channels %} A list of channels you can subscribe to. Replace `` with your resource ID or use `*` for wildcards. #### Account {% #account %} {% table %} * Channel * Description --- * `account` * All account related events (session create, name update...) {% /table %} #### Databases {% #databases %} {% table %} * Channel * Description --- * `rows` * Any create/update/delete events to any row --- * `databases..tables..rows` * Any create/update/delete events to any row in a table --- * `databases..tables..rows.` * Any create/update/delete events to a given row {% /table %} #### Storage {% #storage %} {% table %} * Channel * Description --- * `files` * Any create/update/delete events to any file --- * `buckets..files` * Any create/update/delete events to any file of the given bucket --- * `buckets..files.` * Any create/update/delete events to a given file of the given bucket {% /table %} #### Functions {% #functions %} {% table %} * Channel * Description --- * `executions` * Any execution event --- * `executions.` * Any execution event to a given execution --- * `functions.` * Any execution event to a given function {% /table %} #### Teams & Memberships {% #teams %} {% table %} * Channel * Description --- * `teams` * Any create/update/delete events to any team --- * `teams.` * Any create/update/delete events to a given team --- * `memberships` * Any create/update/delete events to any membership --- * `memberships.` * Any create/update/delete events to a given membership {% /table %} ### Custom endpoint {% #custom-endpoint %} The SDK will guess the endpoint of the Realtime API when setting the endpoint of your Appwrite instance. If you are running Appwrite with a custom proxy and changed the route of the Realtime API, you can call the `setEndpointRealtime` method on the Client SDK and set your new endpoint value. By default the endpoint is `wss://.cloud.appwrite.io/v1/realtime`. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client(); client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client(); client.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime'); ``` ```client-apple import Appwrite let client = Client() client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") ``` ```client-android-kotlin import io.appwrite.Client val client = Client(context) client.setEndpointRealtime("wss://.cloud.appwrite.io/v1/realtime") ``` {% /multicode %} ### Limitations {% #limitations %} While the Realtime API offers robust capabilities, there are currently some limitations to be aware of in its implementation. #### Subscription changes {% #subscription-changes %} The SDK creates a single WebSocket connection for all subscribed channels. Each time a channel is added or unsubscribed, the SDK currently creates a completely new connection and terminates the old one. Therefore, subscriptions to channels should always be done in conjunction with state management so as not to be unnecessarily built up several times by multiple components' life cycles. #### Server SDKs {% #server-sdks %} We currently are not offering access to realtime with Server SDKs and an API key. --- ## REST https://appwrite.io/docs/apis/rest Appwrite supports multiple protocols for accessing the server, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and [Realtime](/docs/apis/realtime). The REST API allows you to access your Appwrite server through HTTP requests without needing an SDK. Each endpoint in the API represents a specific operation on a specific resource. ### Headers {% #headers %} Appwrite's REST APIs expect certain headers to be included with each request: {% table %} - Header - - Description --- - X-Appwrite-Project: [PROJECT-ID] - required - The ID of your Appwrite project --- - Content-Type: application/json - required - Content type of the HTTP request. Typically set to `application/json`. --- - X-Appwrite-Key: [API-KEY] - optional - API key used for server authentication. Your API key is a secret, **do not** use it in client applications. --- - X-Appwrite-JWT: [TOKEN] - optional - Token used for JWT authentication, tokens can be generated using the [Create JWT](/docs/products/auth/jwt) method. --- - X-Appwrite-Response-Format: [VERSION-NUMBER] - optional - Version number used for backward compatibility. The response will be formatted to be compatible with the provided version number. This helps Appwrite SDKs keep backward compatibility with Appwrite server API version. --- - X-Fallback-Cookies: [FALLBACK-COOKIES] - optional - Fallback cookies used in scenarios where browsers do not allow third-party cookies. Often used when there is no Custom Domain set for your Appwrite API. {% /table %} ### Authentication {% #authentication %} Appwrite supports multiple authentication methods, including account sessions, API keys, and JWTs. The authentication method you use depends on your use case. Below are examples showing how you can authenticate using the REST API. #### Client integrations {% #client-integration %} You can create account sessions with POST requests to the Account API. Sessions are persisted using secured cookies. You can learn more about session persistence in the Authentication Guide. The example below shows creating an account session with the Create Account Session with Email endpoint. ```json POST /v1/account/sessions/email HTTP/1.1 Content-Type: application/json X-Appwrite-Project: { "email": "example@email.com", "password": "password" } ``` You can find the cookies used to persist the new session in the response headers. ```json Set-Cookie: a_session_61e71ec784ab035f7259_legacy=eyJ0...aSJ9; expires=Tue, 19-Dec-2023 21:26:51 GMT; path=/; domain=.cloud.appwrite.io; secure; httponly Set-Cookie: a_session_61e71ec784ab035f7259=eyJ0...aSJ9; expires=Tue, 19-Dec-2023 21:26:51 GMT; path=/; domain=.cloud.appwrite.io; secure; httponly; samesite=None ``` These cookies are used in subsequent requests to authenticate the user. ```json GET /v1/account HTTP/1.1 Cookie: a_session_61e71ec784ab035f7259_legacy=eyJ0...aSJ9; a_session_61e71ec784ab035f7259=eyJ0...aSJ9 Content-Type: application/json X-Appwrite-Project: ``` #### Server integrations {% #server-integrations %} Server integrations use API keys to authenticate and are typically used for backend applications. Server APIs are authenticated with API keys instead of account sessions. Simply pass an [API key](/docs/advanced/platform/api-keys) in the `X-Appwrite-key: [API-KEY]` header with the appropriate scopes. ```json GET /v1/tablesdb/{databaseId}/tables/{tableId}/rows HTTP/1.1 Content-Type: application/json X-Appwrite-Project: X-Appwrite-Key: [API_KEY] ``` #### JWT {% #jwt %} JWT authentication is frequently used by server applications to act on behalf of a user. Users generate tokens using the [Create JWT](/docs/references/cloud/client-web/account#createJWT) endpoint. When issuing requests authenticated with a JWT, Appwrite will treat the request like it is from the authenticated user. ```json GET /v1/account HTTP/1.1 Content-Type: application/json X-Appwrite-Project: X-Appwrite-JWT: [TOKEN] ``` ### Files {% #files %} Appwrite implements resumable, chunked uploads for files larger than 5MB. Chunked uploads send files in chunks of 5MB to reduce memory footprint and increase resilience when handling large files. [Appwrite SDKs](/docs/sdks) will automatically handle chunked uploads, but it is possible to implement this with the REST API directly. Upload endpoints in Appwrite, such as [Create File](/docs/references/cloud/client-web/storage#createFile) and [Create Deployment](/docs/references/cloud/server-nodejs/functions#createDeployment), are different from other endpoints. These endpoints take multipart form data instead of JSON data. To implement chunked uploads, you will need to implement the following headers. If you wish, this logic is already available in any of the [Appwrite SDKs](/docs/sdks). {% table %} - Header - - Description --- - X-Appwrite-Project: [PROJECT-ID] - required - The ID of your Appwrite project --- - Content-Type: multipart/form-data; boundary=[FORM-BOUNDARY] - required - Contains the content type of the HTTP request and provides a [boundary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) that is used to parse the form data. --- - Content-Range: bytes [BYTE-RANGE] - required - Contains information about which bytes are being transmitted in this chunk, with the format `[FIRST-BYTE]-[LAST-BYTE]/[TOTAL-BYTES]`. --- - X-Appwrite-ID: [FILE-ID] - required - Contains ID of the file this chunk belongs to. --- - X-Appwrite-Key: [API-KEY] - optional - API key used for server authentication. Your API key is a secret, **do not** use it in client applications. {% /table %} The multipart form data is structured as follows: {% table %} - Key - - Value - File Name - Description --- - fileId - optional - [FILE-ID] - N/A - Contains the file ID of the new file. Only used by file chunks following the first chunk uploaded. --- - file - required - [CHUNK-DATA] - [FILE-NAME] - Contains file chunk data. --- - permissions - required - [PERMISSION ARRAY] - N/A - Contains an array of permission strings about who can access the new file. {% /table %} While cURL and fetch are great tools to explore other REST endpoints, it's impractical to use for chunked file uploads because you need to split files into chunks. The multipart form data posted to file upload endpoints have the following format: ```json POST /v1/storage/buckets/default/files HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye0m6iNBQNHlzTpVM X-Appwrite-Project: demo-project Content-Range: bytes 10485760-12582912/12582912 X-Appwrite-ID: 6369b0bc1dcf4ff59051 ------WebKitFormBoundarye0m6iNBQNHlzTpVM Content-Disposition: form-data; name="fileId" unique() ------WebKitFormBoundarye0m6iNBQNHlzTpVM Content-Disposition: form-data; name="file"; filename="file.txt" Content-Type: application/octet-stream [CHUNKED-DATA] ------WebKitFormBoundarye0m6iNBQNHlzTpVM Content-Disposition: form-data; name="permissions[]" read("user:627a958ded6424a98a9f") ------WebKitFormBoundarye0m6iNBQNHlzTpVM-- ``` ### Images {% #images %} Some use cases do not allow custom headers, such as embedding images from Appwrite in HTML. In these cases, you can provide the Appwrite project ID using the query parameter project. ```HTML ``` ### Permissions {% #permissions %} Appwrite SDKs have helpers to generate permission string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself. {% table %} - Query method - API string --- - `Permission.read()` - `read("")` --- - `Permission.create()` - `read("")` --- - `Permission.update()` - `update("")` --- - `Permission.delete()` - `delete("")` --- - `Permission.write()` - `write("")` {% /table %} #### Roles {% #roles %} Appwrite SDKs have helpers to generate roles string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself. {% table %} - Role method - API string --- - `Role.any()` - `any` --- - `Role.guests()` - `guests` --- - `Role.users()` - `users` --- - `Role.users([STATUS])` - `users/[STATUS]` --- - `Role.user([USER_ID])` - `user:[USER_ID]` --- - `Role.user([USER_ID], [STATUS])` - `user:[USER_ID]/[STATUS]` --- - `Role.team([TEAM_ID])` - `team:[TEAM_ID]` --- - `Role.team([TEAM_ID], [ROLE])` - `team:[TEAM_ID]/[ROLE]` --- - `Role.member([MEMBERSHIP_ID])` - `member:[MEMBERSHIP_ID]` {% /table %} ### Unique ID {% #unique-id %} Appwrite's SDKs have a helper `ID.unique()` to generate unique IDs. When using Appwrite without an SDK, pass the string `"unique()"` into the ID parameter. ### Queries {% #queries %} Appwrite's SDKs provide a `Query` class to generate JSON query strings. When using Appwrite without an SDK, you can template your own JSON strings. You can discover the query methods available in the [Queries page.](/docs/products/databases/queries) #### Query string format {% #queries-string-format %} Appwrite Queries are escaped JSON strings, which look like this. ```json "{\"method\":\"equal\",\"column\":\"name\",\"values\":[\"John\"]}" ``` Query strings are passed to Appwrite using the `queries` parameter. You can attach multiple query strings by including the array parameter multiple times in the query string: `queries[]="..."&queries[]="..."` For example, the unescaped query string might look like this. ```text ?queries[0]={"method":"equal","column":"name","values":["John"]}&queries[1]={"method":"limit","values":[6]} ``` The JSON has a general format like this. ```json { "method": "", "column": "", "values": [ , , ... ] } ``` {% info title="Best practice" %} When using greater than, greater than or equal to, less than, or less than or equal to, it is not recommended to pass in multiple values. While the API will accept multiple values and return results with **or logic**, it's best practice to pass in only one value for performance reasons. {% /info %} For example, to query for all rows with the name "John" or "Jane", the query string would look like this. ```json { "method": "equal", "column": "name", "values": [ "John", "Jane" ] } ``` Here are some more examples of the JSON query format. When in doubt, you can use the Appwrite SDKs to generate the query strings for you. ```json { "method": "isNull", "column": "name" } { "method": "select", "values": ["name", "age", "email"] } { "method": "between", "column": "age", "values": [18, 30] } { "method": "cursorAfter", "values": ["rowId"] } ``` #### Query nesting {% #query-nesting %} Some Appwrite query methods, like `and` and `or`, allow you to nest queries. When using Appwrite without an SDK, you can template your own JSON strings. In these cases, `column` is empty and `values` is an array of queries. ```json { "method": "and", "values": [ { "method": "equal", "column": "name", "values": ["John"] }, { "method": "between", "column": "age", "values": [20, 30] } ] } ``` ### Rate limits {% #rate-limits %} Appwrite's REST APIs are protected by the same rate limit policies, just like when using an SDK. Each API has a different rate limit, which is documented in the References section of each service in the Appwrite documentation. [Learn more about Rate Limits](/docs/advanced/platform/rate-limits). ### Specifications {% #specifications %} Appwrite provides a full REST API specification in the OpenAPI 3 and Swagger 2 formats every release. These can be accessed through Appwrite's GitHub repository and rendered using a variety of parsers and tools. [Find the REST API specification for your Appwrite version](https://github.com/appwrite/appwrite/tree/master/app/config/specs). --- ## Artificial intelligence https://appwrite.io/docs/products/ai Appwrite allows you to build powerful AI powered applications with ease. Leverage Appwrite's powerful functions architecture and start building the future. ### Explore capabilities {% #explore-capabilities %} Detailed explanations and deep dives into how you can implement different machine techniques in your Appwrite projects. {% cards %} {% cards_image_item href="/docs/products/ai/computer-vision" title="Computer vision" light="/images/docs/ai/computer-vision-light.png" dark="/images/docs/ai/computer-vision-dark.png" %} Label and understand the contents of images {% /cards_image_item %} {% cards_image_item href="/docs/products/ai/natural-language" title="Natural language processing" light="/images/docs/ai/natural-language-light.png" dark="/images/docs/ai/natural-language-dark.png" %} Understand and generate human language {% /cards_image_item %} {% cards_image_item href="/docs/products/ai/audio-processing" title="Audio processing" light="/images/docs/ai/audio-processing-light.png" dark="/images/docs/ai/audio-processing-dark.png" %} Process and generate audio data {% /cards_image_item %} {% /cards %} ### Show me some code {% #show-me-some-code %} If you learn best from code examples, follow one of our tutorials. ##### Computer vision {% cards %} {% cards_item href="/docs/products/ai/tutorials/image-classification" title="Image classification" %} Understand and label the contents of images {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/object-detection" title="Object detection" %} Detect and label objects in images {% /cards_item %} {% /cards %} ##### Natural language {% cards %} {% cards_item href="/docs/products/ai/tutorials/text-generation" title="Text generation" %} Generate human-like text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/language-translation" title="Language translation" %} Translate text between languages {% /cards_item %} {% /cards %} ##### Audio processing {% cards %} {% cards_item href="/docs/products/ai/tutorials/speech-recognition" title="Speech recognition" %} Process speech audio into text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/text-to-speech" title="Text to speech" %} Convert text into speech {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/music-generation" title="Music generation" %} Generate music from a text prompt {% /cards_item %} {% /cards %} --- ## Audio processing https://appwrite.io/docs/products/ai/audio-processing Audio processing is a field of machine learning that deals with allowing machines to understand, analyze, and manipulate various audio signals. The applications are vast and varied, from speech recognition to music generation and all the way to noise reduction. it's used in many everyday tools you use including voice assistants, music streaming services and for noise reduction in online calls. ### Tutorials {% cards %} {% cards_item href="/docs/products/ai/tutorials/speech-recognition" title="Speech recognition" %} Recognize and transcribe spoken language into text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/text-to-speech" title="Text to speech" %} Convert written text into spoken language {% /cards_item %} {% /cards %} --- ## Computer vision https://appwrite.io/docs/products/ai/computer-vision Computer vision is a field of AI aiming to provide machines with a comprehensive understanding of visual data from a variety of sources. Images, Videos, Point Clouds, X-Rays, and MRI's from medical devices can be processed with the goal of parsing relevant information for subsequent tasks. ### Tutorials {% cards %} {% cards_item href="/docs/products/ai/tutorials/image-classification" title="Image classification" %} Understand and label the contents of images {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/object-detection" title="Object detection" %} Detect and label objects in images {% /cards_item %} {% /cards %} --- ## Integrating Anyscale https://appwrite.io/docs/products/ai/integrations/anyscale The Anyscale API is a powerful tool for generating text using the leading open-source models. This tutorial will guide you through setting up the Anyscale API and integrating it into your Appwrite project. You'll create a simple function that takes a text prompt and generates a completion using Mistral's Mixtral 8x7B model. Then, using Appwrite functions, you'll create a UI that allows users to input text and see the generated completion. ### Prerequisites {% #prerequisites %} - An Appwrite Project - An [Anyscale API Key](https://app.endpoints.anyscale.com/credentials) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar and click the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add `ANYSCALE_API_KEY`. Generate your AnyScale key [here](https://app.endpoints.anyscale.com/credentials). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add OpenAI SDK" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Install the `openai` package to simplify interacting with the AnyScale API, as it is an OpenAI-compatible API. ```bash npm install openai ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} For this example, the function can take both `GET` and `POST` requests. For the `GET` request, return a static HTML page that will have a form to submit text to the API. Meanwhile, the `POST` request will send the text to the AnyScale API and return the generated text. Write the code to return the static HTML page. To do this, create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` If the method is `GET`, it returns the static HTML page. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create an HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html Anyscale Demo ``` The code above includes a script that will handle the form submission and a script tag that includes of the Alpine.js library. This library will be used to handle the submission of the form. After the `` tag add a `` containing the visible form: ```html

Prompt Anyscale Demo

Use this page to test your implementation with Anyscale using Mixtral 8x7B. Enter text and receive the model output as a response.

``` The form will allow users to submit text to the Appwrite function through a POST request. The Appwrite function will call the Anyscale API and return the response to the user. {% /section %} {% section #step-6 step=6 title="Handle POST request" %} Add methods necessary to integrate with the Anyscale API. Import `openai` and the Appwrite SDK at the top of the `main.js` file. ```js import OpenAI from 'openai'; ``` Add code to validate the body of the request and initialize the Appwrite SDK after the `GET` request handler from earlier: ```js if (!req.body.prompt && typeof req.body.prompt !== "string") { return res.json({ ok: false, error: "Missing required field `prompt`" }, 400); } const openai = new OpenAI( { apiKey: process.env.ANYSCALE_API_KEY, baseURL: "https://api.endpoints.anyscale.com/v1" } ); ``` Make a request to the Anyscale API and return the response: ```js try { const response = await openai.chat.completions.create({ model: "mistralai/Mixtral-8x7B-Instruct-v0.1", max_tokens: parseInt(process.env.ANYSCALE_MAX_TOKENS ?? "512"), messages: [{ role: "user", content: req.body.prompt }], stream: false }); const completion = response.choices[0].message?.content; return res.json({ ok: true, completion }, 200); } catch (err) { error(err); return res.json({ ok: false, error: "Failed to query model." }, 500); } ``` {% /section %} {% section #step-8 step=8 title="Test the function" %} Now that the function is deployed test it by visiting the function URL in your browser. This should show the UI created earlier. To test it, write a prompt and click the submit button. After a brief moment, you should see the text generated by the Anyscale API. ![Testing the function](/images/docs/ai/integrations/anyscale/demo.png) {% /section %} --- ## Integrating ElevenLabs https://appwrite.io/docs/products/ai/integrations/elevenlabs ElevenLabs is an text to speech tool that can generate natural sounding audio from text. It's an excellent tool for dubbing content, creating audiobooks, or even for accessibility purposes. Integrating ElevenLabs into your Appwrite project is simple. This tutorial will guide you through the process of setting up the ElevenLabs API and integrating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - An [ElevenLabs API Key](https://elevenlabs.io/) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `ELEVENLABS_API_KEY`, generate it [here](https://elevenlabs.io/). For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Install the `undici` package to make requests to the ElevenLabs API and `node-appwrite` package to upload the generated audio files to Appwrite Storage. ```bash npm install undici node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Create utility functions" %} For this example, the function will be able to take both `GET` and `POST` requests. For the `GET` request, return a static HTML page that will have a form to submit text to the API. Meanwhile the `POST` request will send the text to the ElevenLabs API and return the generated audio file. To begin with write the code to return the static HTML page, to do this create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` A check is also included to ensure that the `ELEVENLABS_API_KEY`, `APPWRITE_API_KEY` and `APPWRITE_BUCKET_ID` environment variables is set. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, Add a `` tag that will define the style and scripts. ```html ElevenLabs Demo ``` And after the `` tag add this `` which will contain the actual form: ```html

ElevenLabs Demo

Use this page to test your implementation with ElevenLabs. Enter text and receive an audio response.

``` All of this together will render a form that will submit your text to the Appwrite function through a POST request which you'll create next. The Appwrite function will call ElevenLabs's API, upload the audio to Appwrite Storage and return the URL, which will be displayed on your page. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Add methods necessary to integrate with the ElevenLabs API: Import `fetch`, and the required features from the Appwrite Node.js SDK at the top of the `main.js` file ```js import { Client, Storage, ID, Permission, Role } from "node-appwrite"; import { InputFile } from "node-appwrite/file"; import { fetch } from "undici"; ``` Next add code to validate the body of the request and initialize the Appwrite SDK: ```js const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT ?? "https://.cloud.appwrite.io/v1") .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); if (!req.body.text || typeof req.body.text !== "string") { return res.json({ ok: false, error: "Missing required field `text`" }, 400); } ``` Send a request to the ElevenLabs API and return the response: ```js const body = { accent: req.body.accent || "british", accent_strength: 1.0, age: req.body.age || "young", gender: req.body.gender || "female", text: req.body.text, }; const response = await fetch( "https://api.elevenlabs.io/v1/voice-generation/generate-voice", { method: "POST", headers: { "Content-Type": "application/json", "xi-api-key": process.env.ELEVENLABS_API_KEY, }, body: JSON.stringify(body), }, ); if (response.status !== 200) { return res.json({ ok: false, error: "Failed to generate audio" }, 500); } ``` This code will send the prompt to the ElevenLabs API and return the audio as a blob, additionally it'll also catch any errors we could encounter and reports them for easy debugging. {% /section %} {% section #step-7 step=7 title="Store Audio in Appwrite Storage" %} Store the audio file in Appwrite Storage for easy retrieval later: ```js const storage = new Storage(client); const file = await storage.createFile({ bucketId: process.env.APPWRITE_BUCKET_ID, fileId: ID.unique(), file: InputFile.fromBuffer(await response.blob(), "audio.mp3"), permissions: [Permission.read(Role.any())], }); ``` To show it to the user, parse the download URL from Appwrite and return it in the response: ```js const url = `${process.env.APPWRITE_ENDPOINT}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`; return res.json({ ok: true, response: url }); ``` This should finish up the function, Deploy it to Appwrite by pushing to the git repository created earlier. {% /section %} {% section #step-8 step=8 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier and to test it, write a prompt and click the submit button. After a brief moment you should see the audio appear below the input. ![Testing the function](/images/docs/ai/integrations/elevenlabs/demo.png) {% /section %} --- ## Integrating fal.ai https://appwrite.io/docs/products/ai/integrations/fal-ai fal.ai is an AI inference platform with popular models such as Stable Diffusion XL, ControlNet, Whisper available as ready-to-use APIs so that you can easily integrate them into your applications. This tutorial will guide you through the process of setting up the fal.ai API to generate an image using the SDXL model and integrating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - A [fal.ai API Key](https://fal.ai/docs/authentication/key-based) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `FAL_API_KEY`, generate it [here](https://fal.ai/docs/authentication/key-based). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add fal.ai SDK" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the fal.ai SDK by running the following command in your terminal: ```bash npm install @fal-ai/serverless-client ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} In this example, the function will be able to accept both `GET` and `POST` requests. For the `GET` request, return a static HTML page. It will use AlpineJS to make a `POST` request to the function. The `POST` request will use the fal.ai SDK to make a request to the fal.ai API. Write the code to return a static HTML page. Create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write our `GET` request handler in the `src/main.js` file. This handler will return the static HTML page. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` {% /section %} {% section #step-5 step=5 title="Create static page" %} Create the static HTML page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, add a `` tag with the necessary meta tags, stylesheets, and scripts: ```html fal.ai Demo ``` And after the `` tag, add our `` tag with the following content: ```html

fal.ai demo

Use this page to test your implementation with fal.ai. Enter text and receive the model output as a response.

``` This HTML form will allow users to input a prompt and generate an image using the fal.ai API. The AlpineJS script handles the form submission and display the result. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Add methods necessary to integrate with fal.ai's API. Import the fal.ai SDK at the top of the `main.js` file: ```js import * as fal from '@fal-ai/serverless-client'; ``` Handle the `POST` requests to the function. Initialize the fal.ai SDK at the end of the handler function: ```js fal.config({ credentials: process.env.FAL_API_KEY }); ``` Make the request to generate an image using the SDXL model, and return the result: ```js const result = await fal.subscribe('fal-ai/fast-sdxl', { input: { prompt: req.body.prompt, }, }); return res.json({ ok: true, src: result.images[0].url }); ``` With the function complete, deploy it to Appwrite by pushing the changes to your repository. Additional models can be found in the [fal.ai model catalogue](https://fal.ai/models). {% /section %} {% section #step-7 step=7 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in a browser. The UI created earlier will be visible. To test it, write a prompt and click the submit button, after a brief the completion should appear below the input. ![Testing the function](/images/docs/ai/integrations/fal-ai/demo.png) {% /section %} --- ## Integrating LangChain https://appwrite.io/docs/products/ai/integrations/langchain - An Appwrite project - An Appwrite table - An [OpenAI API key](https://platform.openai.com/account/api-keys) - A [Pinecone API key](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key) - A Pinecone index {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `PINECONE_API_KEY`, generate it [here](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key). Add the `OPENAI_API_KEY`, generate it [here](https://platform.openai.com/account/api-keys).For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Add the following dependencies to the `package.json` file: ```bash npm install @pinecone-database/pinecone openai @langchain/core @langchain/openai @langchain/pinecone langchain ``` {% /section %} {% section #step-3 step=3 title="Create utility functions" %} For this example, the function will be able to take both `GET` and `POST` requests. For the `GET` request, return a static HTML page that will have a form to search the Pinecone index. Meanwhile the `POST /search` requests will send the search query to the Pinecone API and return the results. All other `POST` requests will trigger the indexing of the Appwrite table into the Pinecone index. Write the code to return the static HTML page, to do this create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { const html = getStaticFile('index.html'); return res.text(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } }; ``` The function will throw an error if any of the required environment variables are missing. The function will return the static HTML page when a `GET` request is made. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, Add a `` tag that will define the style and scripts. ```html Pinecone Demo ``` And after the `` tag add this `` which will contain the actual form: ```html

Pinecone Demo

Use this demo to verify that the sync between Appwrite Databases and Pinecone was successful. Search your Pinecone vector database using the input below.

``` This will render a form that will submit your search query to the function and display the results. {% /section %} {% section #step-6 step=6 title="Setup SDKs" %} Add methods necessary to integrate with the OpenAI and Pinecone APIs Import `openai` and `@pinecone-database/pinecone` at the top of the `main.js` file: ```js import { Pinecone } from '@pinecone-database/pinecone'; import { OpenAI } from 'openai'; ``` Add the following code at the end of request handler in the `main.js` file: ```js const openai = new OpenAI(); const pinecone = new Pinecone(); const pineconeIndex = pinecone.index(process.env.PINECONE_INDEX_ID); ``` The functions checks the request method, and then initializes the OpenAI and Pinecone SDKs. {% /section %} {% section #step-7 step=7 title="Handle prompt requests" %} First add the following imports from LangChain: ```js import { formatDocumentsAsString } from 'langchain/util/document'; import { ChatOpenAI } from '@langchain/openai'; import { PineconeStore } from '@langchain/pinecone'; import { PromptTemplate } from '@langchain/core/prompts'; import { RunnableSequence, RunnablePassthrough, } from '@langchain/core/runnables'; import { StringOutputParser } from '@langchain/core/output_parsers'; ``` To handle the prompt requests, add the following code to the end of the request handler in the `main.js` file: ```js if (req.path === '/prompt') { if (!req.body.prompt || typeof req.body.prompt !== 'string') { return res.json( { ok: false, error: 'Missing required field `prompt`' }, 400 ); } const vectorStore = await PineconeStore.fromExistingIndex( new OpenAIEmbeddings(), { pineconeIndex } ); const prompt = PromptTemplate.fromTemplate( `Answer the question based with following context:{context}\nQuestion: {question}` ); const chain = RunnableSequence.from([ { context: vectorStore.asRetriever().pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, new ChatOpenAI(), new StringOutputParser(), ]); const result = await chain.invoke(req.body.prompt); return res.json({ ok: true, completion: result }, 200); } ``` This code will handle the prompt requests by creating a LangChain sequence that will format the rows as strings, prompt the user for a question, and then use the OpenAI API to generate a response. The response is then parsed and returned to the user. {% /section %} {% section #step-8 step=8 title="Handle index requests" %} The Appwrite table needs to be indexed into the Pinecone index. Create a new file at `src/appwrite.js` with the following code: ```js import { Client, TablesDB, Query } from 'node-appwrite'; export default class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); } async getAllRows(databaseId, tableId) { const cumulative = []; let cursor = null; do { const queries = [Query.limit(100)]; if (cursor) { queries.push(Query.cursorAfter(cursor)); } const { rows } = await this.tablesDB.listRows({ databaseId, tableId, queries }); if (rows.length === 0) { break; } cursor = rows[rows.length - 1].$id; cumulative.push(...rows); } while (cursor); return cumulative; } } ``` The service provides a method to iterate the rows contained within an entire table, fetching the limit of 100 rows per request. ```js const appwrite = new AppwriteService(); const appwriteRows = await appwrite.getAllRows( process.env.APPWRITE_DATABASE_ID, process.env.APPWRITE_TABLE_ID ); const rows = appwriteRows.map( (row) => new Row({ metadata: { id: row.$id }, pageContent: Object.entries(row) .filter(([key, _]) => !key.startsWith('$')) .map(([key, value]) => `${key}: ${value}`) .join('\n'), }) ); await PineconeStore.fromDocuments(rows, new OpenAIEmbeddings(), { pineconeIndex, maxConcurrency: 5, }); ``` Within our function handler, the service is instantiated and used to create an array of LangChain documents. LangChain documents can then be used with the `PineconeStore.fromDocuments` method to retrieve embeddings from OpenAI and upsert them to your Pinecone index. {% /section %} {% section #step-9 step=9 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier and to test it, write a prompt and click the submit button. After a brief moment you should see the matched results. {% /section %} --- ## Integrating LMNT https://appwrite.io/docs/products/ai/integrations/lmnt LMNT is a text-to-speech tool that can generate natural-sounding audio from text. It's an excellent tool for dubbing content, creating audiobooks, or even for accessibility. Integrating LMNT into your Appwrite project is simple. This tutorial will guide you through setting up the LMNT API and incorporating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - An Appwrite Bucket - An [LMNT API Key](https://app.lmnt.com/account) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar and click the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 2. Click **Create function**. 3. Under **Connect Git repository**, select your provider. 4. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 5. In the **Variables** step, add `APPWRITE_BUCKET_ID`, `LMNT_API_KEY`. Generate your LMNT Key [here](https://app.lmnt.com/account). For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 6. Follow the step-by-step wizard and create the Function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the Function is created, please navigate to the freshly created repository and clone it to your local machine. Install the `lmnt-node` package to make requests to the LMNT API and `node-appwrite` package to upload the generated audio files to Appwrite Storage. ```bash npm install lmnt-node node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Create utility functions" %} For this example, the Function can take both `GET` and `POST` requests. For the `GET` request, return a static HTML page with a form to submit text to the API. Meanwhile, the `POST` request will send the text to the LMNT API and return the generated audio file. To begin with, write the code to return the static HTML page. To do this, create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page, which will be created in the next section. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { throwIfMissing(process.env, [ "LMNT_API_KEY", "APPWRITE_API_KEY", "APPWRITE_BUCKET_ID", "APPWRITE_FUNCTION_PROJECT_ID" ]); if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` A check is also included to ensure that the `LMNT_API_KEY`, `APPWRITE_API_KEY` and `APPWRITE_BUCKET_ID` environment variables are set. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create an HTML web page that the Function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, Add a `` tag that will define the style and scripts. ```html LMNT Demo ``` And after the `` tag add this `` which will contain the actual form: ```html

LMNT Demo

Use this page to test your implementation with LMNT. Enter text and receive an audio response.

``` All of this together will render a form that will submit your text to the Appwrite Function through a `POST` request. The Appwrite function will then call LMNT's API, upload the audio to Appwrite Storage and return the URL, which will be displayed on your page. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Next, you'll add the methods necessary to integrate with the LMNT API. Import the `Speech` class from `lmnt-node`, and the required features from the Appwrite Node.js SDK at the top of the `main.js` file. ```js import { Client, Storage, ID, Permission, Role } from "node-appwrite"; import { InputFile } from "node-appwrite/file"; import Speech from 'lmnt-node'; ``` Next, add code to validate the body of the request and initialize the Appwrite SDK also within `main.js` following the previously added GET handler: ```js const endpoint = process.env.APPWRITE_ENDPOINT ?? "https://.cloud.appwrite.io/v1"; const client = new Client() .setEndpoint(endpoint) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); if (!req.body.text || typeof req.body.text !== "string") { return res.json({ ok: false, error: "Missing required field `text`" }, 400); } ``` Next, send a request to the LMNT API and store the response: ```js const lmnt = new Speech(process.env.LMNT_API_KEY); const response = await lmnt.synthesize(req.body.text, 'lily', { format: 'mp3' }); ``` This code will send the prompt to the LMNT API and return the audio as a blob. Additionally, any errors will be caught and reported for easy debugging. {% /section %} {% section #step-7 step=7 title="Store Audio in Appwrite Storage" %} Store the audio file in Appwrite Storage for easy retrieval later: ```js const storage = new Storage(client); const file = await storage.createFile({ bucketId: process.env.APPWRITE_BUCKET_ID, fileId: ID.unique(), file: InputFile.fromBuffer(new Blob([response.audio]), "audio.mp3"), permissions: [Permission.read(Role.any())], }); ``` To show it to the user, parse the download URL from Appwrite and return it in the response: ```js const url = `${endpoint}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`; return res.json({ ok: true, response: url }); ``` This should finish up the Function. Deploy it to Appwrite by pushing it to the git repository created earlier. {% /section %} {% section #step-8 step=8 title="Test the function" %} Now that the Function is deployed test it by visiting the function URL in your browser. This should show the UI created earlier. To test it, write a prompt and click the submit button. After a brief moment, you should see the audio below the input. ![Testing the function](/images/docs/ai/integrations/lmnt/demo.png) {% /section %} --- ## Integrating OpenAI https://appwrite.io/docs/products/ai/integrations/openai The OpenAI API is a powerful tool that can be used to generate text, images, and more. This tutorial will guide you through the process of setting up the OpenAI API and integrating it into your Appwrite project. We'll create a simple function that takes a text prompt and generates a completion using OpenAI's GPT-3 model. Then, using Appwrite functions we'll create a user interface that allows users to input text and see the generated completion. ### Prerequisites {% #prerequisites %} - An Appwrite Project - An [OpenAI API Key](https://platform.openai.com/account/api-keys) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `OPENAI_API_KEY`, generate it [here](https://platform.openai.com/account/api-keys). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add OpenAI SDK" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Install the `openai` package to simplify the process of interacting with the OpenAI API. ```bash npm install openai ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} For this example, the function will be able to take both `GET` and `POST` requests. For the `GET` request, return a static HTML page that will have a form to submit text to the API. Meanwhile the `POST` request will send the text to the OpenAI API and return the generated text. Write the code to return the static HTML page, to do this create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` If the method is `GET`, it returns the static HTML page. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html OpenAI Demo ``` The code above includes a script that will handle the form submission and a script tag that includes the Alpine.js library. This library will be used to handle the form submission. After the `` tag add a `` containing the visible form: ```html

Prompt ChatGPT Demo

Use this page to test your implementation with OpenAI ChatGPT. Enter text and receive the model output as a response.

``` The form will allows users to submit your text to the Appwrite function through a POST request. The Appwrite function will call the OpenAI API, and return the response to the user. {% /section %} {% section #step-6 step=6 title="Handle POST request" %} Add methods necessary to integrate with the OpenAI API. Import `openai` at the top of the `main.js` file. ```js import { OpenAIApi, Configuration } from 'openai'; ``` Add code to validate the body of the request and initialize the Appwrite SDK: ```js const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT ?? "https://.cloud.appwrite.io/v1") .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); if (!req.body.prompt && typeof req.body.prompt !== "string") { return res.json({ ok: false, error: "Missing required field `prompt`" }, 400); } const openai = new OpenAIApi( new Configuration({ apiKey: process.env.OPENAI_API_KEY, }) ); ``` Make a request to the OpenAI API and return the response: ```js try { const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: req.body.prompt }], }); const completion = response.data.choices[0].message?.content; return res.json({ ok: true, completion }, 200); } catch (err) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } ``` {% /section %} {% section #step-8 step=8 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier and to test it, write a prompt and click the submit button. After a brief moment you should see the generated text from the OpenAI API. {% /section %} --- ## Integrating Perplexity https://appwrite.io/docs/products/ai/integrations/perplexity Integrating Perplexity into your Appwrite project is simple. This tutorial will guide you through the process of setting up the Perplexity API and integrating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - A [Perplexity API Key](https://docs.perplexity.ai/docs/getting-started) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `PERPLEXITY_API_KEY`, generate it [here](https://docs.perplexity.ai/docs/getting-started). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add OpenAI SDK" %} Once the function is created, clone the function and open it in your development environment. The Perplexity API is compatible with the OpenAI SDK, so we can use the OpenAI SDK to interact with Perplexity. Once you have the repository open, install the OpenAI SDK by running the following command in your terminal: ```bash npm install openai ``` Perplexity's API is OpenAI compatible, so we can use the OpenAI SDK to interact with Perplexity. {% /section %} {% section #step-3 step=3 title="Create utility function" %} For our example, our function will be able to take both `GET` and `POST` requests. The function will return a web page on `GET` requests and return a response from Perplexity on `POST` requests. To begin with we will write the code to return the static HTML page. Create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} We're going to write our `GET` request handler in the `src/main.js` file. This handler will return a static HTML page we'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` {% /section %} {% section #step-5 step=5 title="Create static page" %} Create the static HTML page that our function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, we're going to add a `` tag that will define our style and scripts. ```html Perplexity AI Demo ``` And after the `` tag we're going to add our `` which will contain the actual form: ```html

Perplexity AI Demo

Use this page to test your implementation with Perplexity AI. Enter text and receive the model output as a response.

``` All of this together will render a form that will submit your question to the Appwrite Function through a POST request which we'll create next. The Appwrite Function will call Perplexity's API and return the response, which will be displayed on your page. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Now that we're serving a basic HTML page, we can add methods necessary to integrate with Perplexity's API. Import the OpenAI SDK at the top of our `main.js` file: ```js import { OpenAI } from 'openai'; ``` Next, add code to validate the body of the request and initialize the OpenAI SDK with the Perplexity API key: ```js if (!req.body.prompt) { return res.json({ ok: false, error: 'Missing required fields: prompt' }, 400); } const perplexity = new OpenAI({ apiKey: process.env.PERPLEXITY_API_KEY, baseURL: 'https://api.perplexity.ai', }); ``` This code also allows us to modify what model we use by setting the `PERPLEXITY_MODEL` environment variable. Send the request to the Perplexity API and return the response: ```js try { const response = await perplexity.chat.completions.create({ model: 'mistral-7b-instruct', max_tokens: parseInt(process.env.PERPLEXITY_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: req.body.prompt }], stream: false, }); const completion = response.choices[0].message?.content; return res.json({ ok: true, completion }, 200); } catch (err) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } ``` This code will send our prompt to the perplexity chat completions API and return the response to the user, additionally it'll also catch any errors we could encounter and reports them for easy debugging. With our function now complete, you can deploy it to Appwrite by simply pushing the change to your repository. {% /section %} {% section #step-7 step=7 title="Test our function" %} Now that our function is deployed, we can test it by visiting the function URL in our browser. Write a prompt and click the submit button, after a brief moment you should see the completion appear below the input. ![Testing the function](/images/docs/ai/integrations/perplexity/demo.png) {% /section %} --- ## Integrating Pinecone https://appwrite.io/docs/products/ai/integrations/pinecone Pinecone is a vector database that allows you to store and query high-dimensional vectors. It is a great tool for building recommendation systems, search engines, and more. In this tutorial, we'll show you how to integrate Pinecone into your Appwrite project. Inside an Appwrite Function, we'll create a method to that indexes an Appwrite table into Pinecone. We'll also create a method to query the Pinecone index and return the results. ### Prerequisites {% #prerequisites %} - An Appwrite project - An Appwrite table - An [OpenAI API key](https://platform.openai.com/account/api-keys) - A [Pinecone API key](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key) - A Pinecone index {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `PINECONE_API_KEY`, generate it [here](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key). Add the `OPENAI_API_KEY`, generate it [here](https://platform.openai.com/account/api-keys).For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Install the `@pinecone-database/pinecone` package to simplify the process of interacting with the Pinecone API. We'll also install the `openai` package to interact with the OpenAI API. ```bash npm install @pinecone-database/pinecone openai ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} For this example, the function will be able to take both `GET` and `POST` requests. Create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { const html = getStaticFile('index.html'); return res.text(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } }; ``` The code checks if all required environment variables are present and then returns the static HTML page when a `GET` request is made. {% /section %} {% section #step-5 step=5 title="Create web page" %} Create a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, Add a `` tag that will define the style and scripts. ```html Pinecone Demo ``` And after the `` tag add this `` which will contain the actual form: ```html

Pinecone Demo

Use this demo to verify that the sync between Appwrite Databases and Pinecone was successful. Search your Pinecone vector database using the input below.

``` This will render a form that will submit your search query to the function and display the results. {% /section %} {% section #step-6 step=6 title="Setup SDKs" %} Add methods necessary to integrate with the OpenAI and Pinecone APIs Import `openai` and `@pinecone-database/pinecone` at the top of the `main.js` file: ```js import { Pinecone } from '@pinecone-database/pinecone'; import { OpenAI } from 'openai'; ``` Add the following code at the end of request handler in the `main.js` file: ```js const openai = new OpenAI(); const pinecone = new Pinecone(); const pineconeIndex = pinecone.index(process.env.PINECONE_INDEX_ID); ``` The functions checks the request method, and then initializes the OpenAI and Pinecone SDKs. {% /section %} {% section #step-7 step=7 title="Handle search requests" %} To handle the search requests, add the following code to the end of the request handler in the `main.js` file: ```js if (req.path === '/search') { const queryEmbedding = await openai.embeddings.create({ model: 'text-embedding-ada-002', input: req.body.prompt, }); const searchResults = await pineconeIndex.query({ vector: queryEmbedding.data[0].embedding, topK: 5, }); return res.json(searchResults); } ``` For all requests with the path `/search`, the function sends the search query to the OpenAI API to get the embedding. The function then queries the Pinecone index with the embedding and returns the results. {% /section %} {% section #step-8 step=8 title="Handle indexing requests" %} The Appwrite table needs to be indexed into the Pinecone index. Create a new file at `src/appwrite.js` with the following code: ```js import { Client, TablesDB, Query } from 'node-appwrite'; export default class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); } async getAllRows(databaseId, tableId) { const cumulative = []; let cursor = null; do { const queries = [Query.limit(100)]; if (cursor) { queries.push(Query.cursorAfter(cursor)); } const { rows } = await this.tablesDB.listRows({ databaseId, tableId, queries }); if (rows.length === 0) { break; } cursor = rows[rows.length - 1].$id; cumulative.push(...rows); } while (cursor); return cumulative; } } ``` The service provides a method to iterate the rows contained within an entire table, fetching the limit of 100 rows per request. ```js const appwrite = new AppwriteService(); const rows = await appwrite.getAllRows( process.env.APPWRITE_DATABASE_ID, process.env.APPWRITE_TABLE_ID ); const embeddings = await Promise.all( rows.map(async (row) => { const record = await openai.embeddings.create({ model: 'text-embedding-ada-002', input: JSON.stringify(row), }); return { id: row.$id, values: record.data[0].embedding, metadata: row, }; }) ); await pineconeIndex.upsert(embeddings); ``` The code fetches all rows from the Appwrite table, then sends each row to the OpenAI API to get the embedding. The embeddings are then uploaded to the Pinecone index. {% /section %} {% section #step-9 step=9 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier and to test it, write a search query and click the submit button. After a brief moment you should see the matched results. {% /section %} --- ## Integrating Replicate https://appwrite.io/docs/products/ai/integrations/replicate Integrating Replicate into your Appwrite project is simple. This tutorial will guide you through the process of setting up the Replicate API and integrating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - A [Replicate API Key](https://replicate.com/docs/reference/http#authentication) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `REPLICATE_API_KEY`, generate it [here](https://replicate.com/docs/reference/http#authentication). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add Replicate SDK" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the Replicate by running the following command in your terminal: ```bash npm install replicate ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} For our example, our function will be able to take both `GET` and `POST` requests. For the `GET` request, return a static HTML page which we'll write later that will use AlpineJS to make a `POST` request to our function. Meanwhile, our `POST` request will use the Replicate SDK to make a request to the Replicate API. To begin with we will write the code to return the static HTML page, to do this we'll create a new `src/utils.js` file with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} We're going to write our `GET` request handler in the `src/main.js` file. This handler will return a static HTML page we'll create later. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` {% /section %} {% section #step-5 step=5 title="Create static page" %} Create the static HTML page that our function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, we're going to add a `` tag that will define our style and scripts. ```html Replicate Demo ``` And after the `` tag we're going to add our `` which will contain the actual form: ```html

Replicate Demo

Use this page to test your implementation with Replicate. Enter text and receive the model output as a response.

``` All of this together will render a form that will submit your question to the Appwrite Function through a POST request which we'll create next. The Appwrite Function will call Replicate's API and return the response, which will be displayed on your page using different conditional statements depending on the output media type. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Now that we're serving a basic HTML page, we can add methods necessary to integrate with Replicate's API. Import the Replicate SDK at the top of our `main.js` file: ```js import Replicate from "replicate"; ``` Next after we serve the HTML we're going to add code to validate the body of the request, define our models and initialize the Replicate SDK: ```js const models = { 'audio': 'meta/musicgen:b05b1dff1d8c6dc63d14b0cdb42135378dcb87f6373b0d3d341ede46e59e2b38', 'text': 'meta/llama-2-70b-chat', 'image': 'stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b' }; if (req.body.type !== 'audio' && req.body.type !== 'text' && req.body.type !== 'image') { return res.json({ ok: false, error: 'Invalid type' }, 400); } const replicate = new Replicate(); ``` In this example we're going to be using meta's musicgen and llama2 70b models for music and text generation while using Stability AI's SDXL model for image generation. You can find more models on the [Replicate explore page](https://replicate.com/explore). Next we're going to add some per model configurations: ```js let request = { input: { prompt: req.body.prompt, } }; // Allows you to tinker parameters for individual output types switch (req.body.type) { case 'audio': request.input = { ...request.input, length: 30, } break; case 'text': request.input = { ...request.input, max_new_tokens: 512, } break; case 'image': request.input = { ...request.input, width: 512, height: 512, negative_prompt: "deformed, noisy, blurry, distorted", } break; }; ``` This allows us to individually configure each of the models we're using, feel free to play with this configuration to get the best results for your use case. Finally with our request built we can call the replicate API and generate a prediction: ```js let response; try { response = await replicate.run(models[req.body.type], request); } catch (err) { return res.json({ ok: false, error: 'Failed to run model' }, 500); } if (req.body.type === 'image') { response = response[0] } else if (req.body.type === 'text') { response = response.join(''); } return res.json({ ok: true, response, type: req.body.type }, 200); ``` This code will send our prompt to the replicate API and return the response to the user, additionally it'll also catch any errors we could encounter and reports them for easy debugging. With our function now complete, you can deploy it to Appwrite by simply pushing the change to your repository. {% /section %} {% section #step-7 step=7 title="Test our function" %} Now that our function is deployed, we can test it by visiting the function URL in our browser. This should show the UI we created earlier and to test it we can write a prompt and click the submit button, after a brief moment you should see the completion appear below the input. ![Testing the function](/images/docs/ai/integrations/replicate/demo.png) {% /section %} --- ## Integrating TensorFlow with Appwrite https://appwrite.io/docs/products/ai/integrations/tensorflow The TensorFlow API allows you to create powerful machine learning models for various tasks. This tutorial will guide you through the process of setting up a TensorFlow-based text generation model and integrating it into your Appwrite project. We'll create a function that uses TensorFlow to generate text completions based on a given prompt. Using Appwrite functions, we'll build a user interface that allows users to input text and see the generated completion. ### Prerequisites {% #prerequisites %} - An Appwrite Project - Basic knowledge of Python and TensorFlow {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar, and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Python ML** starter template. 1. In the **Variables** step, add any necessary variables like `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT`, and `APPWRITE_FUNCTION_PROJECT_ID`. 1. Follow the step-by-step wizard to create the function. {% /section %} {% section #step-2 step=2 title="Add TensorFlow and necessary packages" %} Once the function is created, navigate to the freshly created repository and clone it to your local machine. Add the necessary dependencies in the `requirements.txt` file: ``` tensorflow numpy ``` Install these packages by running: ```bash pip install -r requirements.txt ``` {% /section %} {% section #step-3 step=3 title="Train the TensorFlow model" %} Create a `src/train.py` file to train your TensorFlow model. This script will download a dataset, preprocess it, and train a model. ```python import tensorflow as tf import numpy as np import os def main(): path_to_file = tf.keras.utils.get_file( "shakespeare.txt", "https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt", ) text = open(path_to_file, "rb").read().decode(encoding="utf-8") vocab = sorted(set(text)) char2idx = {u: i for i, u in enumerate(vocab)} idx2char = np.array(vocab) text_as_int = np.array([char2idx[c] for c in text]) seq_length = 100 char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int) sequences = char_dataset.batch(seq_length + 1, drop_remainder=True) def split_input_target(chunk): input_text = chunk[:-1] target_text = chunk[1:] return input_text, target_text dataset = sequences.map(split_input_target) BATCH_SIZE = 64 BUFFER_SIZE = 10000 dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True) vocab_size = len(vocab) embedding_dim = 256 rnn_units = 1024 model = tf.keras.Sequential( [ tf.keras.layers.Embedding( vocab_size, embedding_dim, batch_input_shape=[BATCH_SIZE, None] ), tf.keras.layers.GRU( rnn_units, return_sequences=True, stateful=True, recurrent_initializer="glorot_uniform", ), tf.keras.layers.Dense(vocab_size), ] ) def loss(labels, logits): return tf.keras.losses.sparse_categorical_crossentropy( labels, logits, from_logits=True ) model.compile(optimizer="adam", loss=loss) EPOCHS = 10 checkpoint_dir = "./training_checkpoints" checkpoint_prefix = f"{checkpoint_dir}/ckpt_{{epoch}}" checkpoint_callback = tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix, save_weights_only=True ) model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback]) model.save("text_generation_model.h5") np.save("char2idx.npy", char2idx) np.save("idx2char.npy", idx2char) os.remove(path_to_file) if __name__ == "__main__": main() ``` {% /section %} {% section #step-4 step=4 title="Create utility functions" %} Create a `src/utils.py` file with utility functions to handle file retrieval and error handling. ```python import os __dirname = os.path.dirname(os.path.abspath(__file__)) static_folder = os.path.join(__dirname, "../static") def get_static_file(file_name: str) -> str: file_path = os.path.join(static_folder, file_name) with open(file_path, "r") as file: return file.read() def throw_if_missing(obj: object, keys: list[str]) -> None: missing = [key for key in keys if key not in obj or not obj[key]] if missing: raise ValueError(f"Missing required fields: {', '.join(missing)}") ``` {% /section %} {% section #step-5 step=5 title="Handle GET request" %} Write the `GET` request handler in the `src/main.py` file. This handler will return a static HTML page. ```python import tensorflow as tf import numpy as np from .utils import get_static_file, throw_if_missing def main(context): if context.req.method == "GET": return context.res.text( get_static_file("index.html"), 200, {"content-type": "text/html; charset=utf-8"}, ) ``` {% /section %} {% section #step-6 step=6 title="Handle POST request" %} Add the methods necessary to integrate with the TensorFlow model. For now, call a placeholder function `generate_text` that returns the prompt as is. ```python def main(context): if context.req.method == "GET": return context.res.text( get_static_file("index.html"), 200, {"content-type": "text/html; charset=utf-8"}, ) try: throw_if_missing(context.req.body, ["prompt"]) except ValueError as err: return context.res.json({"ok": False, "error": err.message}, 400) prompt = context.req.body["prompt"] generated_text = generate_text(prompt) return context.res.json({"ok": True, "completion": generated_text}, 200) ``` {% /section %} {% section #step-7 step=7 title="Build the generate_text function" %} Create the `generate_text` function in the `src/main.py` file to generate text completions using the TensorFlow model. ```python model = tf.keras.models.load_model("text_generation_model.h5") char2idx = np.load("char2idx.npy", allow_pickle=True).item() idx2char = np.load("idx2char.npy", allow_pickle=True) def generate_text(prompt): input_eval = [char2idx[s] for s in prompt] input_eval = tf.expand_dims(input_eval, 0) text_generated = [] temperature = 1.0 model.reset_states() for _ in range(1000): predictions = model(input_eval) predictions = tf.squeeze(predictions, 0) predictions = predictions / temperature predicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy() input_eval = tf.expand_dims([predicted_id], 0) text_generated.append(idx2char[predicted_id]) return prompt + "".join(text_generated) ``` {% /section %} {% section #step-8 step=8 title="Create web page" %} Create a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html Generate with TensorFlow demo

Generate with TensorFlow demo

Use this page to test your implementation with TensorFlow. Enter text and receive the model output as a response.

``` The form will allow users to submit their text to the Appwrite function through a POST request. The Appwrite function will call the TensorFlow model and return the generated text to the user. {% /section %} {% section #step-9 step=9 title="Test the function" %} Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier. To test it, write a prompt and click the submit button. After a brief moment, you should see the generated text from the TensorFlow model. {% /section %} This concludes the tutorial on integrating TensorFlow with Appwrite. You now have a working example of a text generation model integrated with Appwrite functions! --- ## Integrating Together AI https://appwrite.io/docs/products/ai/integrations/togetherai Together AI is an AI as a Service provider that's powered by an industry-leading inference engine providing fast and cheap inference. The platform can generate text and images using leading open-source models such as LLaMA 3 and Stable Diffusion. Integrating Together AI into your Appwrite project is simple. This tutorial will guide you through setting up the Together AI API and integrating it into your Appwrite project. ### Prerequisites {% #prerequisites %} - An Appwrite Project - An Appwrite Bucket - A [Together AI API Key](https://docs.together.ai/reference/authentication-1) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console), click, on **Functions** in the left sidebar, and click, the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add `APPWRITE_BUCKET_ID` and `TOGETHER_API_KEY`. You can generate your Together AI key [here](https://api.together.xyz/settings/api-keys). For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**.. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add Undici" %} Once the function is created, clone and open it in your development environment. Once inside the cloned function, install `undici` ( an HTTP client ) to interact with Together AI's API. ``` npm install undici ``` {% /section %} {% section #step-3 step=3 title="Create utility function" %} For this example, the function will be able to handle both `GET` and `POST` requests. For the `GET` request, return a static HTML landing page, that will use AlpineJS to make a `POST` request to our function. Meanwhile, the `POST` request will use fetch to make a request to the Together AI API. In preparation for the `GET` request handler, create a new `src/utils.js` file with some utility functions: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` {% /section %} {% section #step-4 step=4 title="Handle GET request" %} Write the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page, which will be created in the next section. ```js import { getStaticFile } from './utils.js'; export default async ({ req, res, error }) => { if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` {% /section %} {% section #step-5 step=5 title="Create static page" %} Create the static HTML page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate: ```html ``` Within the `` tag, add a `` tag that will define the style and scripts. ```html Together AI Demo ``` And after the `` tag add a `` which will contain the actual form: ```html

Together AI Demo

Use this page to test your implementation with Together AI. Enter text and receive the model output as a response.

``` Together, this will render a form to submit a query to the Appwrite Function through a `POST` request. The Appwrite Function invokes Together AI's API and returns the response, which will be displayed on the page using different conditional statements depending on the output media type. {% /section %} {% section #step-6 step=6 title="Handle POST Request" %} Now that you're serving a basic HTML page add methods necessary to integrate with Together AI's API. Import `fetch` from `undici` and the Appwrite SDK at the top of `src/main.js`. ```js import { fetch } from 'undici' import { Client, ID, Storage } from 'node-appwrite'; import { InputFile } from 'node-appwrite/file'; ``` To handle the `POST` request, add the following code to the end of the request handler in the `src/main.js` file to validate the request body and define the models: ```js const models = { 'text': 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'image': 'stabilityai/stable-diffusion-xl-base-1.0' }; if (!req.body.prompt || typeof req.body.prompt !== 'string') { return res.json({ ok: false, error: 'Missing required field `prompt`' }, 400); } if (req.body.type !== 'text' && req.body.type !== 'image') { return res.json({ ok: false, error: 'Invalid field `type`' }, 400); } ``` In this example, you will use Mistral's `Mixtral 8x7B` for text generation and StabilityAI's `Stable Diffusion XL` for image generation. You can find more models on [Together AI's docs](https://docs.together.ai/docs/inference-models). Next, following the previous code dd some per-model configurations: ```js let request = { model: models[req.body.type], }; switch (req.body.type) { case 'text': request = { ...request, messages: [ { role: "system", content: "You are a helpful assistant" }, { role: "user", content: req.body.prompt } ], max_tokens: 512, repetition_penalty: 1, } break; case 'image': request = { ...request, prompt: req.body.prompt, width: 512, height: 512, steps: 20, results: 1, negative_prompt: "deformed, noisy, blurry, distorted", } break; }; ``` This allows you to configure each of the models you use individually. Feel free to play with this configuration to get the best results for your use case. Finally, with the request built, you can call the Together AI API and generate a prediction: ```js let response; let url = 'https://api.together.xyz/v1/completions'; if (req.body.type === 'text') { url = 'https://api.together.xyz/v1/chat/completions' }; try { response = await fetch(URL, { headers: { "content-type": "application/json", "Authorization": `Bearer ${process.env.TOGETHER_API_KEY}` }, method: 'POST', body: JSON.stringify(request) }) } catch (err) { error(err); return res.json({ ok: false, error: 'Failed to run model' }, 500); } let resJson = await response.json(); // Upload image to Appwrite Storage and return URL if (req.body.type === 'image') { const endpoint = process.env.APPWRITE_ENDPOINT || 'https://.cloud.appwrite.io/v1' const client = new Client() .setEndpoint(endpoint) .setKey(process.env.APPWRITE_API_KEY) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) const storage = new Storage(client); let data = Buffer.from(resJson.choices[0].image_base64, 'base64'); let file = await storage.createFile({ bucketId: process.env.APPWRITE_BUCKET_ID, fileId: ID.unique(), file: InputFile.fromBuffer(data, "image.png") }); return res.json({ ok: true, type: req.body.type, response: `${endpoint}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file["$id"]}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}` }) } return res.json({ ok: true, type: req.body.type, response: resJson.choices[0].message.content}, 200); ``` This code sends the prompt to the Together AI API and returns the response to the user. The function also uploads any images generated to a bucket and returns the URL to the user. Any errors encountered during the process are caught and reported for easy debugging. The function can now be deployed to Appwrite by pushing the changes to your repository. {% /section %} {% section #step-7 step=7 title="Test the function" %} Now that the function is deployed test it by visiting the function URL in your browser. This should show the UI created earlier. To test it, write a prompt and click the submit button. After a brief moment, you should see the results. ![Testing the function](/images/docs/ai/integrations/together/demo.png) {% /section %} --- ## Natural language processing https://appwrite.io/docs/products/ai/natural-language Natural language processing (NLP) is a fascinating intersection of computer science, artificial intelligence, and linguistics. It's about teaching computers to understand, interpret, and generate human language (Jones et al., 2018). Translating languages, answering questions, or helping find information, NLP is at the heart of many technologies we use every day. ### Tutorials {% cards %} {% cards_item href="/docs/products/ai/tutorials/text-generation" title="Text generation" %} Generate text from a prompt {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/language-translation" title="Language translation" %} Translate text from one language to another {% /cards_item %} {% /cards %} --- ## Image classification with Hugging Face https://appwrite.io/docs/products/ai/tutorials/image-classification Learn to setup an Appwrite Function utilizing image classification with Hugging Face. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the Appwrite NodK and the Hugging Face inference SDK by running the following command in your terminal: ```bash npm install @huggingface/inference node-appwrites ``` {% /section %} {% section #step-3 step=3 title="Parse payload body" %} After installing the SDK, write the code that will accept a JSON body. The function will serve two purposes: it can recieve a body via direct execution or it can be called via a file create event. Open up your `src/main.js` file and replace the function body with the following code: ```js export default async ({ req, res, log, error }) => { const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai'; const tableId = process.env.APPWRITE_TABLE_ID ?? 'image_classification'; const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'image_classification'; // Allows using direct execution or file create event const fileId = req.body.$id || req.body.imageId; if (!fileId) { return res.text('Bad request', 400); } // Only allow specific bucketId if ( req.body.bucketId && req.body.bucketId != bucketId ) { return res.text('Bad request', 400); } } ``` {% /section %} {% section #step-4 step=4 title="Create Storage bucket" %} In order for this function to work, create a new bucket in the Appwrite Storage. You can do this by navigating to the Appwrite Console and clicking on **Storage** in the left sidebar, then clicking on the **Create Bucket** button. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} Use the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later. {% /section %} {% section #step-6 step=6 title="Create Appwrite table" %} Before saving the classification result to Appwrite Databases, create a new database and table in the Appwrite Console. Navigate to the Appwrite Console and click on **Database** in the left sidebar, then click on the **Create database** button, and name it, for example `AI`. Once you've created the database, click on the **Create table** button and create a new table, and name it, for example `Image Labels`. Next, create the following schema for the table: | Column | Type | Size | Required | Array | | --------- | --------- | --------- | --------- | --------- | | image | String | 256 | Yes | No | | labels | String | 256 | Yes | Yes | {% only_dark %} ![Image Classification Database](/images/docs/ai/tutorials/image-classification/dark/database.png) {% /only_dark %} {% only_light %} ![Image Classification Database](/images/docs/ai/tutorials/image-classification/database.png) {% /only_light %} {% /section %} {% section #step-7 step=7 title="Downloading image" %} With the payload parsed, now you can download the image from Appwrite Storage. Create a new file called `appwrite.js` in the `src` directory and add the following code: ```js import { Client, TablesDB, ID, Storage } from 'node-appwrite'; class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); this.storage = new Storage(client); } async getFile(bucketId, fileId) { return await this.storage.getFileDownload({ bucketId, fileId }); } } export default AppwriteService; ``` This code creates a new `AppwriteService` class that initializes the Appwrite client and provides a method to download a file from the Appwrite Storage. Import the class into the `src/index.js` file, at the top of the file, add the following line: ```js import AppwriteService from './appwrite.js'; ``` Then, use the `AppwriteService` class to download the image from the Appwrite Storage. After the bucket check in `main.js` add the following code: ```js const appwrite = new AppwriteService(); file = await appwrite.getFile(bucketId, fileId); ``` This code will download the file from the Appwrite Storage and return a `404 - File Not Found` status code if the file is not found or a `400 - Bad request` status code if an error occurs. {% /section %} {% section #step-8 step=8 title="Integrate with Huggingface" %} With the image downloaded, use the Hugging Face inference SDK to classify the image. At the top of the `src/index.js` file, add: ```js import { HfInference } from '@huggingface/inference'; ``` Use the Hugging Face SDK and classify the image, for this task you can use various models that you can find [on Hugging Face.](https://huggingface.co/models?pipeline_tag=image-classification&sort=trending) This example uses the `microsoft/resnet-50` model. ```js const hf = new HfInference(process.env.HUGGING_FACE_API_KEY); const result = await hf.imageClassification({ data: file, model: 'microsoft/resnet-50', }); ``` {% /section %} {% section #step-9 step=9 title="Save result" %} With the image classified, save the result to the Appwrite Databases. To begin, add a new function to the `appwrite.js` file created earlier which will add these records in the database. ```js async createImageLabels(databaseId, tableId, imageId, labels) { await this.tablesDB.createRow({ databaseId, tableId, rowId: ID.unique(), data: { image: imageId, labels, } }); } ``` In the `main.js` file, save the result to the Appwrite Database. Add the following code: ```js await appwrite.createImageLabels(databaseId, tableId, fileId, result); log('Image ' + fileId + ' classified', result); return res.json(result); ``` {% /section %} {% section #step-10 step=10 title="Configure events" %} To test the function attach it directly to the Storage bucket using events. Navigate to your function in the Appwrite Console, under **Settings** > **Events**, click on the **Add Event** button. At the bottom of the dialog within the text input, click on the pen icon and enter `buckets.[Bucket ID].files.*.create`. Making sure to replace `[Bucket ID]` with the ID of the bucket you created earlier. {% only_dark %} ![Image Classification Event](/images/docs/ai/tutorials/image-classification/dark/event.png) {% /only_dark %} {% only_light %} ![Image Classification Event](/images/docs/ai/tutorials/image-classification/event.png) {% /only_light %} {% /section %} {% section #step-10 step=10 title="Test the function" %} Commit the changes to the repository and deploy the function. Test the function by uploading an image to the Appwrite Storage. Navigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image. After a few seconds, you should see an execution appear in the function's execution logs and the classification result should be saved to the Appwrite Database. {% only_dark %} ![Image Classification Test](/images/docs/ai/tutorials/image-classification/dark/result.png) {% /only_dark %} {% only_light %} ![Image Classification Test](/images/docs/ai/tutorials/image-classification/result.png) {% /only_light %} {% /section %} --- ## Language translation with Hugging Face https://appwrite.io/docs/products/ai/tutorials/language-translation Learn to setup an Appwrite Function utilizing language translation with Hugging Face. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add HuggingFace SDK" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal: ```bash npm install @huggingface/inference ``` {% /section %} {% section #step-3 step=3 title="Parse payload body" %} After installing the SDK, write the code that will accept a validate the request body. Open up your `src/main.js` file and replace the function body with the following code: ```js export default async ({ req, res }) => { if (!req.body.source || typeof req.body.source !== 'string') { return res.json({ ok: false, error: 'Missing requrired field `source`', }, 400); } } ``` {% /section %} {% section #step-4 step=4 title="Make a request to Hugging Face" %} Add the following import at the top of your `src/main.js` file: ```js import { HfInference } from '@huggingface/inference'; ``` In your function body, add the following code after the parameter checks: ```js export default async ({ req, res }) => { // ... existing parameter checks const hf = new HfInference(process.env.HUGGINGFACE_ACCESS_TOKEN); try { const translation = await hf.translation({ model: 'facebook/mbart-large-50-many-to-many-mmt', inputs: req.body.source, parameters: { src_lang: 'en_XX', // English locale tgt_lang: 'fr_XX', // French locale } }); return res.json({ ok: true, output: translation.translation_text }); } catch (err) { return res.json({ ok: false, error: 'Failed to query Hugging Face' }, 500); } } ``` First, ensure the function is called with method `POST`. Then, make a request to the Hugging Face API to translate the `source` text from English to French. You can change the `src_lang` and `tgt_lang` parameters to any language supported by the model you choose. {% /section %} {% section #step-5 step=5 title="Test the function" %} Test our function by sending a POST request to the function's endpoint with a JSON body containing the `source` parameter. Navigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body: ```json { "source": "Hello, how are you?" } ``` Click **Execute** and you should see a response similar to the following: ```json { "ok": true, "output": "Bonjour, comment ça va?" } ``` {% /section %} --- ## Music generation with Hugging Face https://appwrite.io/docs/products/ai/tutorials/music-generation Hugging Face is a platform that hosts ML models for all types of applications, including music generation. This example uses the "facebook/musicgen-large" from Hugging Face to convert text to music, but the same concept can be applied to other models. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, clone the function and open it in your development environment. Install the `undici` package (global `fetch` is not available in Node.js 16) to make requests to the Hugging Face API. Install the `node-appwrite` package, to simplify uploading the generated audio file to Appwrite Storage. ```bash npm install undici node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Create an Appwrite service" %} The function will interact with Appwrite to store the generated audio files and the text-to-speech data. To make this easier, create a service class that will handle all the Appwrite interactions. Create a file called `src/appwrite.js` and implement the following class: ```js import { Client, ID, Storage } from 'node-appwrite'; import { InputFile } from 'node-appwrite/file'; class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); this.storage = new Storage(client); } async createFile(bucketId, blob) { const file = await InputFile.fromBuffer(blob, 'audio.flac'); return await this.storage.createFile({ bucketId: bucketId, fileId: ID.unique(), file: file }); } } export default AppwriteService; ``` {% /section %} {% section #step-4 step=4 title="Create Storage bucket" %} In order for this function to work, create a new bucket in the Appwrite Storage. You can do this by navigating to the Appwrite Console and clicking on **Storage** in the left sidebar, then clicking on the **Create Bucket** button. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} Use the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later. {% /section %} {% section #step-5 step=5 title="Integrate with Hugging Face" %} In `src/main.js`, add the function to convert text to speech using the Hugging Face API. ```js import { fetch } from 'undici'; import { throwIfMissing } from './utils.js'; import AppwriteService from './appwrite.js'; const HUGGINGFACE_API = 'https://api-inference.huggingface.co'; export default async ({ req, res, error }) => { const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'generated_music'; const response = await fetch( `${HUGGINGFACE_API}/models/facebook/musicgen-small`, { headers: { Authorization: `Bearer ${process.env.HUGGINGFACE_ACCESS_TOKEN}`, }, method: 'POST', body: JSON.stringify({ inputs: req.body.prompt, }), } ); const blob = await response.blob(); const appwrite = new AppwriteService(); const file = await appwrite.createFile(bucketId, blob); return res.json({ ok: true, fileId: file.$id, }); }; ``` This Appwrite Function checks if the required environment variables are set, then processes the text using the Hugging Face API, stores the generated audio file in Appwrite Storage. {% /section %} {% section #step-6 step=6 title="Test the function" %} Test the function by sending a POST request to the function's endpoint with a JSON body containing the `prompt` parameter. Navigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body: ```json { "prompt": "A happy tune, with a fast tempo, in the key of C major" } ``` Click **Execute** and you should see a response similar to the following: ```json { "ok": true, "fileId": "61f7b3b3c7b7d" } ``` Use the `fileId` to download the generated audio file from the Appwrite Storage. Here's an example of music generated from the prompt above: ![Audio of generated music](/audio/docs/ai/tutorials/music-generation/generated-music.wav) {% /section %} --- ## Object detection with Hugging Face https://appwrite.io/docs/products/ai/tutorials/object-detection Learn to setup an Appwrite Function utilizing object detection with Hugging Face. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create a new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal: ```bash npm install @huggingface/inference node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Parsing the body" %} After installing the SDK, write the code that will parse the body of the request. The function will serve two purposes: it can recieve a body via direct execution or it can be called via a file create event. Open up your `src/index.js` file and replace the function body with the following code: ```js export default async ({ req, res, log, error }) => { const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai'; const tableId = process.env.APPWRITE_TABLE_ID ?? 'image_classification'; const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'image_classification'; // Allows using direct execution or file create event const fileId = req.body.$id || req.body.imageId; if (!fileId) { return res.text('Bad request', 400); } if ( req.body.bucketId && req.body.bucketId != bucketId ) { return res.text('Bad request', 400); } } ``` This code will parse the body of the request and check if the request is a POST request. It will then check if the request contains the required fields and if the bucket ID matches the one we set in the environment variables. {% /section %} {% section #step-4 step=4 title="Setting up your Appwrite Storage bucket" %} Create a new bucket in the Appwrite Storage. Navigate to the Appwrite Console and click on **Storage** in the left sidebar, then the **Create Bucket** button. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} Note down the bucket ID so we can add it as an environment variable later. {% /section %} {% section #step-5 step=5 title="Downloading the image using Appwrite Storage" %} With the payload parsed, you can now download the image from the Appwrite Storage. Create a new file in the `src` directory called `appwrite.js` and add the following code: ```js import { Client, TablesDB, ID, Storage } from 'node-appwrite'; class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); this.storage = new Storage(client); } async getFile(bucketId, fileId) { return await this.storage.getFileDownload({ bucketId, fileId }); } } export default AppwriteService; ``` This code creates a new `AppwriteService` class that initializes the Appwrite client and provides a method to download a file from the Appwrite Storage. Import the class into the `src/index.js` file, at the top of the file, add the following line: ```js import AppwriteService from './appwrite.js'; ``` Then, we can use the `AppwriteService` class to download the image from the Appwrite Storage. After the bucket check within `main.js` add the following code: ```js const appwrite = new AppwriteService(); const file = await appwrite.getFile(bucketId, fileId); ``` This code will download the file from the Appwrite Storage and return a `404 - File Not Found` status code if the file is not found or a `400 - Bad request` status code if an error occurs. {% /section %} {% section #step-6 step=6 title="Detecting objects in the image" %} With the image downloaded, we can now use the Hugging Face inference SDK to classify the image. At the top of the `src/index.js` file, add: ```js import { HfInference } from '@huggingface/inference'; ``` Next we're going to use the Hugging Face SDK and classify the image, for this task we can use various models that you can find [on Hugging Face.](https://huggingface.co/models?pipeline_tag=object-detection&sort=trending) For this example we'll be using the `facebook/detr-resnet-50` model. ```js const hf = new HfInference(process.env.HUGGING_FACE_API_KEY); const result = await hf.objectDetection({ data: file, model: 'facebook/detr-resnet-50', }); ``` {% /section %} {% section #step-7 step=7 title="Setting up our database" %} Before we can save our detection result to the Appwrite Database, we need to create a new database and table in the Appwrite Console. Navigate to the Appwrite Console and click on **Database** in the left sidebar, then click on the **Create database** button, you can call this database anything you like, for this example we'll call it `AI`. Once you've created the database, click on the **Create table** button and create a new table, once again you can call it anything you want but for this example we'll call it `Image Labels`. Add two string columns to our table, `image` and `labels`. The `image` column will store the ID of the image we're detecting objects in and the `labels` column will store the detection result. Both of these columns should be `required` with `image` having the size of `256` and `labels` having the size of around `4096`. {% only_dark %} ![Object detection database](/images/docs/ai/tutorials/object-detection/dark/database.png) {% /only_dark %} {% only_light %} ![Object detection database](/images/docs/ai/tutorials/object-detection/database.png) {% /only_light %} {% /section %} {% section #step-8 step=8 title="Saving the object detection result" %} With the image classified, we can now save the result to the Appwrite Database. To begin with we're going to add a new function to the `appwrite.js` file we created earlier which will create these records in the database. ```js async createImageLabels(databaseId, tableId, imageId, labels) { await this.tablesDB.createRow({ databaseId, tableId, rowId: ID.unique(), data: { image: imageId, labels: JSON.stringify(labels), } }); } ``` This abstraction keeps our codebase clean and makes it easier to test and maintain. Next, using the function we just added we can save the object detection result to the Appwrite Database. We'll also add some logging and error handling to make sure everything is working as expected. Add the following code: ```js await appwrite.createImageLabels(databaseId, tableId, fileId, result); log('Image ' + fileId + ' detected', result); return res.json(result); ``` {% /section %} {% section #step-9 step=9 title="Configuring events" %} To test our function we're going to attach it directly to our Storage bucket using events. Navigate to your function in the Appwrite Console and visit it's **settings** tab, then under the **Events** section click on the **Add Event** button. At the bottom of the dialog within the text input, click on the pen icon and enter `buckets.[Bucket ID].files.*.create`. Making sure to replace `[Bucket ID]` with the ID of the bucket you created earlier. {% only_dark %} ![Object detection event](/images/docs/ai/tutorials/object-detection/dark/event.png) {% /only_dark %} {% only_light %} ![Object detection event](/images/docs/ai/tutorials/object-detection/event.png) {% /only_light %} {% /section %} {% section #step-10 step=10 title="Testing the function" %} Test our function by uploading an image to the Appwrite Storage. Navigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image. After a few seconds, you should see an execution appear in the function's execution logs and the object detection result should be saved to the Appwrite Database. {% only_dark %} ![Object detection test](/images/docs/ai/tutorials/object-detection/dark/result.png) {% /only_dark %} {% only_light %} ![Object detection test](/images/docs/ai/tutorials/object-detection/result.png) {% /only_light %} {% /section %} --- ## Speech recognition with Hugging Face https://appwrite.io/docs/products/ai/tutorials/speech-recognition Hugging Face is a platform that hosts ML models for all types of applications, including for speech recognition. This example uses the `openai/whisper-large-v3` from Hugging Face to perform speech recognition. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, clone the function and open it in your development environment. Install the Hugging Face SDK and the Appwrite Node.js SDK so we can upload the generated audio file to Appwrite Storage. ```bash npm install @huggingface/inference node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Create an Appwrite service" %} The function will interact with Appwrite to store the original audio and generated text transcript. To make this easier, create a service class that will handle all the Appwrite interactions. Create a file called `src/appwrite.js` and implement the following class: ```js import { Client, TablesDB, ID, Storage } from 'node-appwrite'; class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.tablesDB = new TablesDB(client); this.storage = new Storage(client); } async createRecognitionEntry(databaseId, tableId, audioId, speech) { await this.tablesDB.createRow({ databaseId, tableId, rowId: ID.unique(), data: { audio: audioId, speech: speech, } }); } async getFile(bucketId, fileId) { return await this.storage.getFileDownload({ bucketId, fileId }); } } export default AppwriteService; ``` The constructor initializes the Appwrite client and the database and storage services. This `createRecognitionEntry` method creates a row in the Appwrite database with the audio and speech recognition text. This `getFile` method retrieves a file from Appwrite Storage. {% /section %} {% section #step-4 step=4 title="Create Storage bucket" %} In order for this function to work, create a new bucket in the Appwrite Storage. You can do this by navigating to the Appwrite Console and clicking on **Storage** in the left sidebar, then clicking on the **Create Bucket** button. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} Use the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later. {% /section %} {% section #step-5 step=5 title="Create Appwrite table" %} Before saving the classification result to Appwrite Databases, create a new database and table in the Appwrite Console. Navigate to the Appwrite Console and click on **Database** in the left sidebar, then click on the **Create database** button, and name it, for example `AI`. Once you've created the database, click on the **Create table** button and create a new table, and name it, for example `Speech Recognition`. Next, create the following schema for the table: | Column | Type | Size | Required | Array | | --------- | --------- | --------- | --------- | --------- | | audio | String | 64 | true | false | | speech | String | 10000 | true | false | {% /section %} {% section #step-6 step=6 title="Integrate with Hugging Face" %} In `src/main.js` implement the following function to convert speech to a text transcript using the Hugging Face API. ```js import { HfInference } from '@huggingface/inference'; import AppwriteService from './appwrite.js'; export default async ({ req, res, log, error }) => { const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai'; const tableId = process.env.APPWRITE_TABLE_ID ?? 'speech_recognition'; const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'speech_recognition'; let fileId = req.body.$id || req.body.fileId; if (!fileId) { return res.text('Bad request', 400); } if ( req.body.bucketId && req.body.bucketId != bucketId ) { return res.text('Bad request', 400); } const appwrite = new AppwriteService(); const file = await appwrite.getFile(bucketId, fileId); const hf = new HfInference(process.env.HUGGING_FACE_API_KEY); const result = await hf.automaticSpeechRecognition({ data: file, model: 'openai/whisper-large-v3', }); await appwrite.createRecognitionEntry(databaseId, tableId, fileId, result.text); log('Audio ' + fileId + ' recognised', result.text); return res.json({ text: result.text }); }; ``` This Appwrite Function checks if the required environment variables are set, then load the original audio from Appwrite Storage. The function processes the audio file using the Hugging Face API, stores the generated text transcript in Appwrite Databases and returns the transcript text. {% /section %} {% section #step-7 step=7 title="Test the function" %} Test our function by uploading an audio file the Appwrite Storage. Navigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image. After a few seconds, you should see an execution appear in the function's execution logs and the classification result should be saved to the Appwrite Database. {% only_dark %} ![Speech recognition test](/images/docs/ai/tutorials/speech-recognition/dark/result.png) {% /only_dark %} {% only_light %} ![Speech recognition test](/images/docs/ai/tutorials/speech-recognition/result.png) {% /only_light %} {% /section %} --- ## Text generation with Hugging Face https://appwrite.io/docs/products/ai/tutorials/text-generation Learn to setup an Appwrite Function utilizing text generation with Hugging Face. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add HuggingFace SDK" %} Once the function is created, clone the function and open it in your development environment. Once you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal: ```bash npm install @huggingface/inference ``` {% /section %} {% section #step-3 step=3 title="Parse payload body" %} After installing the SDK, write the code that will accept a JSON body. Open up your `src/main.js` file and replace the function body with the following code: ```js export default async ({ req, res }) => { if (!req.body.prompt || typeof req.body.prompt !== 'string') { return res.json({ ok: false, error: 'Missing required field `prompt`' }, 400); } } ``` {% /section %} {% section #step-4 step=4 title="Make a request to Hugging Face" %} Add the following import at the top of your `src/main.js` file: ```js import { HfInference } from '@huggingface/inference'; ``` In your function body, add the following code after the parameter checks: ```js export default async ({ req, res }) => { // ... existing parameter checks const hf = new HfInference(process.env.HUGGINGFACE_ACCESS_TOKEN); try { const completion = await hf.textGeneration({ model: 'mistralai/Mistral-7B-Instruct-v0.2', inputs: req.body.prompt, max_new_tokens: req.body.max_new_tokens || 200, }); return res.json({ ok: true, completion }, 200); } catch (err) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } } ``` The function makes a request to the Hugging Face API with the prompt provided in the request body. The response will be sent back to the client. {% /section %} {% section #step-5 step=5 title="Test the function" %} Test our function by sending a POST request to the function's endpoint with a JSON body containing the `prompt` parameter. Navigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body: ```json { "prompt": "Write a story about a dragon", } ``` Click **Execute** and you should see a response similar to the following: ```json { "ok": true, "completion": "Once upon a time, in a land far away, there was a dragon... [truncated]" } ``` {% /section %} --- ## Text to Speech with Hugging Face https://appwrite.io/docs/products/ai/tutorials/text-to-speech Hugging Face is a platform that hosts ML models for all types of applications, including text to speech. This example uses the "ESPnet2 TTS pretrained model" from Hugging Face to convert text to speech, but the same concept can be applied to other models. ### Prerequisites {% #prerequisites %} - An Appwrite project - A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token) {% section #step-1 step=1 title="Create new function" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. {% only_dark %} ![Create function screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/template.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Click **Create function**. 1. Under **Connect Git repository**, select your provider. 1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template. 1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token). For the `APPWRITE_API_KEY`, tick the box to **Generate API key on completion**. 1. Follow the step-by-step wizard and create the function. {% /section %} {% section #step-2 step=2 title="Add dependencies" %} Once the function is created, clone the function and open it in your development environment. Install the `undici` package (global `fetch` is not available in Node.js 16) to make requests to the Hugging Face API. Install the `node-appwrite` package, to simplify uploading the generated audio file to Appwrite Storage. ```bash npm install undici node-appwrite ``` {% /section %} {% section #step-3 step=3 title="Create an Appwrite service" %} The function will interact with Appwrite to store the generated audio files and the text-to-speech data. To make this easier, create a service class that will handle all the Appwrite interactions. Create a file called `src/appwrite.js` and implement the following class: ```js import { Client, ID, Storage } from 'node-appwrite'; import { InputFile } from 'node-appwrite/file'; class AppwriteService { constructor() { const client = new Client(); client .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1' ) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.storage = new Storage(client); } async createFile(bucketId, blob) { const file = await InputFile.fromBuffer(blob, 'audio.flac'); return await this.storage.createFile({ bucketId: bucketId, fileId: ID.unique(), file: file }); } } export default AppwriteService; ``` {% /section %} {% section #step-4 step=4 title="Create Storage bucket" %} In order for this function to work, create a new bucket in the Appwrite Storage. You can do this by navigating to the Appwrite Console and clicking on **Storage** in the left sidebar, then clicking on the **Create Bucket** button. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} Use the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later. {% /section %} {% section #step-5 step=5 title="Integrate with Hugging Face" %} in `src/main.js` implement the following function to convert text to speech using the Hugging Face API. ```js import fetch from 'node-fetch'; import { throwIfMissing } from './utils.js'; import AppwriteService from './appwrite.js'; const HUGGINGFACE_API = 'https://api-inference.huggingface.co'; export default async ({ req, res, error }) => { const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'generated_speech'; if (!req.body.text || typeof req.body.text !== 'string') { return res.json({ ok: false, error: 'Missing required field `text`' }, 400); } const response = await fetch( `${HUGGINGFACE_API}/models/espnet/kan-bayashi_ljspeech_vits`, { headers: { Authorization: `Bearer ${process.env.HUGGINGFACE_ACCESS_TOKEN}`, }, method: 'POST', body: JSON.stringify({ inputs: req.body.text, }), } ); if (!response.ok) { error(await response.text()); return res.json({ ok: false, error: 'Failed to process text' }, 500); } const blob = await response.blob(); const appwrite = new AppwriteService(); const file = await appwrite.createFile(bucketId, blob); return res.json({ ok: true, fileId: file.$id, }); }; ``` This Appwrite Function checks if the required environment variables are set, then processes the text using the Hugging Face API, stores the generated audio file in Appwrite Storage, and creates a row in the Appwrite database of the original text. {% /section %} {% section #step-6 step=6 title="Test the function" %} Test the function by sending a POST request to the function's endpoint with a JSON body containing the `text` parameter. Navigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body: ```json { "text": "Hello, world!" } ``` Click **Execute** and you should see a response similar to the following: ```json { "ok": true, "fileId": "61f7b3b3c7b7d" } ``` Then, use the fileId to download the generated audio file from the Appwrite Storage. {% /section %} --- ## Video processing https://appwrite.io/docs/products/ai/video-processing --- ## Authentication https://appwrite.io/docs/products/auth Appwrite **Authentication** delivers more than just user sign up and log in. Authentication makes it easy to build secure and robust authentication with support for many different authentication methods. {% arrow_link href="/docs/products/auth/quick-start" %} Add authentication to your app in 5 minutes {% /arrow_link %} ### Authentication methods {% #auth-methods %} Appwrite supports a variety of authentication methods to fit every app and every niche. Explore Appwrite's authentication flows. {% cards %} {% cards_item href="/docs/products/auth/email-password" title="Email and password" %} Email and password login with just a few lines of code secured with state of the art Argon2 hashing. {% /cards_item %} {% cards_item href="/docs/products/auth/phone-sms" title="Phone (SMS)" %} Log in users without a password using their phone number and SMS verification. {% /cards_item %} {% cards_item href="/docs/products/auth/magic-url" title="Magic URL" %} Passwordless login with a magic link sent to the user's email. {% /cards_item %} {% cards_item href="/docs/products/auth/email-otp" title="Email OTP" %} Generate a time-based single-use password sent to the user's email. {% /cards_item %} {% cards_item href="/docs/products/auth/oauth2" title="OAuth 2" %} Authenticate users with existing accounts from GitHub, Google, Facebook, and 30+ other providers. {% /cards_item %} {% cards_item href="/docs/products/auth/anonymous" title="Anonymous" %} Create guest sessions for visitors and convert to full accounts when they're ready. {% /cards_item %} {% cards_item href="/docs/products/auth/jwt" title="JWT" %} Deligate access for a user through passing JWT tokens. {% /cards_item %} {% cards_item href="/docs/products/auth/server-side-rendering" title="Server-side rendering (SSR)" %} Authenticate users in server-side rendered applications. {% /cards_item %} {% cards_item href="/docs/products/auth/custom-token" title="Custom token" %} Implement custom authentication methods like biometric and passkey login by generating custom tokens. {% /cards_item %} {% cards_item href="/docs/products/auth/mfa" title="Multifactor authentication (MFA)" %} Implementing MFA to add extra layers of security to your app. {% /cards_item %} {% /cards %} ### Flexible permissions {% #flexible-permissions %} When users sign up using Appwrite, their identity is automatically attached to a robust permissions system. Appwrite Authentication provides permissions for individual users and groups of users through [teams](/docs/products/auth/teams) and [labels](/docs/products/auth/labels). ### Built in preferences {% #built-in-preferences %} Appwrite **Authentication** comes with built-in [preferences](/docs/products/auth/preferences) for users to manage their account settings. Store notification settings, themes, and other user preferences to be shared across devices. --- ## Accounts https://appwrite.io/docs/products/auth/accounts Appwrite Account API is used for user signup and login in client applications. Users can be organized into teams and be given labels, so they can be given different permissions and access different resources. {% partial file="account-vs-user.md" /%} ### Signup and login {% #signup-login %} You can signup and login a user with an account create through [email password](/docs/products/auth/email-password), [phone (SMS)](/docs/products/auth/phone-sms), [Anonymous](/docs/products/auth/anonymous), [magic URL](/docs/products/auth/magic-url), and [OAuth 2](/docs/products/auth/oauth2) authentication. ### Permissions {% #permissions %} You can grant permissions to all users using the `Role.users()` role or individual users using the `Role.user(, )` role. | Description | Role | | ------------------------------------------- | ------------------------------------------- | | Verified users | `Role.users('verified')`| | Unverified users | `Role.users('unverified')` | | Verified user | `Role.user(, 'verified')`| | Unverified user | `Role.user(, 'unverified')` | {% arrow_link href="/docs/advanced/platform/permissions" %} Learn more about permissions {% /arrow_link %} --- ## Anonymous login https://appwrite.io/docs/products/auth/anonymous Anonymous sessions allow you to implement **guest** users. Guest users let you store user information like items in their cart or theme preferences before they create an account. This reduces the friction for your users to get started with your app. **If a user later creates an account**, their information will be inherited by the newly created account. ### Create anonymous session {% #createSession %} Create an anonymous session with [Create Anonymous Session](/docs/references/cloud/client-web/account#createAnonymousSession) method. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const promise = account.createAnonymousSession(); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final user = await account.createAnonymousSession(); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let user = try await account.createAnonymousSession() ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val user = account.createAnonymousSession() ``` ```graphql mutation { accountCreateAnonymousSession { _id userId provider expire } } ``` {% /multicode %} ### Attaching an account {% #attach-account %} Anonymous users cannot sign back in. If the session expires, they move to another computer, or they clear their browser data, they won't be able to log in again. Remember to prompt the user to create an account to not lose their data. Create an account with any of these methods to transition from an anonymous session to a user account session. {% arrow_link href="/docs/products/auth/email-password" %} Email and password {% /arrow_link %} {% arrow_link href="/docs/products/auth/phone-sms" %} Phone (SMS) {% /arrow_link %} {% arrow_link href="/docs/products/auth/magic-url" %} Magic URL {% /arrow_link %} {% arrow_link href="/docs/products/auth/oauth2" %} OAuth2 {% /arrow_link %} --- ## Checking auth status https://appwrite.io/docs/products/auth/checking-auth-status One of the first things your application needs to do when starting up is to check if the user is authenticated. This is an important step in creating a great user experience, as it determines whether to show login screens or protected content. ### Check auth with `account.get()` The recommended approach for checking authentication status is to use the `account.get()` method when your application starts: {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); // Check if user is logged in async function checkAuthStatus() { try { // If successful, user is authenticated const user = await account.get(); console.log("User is authenticated:", user); // Proceed with your authenticated app flow return user; } catch (error) { console.error("User is not authenticated:", error); // Redirect to login page or show login UI // window.location.href = '/login'; return null; } } // Call this function when your app initializes checkAuthStatus(); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void checkAuthStatus() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); try { // If successful, user is authenticated final user = await account.get(); print('User is authenticated: ${user.name}'); // Proceed with your authenticated app flow } catch (e) { print('User is not authenticated: $e'); // Redirect to login page or show login UI } } // Call this function when your app initializes ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.exceptions.AppwriteException class AuthManager { private val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") private val account = Account(client) suspend fun checkAuthStatus(): Boolean { return try { val user = account.get() Log.d("Auth", "User is authenticated: ${user.name}") // Proceed with your authenticated app flow true } catch (e: AppwriteException) { Log.e("Auth", "User is not authenticated: ${e.message}") // Redirect to login page or show login UI false } } } // Call this function when your app initializes ``` ```client-apple import Appwrite func checkAuthStatus() { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let account = Account(client) Task { do { // If successful, user is authenticated let user = try await account.get() print("User is authenticated: \(user.name)") // Proceed with your authenticated app flow } catch { print("User is not authenticated: \(error)") // Redirect to login page or show login UI } } } // Call this function when your app initializes ``` {% /multicode %} ### Missing scope error When a user is not authenticated and you call `account.get()`, you might see an error message like: ``` User (role: guests) missing scope (account) ``` This error is telling you that: 1. The current user has the role of "guest" (unauthenticated visitor) 2. This guest user does not have the required permission scope to access account information 3. This is the expected behavior when a user is not logged in {% info title="Authentication flow" %} In a typical application flow: 1. Call `account.get()` when your app starts 2. If successful → User is authenticated → Show the main app UI 3. If error → User is not authenticated → Redirect to login screen {% /info %} ### Best practices - Call `account.get()` early in your application lifecycle - Handle both authenticated and unauthenticated states gracefully - Show appropriate loading states while checking authentication - Implement proper error handling to avoid showing error messages to users --- ## Custom token login https://appwrite.io/docs/products/auth/custom-token Tokens are short-lived secrets created by an [Appwrite Server SDK](/docs/sdks#server) that can be exchanged for session by a [Client SDK](/docs/sdks#client) to log in users. You may already be familiar with tokens if you checked out [Magic URL login](/docs/products/auth/magic-url), [Email OTP login](/docs/products/auth/email-otp) or [Phone (SMS) login](/docs/products/auth/phone-sms). Custom token allows you to use [Server SDK](/docs/sdks#server) to generate tokens for your own implementations. This allows you to code your own authentication methods using Appwrite Functions or your own backend. You could implement username and password sign-in, captcha-protected authentication, phone call auth, and much more. Custom tokens also allow you to skip authentication which is useful when you integrate Appwrite with external authenticaion providers such as Auth0, TypingDNA, or any provider trusted by your users. ### Create custom token {% #create-custom-token %} Once you have your server endpoint prepared either in an Appwrite Function or a server integration, you can use the [Create token](/docs/references/cloud/server-nodejs/users#createToken) endpoint of the [Users API](/docs/products/auth/users) to generate a token. {% multicode %} ```server-nodejs import { Client, Users } from "node-appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID .setKey(''); // Your project API key const users = new Users(client); const token = await users.createToken({ userId: '' }); const secret = token.secret; ``` ```php use Appwrite\Client; use Appwrite\Users; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey(''); // Your project API key $users = new Users($client); $token = $users->createToken(''); $secret = $token['secret']; ``` ```python from appwrite.client import Client from appwrite.users import Users client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your project API key ) users = Users(client) token = users.create_token('') secret = token.secret ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your project API key users = Users.new(client) token = users.create_token('') secret = token['secret'] ``` ```deno import { Client, Users } from "npm:node-appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your project API key const users = new Users(client); const token = await users.createToken({ userId: '[USER_ID]' }); const secret = token.secret; ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your project API key final users = Users(client); final token = await users.createToken(''); final secret = token.secret; ``` ```kotlin import io.appwrite.Client import io.appwrite.Users val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your project API key val users = Users(client) val token = users.createToken("") val secret = token.secret ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your project API key let users = Users(client) let token = try await users.createToken("") let secret = token.secret ``` ```csharp using Appwrite; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey(""); // Your project API key var users = new Users(client); var token = await users.CreateToken(""); var secret = token.secret; ``` {% /multicode %} The newly created token includes a `secret` which is 6 character long hexadecimal string. You can configure length of the secret and expiry when creating a token. If you are integrating with external authentication providers or implementing your own authentication, make sure to validate user authenticated properly before generating a token for them. If you are implementing token-based authentication flow, share the token secret with user over any channel of your choice instead of directly giving it to him in the response. If the client doesn't know the user's ID during authentication, we recommend to directly return user ID to the client as part of this step. If necessary, you can check if the user with an user ID exists first, and create a new user if needed. ### Login {% #login %} Once the client receives a token secret, we can use it to authenticate the user in the application. Use the [Client SDK's](/docs/sdks#client) [Create session endpoint](/docs/references/cloud/server-nodejs/account#createSession) to exchange the token secret for a valid session, which logs the user. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const session = await account.createSession({ userId: '', secret: '' }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final session = await account.createSession( userId: '', secret: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); let account = Account(client); let session = try await account.createSession( userId: "", secret: "" ); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); val account = Account(client); val session = account.createSession( userId = "", secret = "" ); ``` ```graphql mutation { accountcreateSession(userId: "", secret: "") { _id userId provider expire } } ``` {% /multicode %} When the session is successfully created, the session is stored in a persistent manner and you can now do requests as authorized user from the application. --- ## Email OTP https://appwrite.io/docs/products/auth/email-otp Email OTP (one-time password) authentication lets users create accounts using their email address and log in using a 6 digit code delivered to their email inbox. This method is similar to [Magic URL login](/docs/products/auth/magic-url), but can provide better user experience in some scenarios. {% info title="Email OTP vs Magic URL" %} Email OTP sends an email with a 6 digit code that user needs to enter into the app, while Magic URL delivers a clickable button or a link to user's inbox. Both allow passwordless login flows with different advantages. | Benefits of Email OTP | Downsides of Email OTP | |--------------------------------------------------------------------|------------------------------------------| | Doesn't require user to be signed into email inbox on the device | Expires quicker | | Doesn't disturb application flow with a redirect | Requires more inputs from user | | Doesn't require deep linking on mobile apps | | {% /info %} ### Send email {% #send-email %} Email OTP authentication is done using a two-step authentication process. The authentication request is initiated from the client application and an email message is sent to the user's email inbox. The email will contain a 6-digit number the user can use to log in. Send an email to initiate the authentication process. If the email address has never been used, a **new account is created** using the provided `userId`. Otherwise, if the email address is already attached to an account, the **user ID is ignored**. Then, the user will receive an email with the one-time password. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const sessionToken = await account.createEmailToken({ userId: ID.unique(), email: 'email@example.com' }); const userId = sessionToken.userId; ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final sessionToken = await account.createEmailToken( userId: ID.unique(), email: 'email@example.com' ); final userId = sessionToken.userId; ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); let account = Account(client); let sessionToken = try await account.createEmailToken( userId: ID.unique(), email: "email@example.com" ); let userId = sessionToken.userId; ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); val account = Account(client); val sessionToken = account.createEmailToken( userId = ID.unique(), email = "email@example.com" ); val userId = sessionToken.userId; ``` ```graphql mutation { accountCreateEmailToken(userId: "unique()", email: "email@example.com") { _id userId secret expire } } ``` {% /multicode %} ### Login {% #login %} After initiating the email OTP authentication process, the returned user ID and secret are used to authenticate the user. The user will use their 6-digit one-time password to log in to your app. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const session = await account.createSession({ userId: userId, secret: '' }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final session = await account.createSession( userId: userId, secret: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); let account = Account(client); let session = try await account.createSession( userId: userId, secret: "" ); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); val account = Account(client); val session = account.createSession( userId = userId, secret = "" ); ``` ```graphql mutation { accountcreateSession(userId: "", secret: "") { _id userId provider expire } } ``` {% /multicode %} After the secret is verified, a session will be created. ### Security phrase {% #security-phrase %} A security phrase is a randomly generated phrase provided on the login page, as well as inside Email OTP login email. Users must match the phrase on the login page with the phrase provided inside the email. Security phrases offer protection for various types of phishing and man-in-the-middle attacks. By default, security phrases are disabled. To enable a security phrase in Email OTP, enable it in first step of the authentication flow. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const promise = account.createEmailToken({ userId: ID.unique(), email: 'email@example.com', phrase: true }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```graphql mutation { accountCreateEmailToken( userId: "ID.unique()", email: "email@example.com", phrase: true ) { _id _createdAt userId secret expire phrase } } ``` {% /multicode %} By enabling security phrase feature, you will recieve `phrase` in the response. You need to display this phrase to the user, and we recommend informing user what this phrase is and how to check it. When security phrase is enabled, email will also include a new section providing user with the security phrase. --- ## Email and password login https://appwrite.io/docs/products/auth/email-password Email and password login is the most commonly used authentication method. Appwrite Authentication promotes a safer internet by providing secure APIs and promoting better password choices to end users. Appwrite supports added security features like blocking personal info in passwords, password dictionary, and password history to help users choose good passwords. ### Signup {% #sign-up %} You can use the Appwrite Client SDKs to create an account using email and password. ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setProject(''); // Your project ID .setEndpoint('https://.cloud.appwrite.io/v1'); const account = new Account(client); try { const user = await account.create({ userId: '[USER_ID]', email: 'email@example.com', password: '' }); console.log(user) } catch (e){ console.error(e) } ``` Passwords are hashed with [Argon2](https://github.com/P-H-C/phc-winner-argon2), a resilient and secure password hashing algorithm. ### Login {% #login %} After an account is created, users can be logged in using the Create Email Session route. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const result = await account.createEmailPasswordSession({ email: 'email@example.com', password: 'password' }); console.log(result); ``` ### Verification {% #verification %} After logging in, the email can be verified through the account create verification route. The user doesn't need to be verified to log in, but you can restrict resource access to verified users only using permissions through the `user([USER_ID], "verified")` role. First, send a verification email. Specify a redirect URL which users will be redirected to. The verification secrets will be appended as query parameters to the redirect URL. In this example, the redirect URL is `https://example.com/verify`. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setProject('') // Your project ID const account = new Account(client); const promise = account.createVerification({ url: 'https://example.com/verify' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` Next, implement the verification page in your app. This page will parse the secrets passed in through the `userId` and `secret` query parameters. In this example, the code below will be found in the page served at `https://example.com/verify`. Since the secrets are passed in through url params, it will be easiest to perform this step in the browser. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setProject(''); // Your project ID const account = new Account(client); const urlParams = new URLSearchParams(window.location.search); const secret = urlParams.get('secret'); const userId = urlParams.get('userId'); const promise = account.updateVerification({ userId, secret }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ### Password Recovery {% #password-recovery %} If a user forgets their password, they can initiate a password recovery flow to recover their password. The Create Password Recovery endpoint sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link, they are redirected back to the password reset URL with the secret key and email address values attached to the URL as query strings. Only redirect URLs to domains added as a platform on your Appwrite Console will be accepted. URLs not added as a platform are rejected to protect against redirect attacks. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setProject(''); // Your project ID const promise = account.createRecovery({ email: 'email@example.com', url: 'https://example.com/recovery' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` After receiving an email with the secret attached to the redirect link, submit a request to the Create Password Recovery (confirmation) endpoint to complete the recovery flow. The verification link sent to the user's email address is valid for 1 hour. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setProject(''); // Your project ID const promise = account.updateRecovery({ userId: '', secret: '', password: 'password' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ### Security {% #security %} Appwrite's security first mindset goes beyond a securely implemented authentication API. You can enable features like password dictionary, password history, and disallow personal data in passwords to encourage users to pick better passwords. By enabling these features, you protect user data and teach better password choices, which helps make the internet a safer place. --- ## Identities https://appwrite.io/docs/products/auth/identities Identities enable linking multiple authentication methods to a single user account. This allows users to access a unified account through various OAuth2 providers. An identity is another way to refer to a user account. A single user can have multiple identities, each corresponding to different authentication methods. Currently, identities are primarily used with OAuth2 providers. When a user logs in via an OAuth2 provider, an identity is created and linked to their Appwrite account. This system enables: - Connecting multiple OAuth2 accounts to a single Appwrite account - Maintaining consistent access regardless of login method - Tracking which external providers are linked to an account ### Use cases {% #use-cases %} Identities are primarily used in the following scenarios: 1. **OAuth2 authentication**: When users authenticate through any OAuth2 provider 2. **Account management**: When users want to link or unlink external provider accounts 3. **User profile consolidation**: When maintaining a single user profile across multiple authentication methods ### Create new identities {% #create-new-identities %} To create a new identity: 1. The user must be logged into their Appwrite account 2. Initiate the OAuth2 authentication flow for the desired provider 3. The new identity will be automatically created and linked to the current account For implementation details and code examples, refer to the [OAuth2 documentation](/docs/products/auth/oauth2). ### Manage email addresses {% #manage-email-addresses %} Each email address must be unique across all users and identities. For example, if a user with email `joe@example.com` creates an identity using `other@company.com`, that second email becomes reserved. This means no other user can create either a new account or a new identity using `other@company.com`. This restriction helps maintain consistent user identity across your application. ### List and delete identities {% #list-and-delete-identities %} Users and administrators can manage identities through various operations available in the Account API: - [List identities](/docs/references/cloud/client-web/account#listIdentities) - [Delete an identity](/docs/references/cloud/client-web/account#deleteIdentity) For detailed API specifications and code examples, refer to the [Account API Reference](/docs/references/cloud/client-web/account). ### Clean up identities {% #clean-up-identities %} When a user account is deleted: - Associated identities (and related targets) are removed via a background job - This deletion is asynchronous and may not be immediate due to queue processing times - In testing scenarios where instant deletion is required, manually remove identities (and targets) before deleting the user account ### Best practices {% #best-practices %} A good user experience typically includes clear visibility of connected providers and straightforward identity management. Verify email addresses where possible and implement proper session management. Secure identity deletion can help prevent unauthorized access. Testing should ideally cover the cleanup of test identities and email conflict scenarios. --- ## JWT login https://appwrite.io/docs/products/auth/jwt You can extend Appwrite's APIs by building backend apps using [Server SDKs](/docs/sdks#server). To secure your backend app's APIs, client apps must prove their identity against your backend app before accessing sensitive information. You can secure these APIs and enforce access permissions in your backend app by using JWT authentication. If you are already authenticated on your client-side app and need your backend app to **act on behalf of the user**, this guide will walk you through the process. ### Proof of Identity {% #proof-of-identity %} Before making requests to your backend APIs, your client application needs to first create a session **directly with Appwrite** using the account service. This session will act like an ID card for the user and can be used to access resources in Appwrite. The client will **only receive information accessible to the user** based on the resources' [permissions](/docs/advanced/platform/permissions). When you build backend APIs to extend Appwrite's functionality, these APIs should still **respect access permissions** to keep user data secure. Appwrite's backend SDKs allow you to securely act on behalf of a user with the same permissions by using JWT authentication. ### JWT Authentication {% #jwt %} [JSON Web Tokens](https://jwt.io/introduction) (JWTs) are a secure means to transfer information or claims between two parties. JWTs act like temporary copies of the user's ID card that allow Appwrite's Server SDKs to access information on behalf of a user. You need to create a session using the Client SDKs **before** generating a JWT. The JWT will be a stateless proof of claim for the identity of the authenticated user and expire after 15 minutes or when the session is deleted. You can generate a JWT like this on a [Client SDK](/docs/sdks#client). {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const user = await account.createJWT(); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final jwt = await account.createJWT(); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let jwt = try await account.createJWT() ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val jwt = account.createJWT() ``` ```graphql mutation { accountCreateJWT { jwt } } ``` {% /multicode %} Your server application can use the JWT to act on behalf of the user by creating a `Client` instance with the JWT for **each request it receives**. To keep your API secure, **discard the client object** after each request. Use JWTs tokens like this in a [Server SDK](/docs/sdks#server). {% multicode %} ```js const { Client } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token ``` ```php use Appwrite\Client; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token ``` ```deno import { Client } from "npm:node-appwrite"; let client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token ``` ```kotlin import io.appwrite.Client val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT("eyJJ9.eyJ...886ca") // Your secret JSON Web Token ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT("eyJJ9.eyJ...886ca") // Your secret JSON Web Token ``` ```csharp using Appwrite; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetJWT("eyJJ9.eyJ...886ca"); // Your secret JSON Web Token ``` {% /multicode %} ### When should I use JWTs? {% #when-to-use %} JWT auth is useful when you need your backend app's Server SDK to be restricted by the same set of permissions. If your backend app's Server SDK is using an [API key](/docs/advanced/platform/api-keys), it will fetch **all resources** regardless of permissions. This means the Server SDK might fetch files and rows your user should not be able to see, which is not helpful when you need to act on behalf of a user. If your backend app's Server SDK is using a **JWT**, it will only fetch resources your user has permissions to access. ### Example {% #when-to-use-example %} Here's an example table of birthdays with the following rows. Notice how they all have **different permissions**. | $id | name | birthday | $permissions | |-------------|-------|-----------|------------------------| | ac5fc866ad1e| Kevin | 2012-02-03| "read(\"user:user-a\")"| | bc7fc866ad1e| Laura | 1999-09-22| "read(\"user:user-b\")"| | cc2fc886ad1e| Bob | 1982-05-11| "read(\"user:user-c\")"| If you're authenticated on the client-side as `user-a` and created a JWT `'eyJJ9.eyJ...886ca'`, you can pass this JWT to a Server SDK on the backend server to fetch only the birthdays `user-a` can read. {% multicode %} ```js const { Client } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token const tablesDB = new sdk.TablesDB(client); const rows = await tablesDB.listRows({ databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' }); // ... More code to manipulate the results ``` ```php use Appwrite\Client; $client = (new Client()) .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Tokens $tablesDB = new TablesDB($client); $rows = $tablesDB->listRows( databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' ); // ... More code to manipulate the results ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token ) tablesDB = TablesDB(client) rows = tablesDB.list_rows( database_id='642f358bf4084c662590', table_id='642f3592aa5fc856ad1e' ) ### ... More code to manipulate the results ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token tablesDB = TablesDB.new(client) rows = tablesDB.list_rows( database_id: '642f358bf4084c662590', table_id: '642f3592aa5fc856ad1e' ) ### ... More code to manipulate the results ``` ```deno import { Client } from "npm:node-appwrite"; let client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token let tablesDB = new sdk.TablesDB(client); let rows = await tablesDB.listRows({ databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' }); // ... More code to manipulate the results ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token final tablesDB = TablesDB(client); final rows = await tablesDB.listRows( databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e', ); // ... More code to manipulate the results ``` ```kotlin import io.appwrite.Client val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT("eyJJ9.eyJ...886ca") // Your secret JSON Web Token val tablesDB = TablesDB(client) val rows = tablesDB.listRows( databaseId = "642f358bf4084c662590", tableId = "642f3592aa5fc856ad1e", ) // ... More code to manipulate the results ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT("eyJJ9.eyJ...886ca") // Your secret JSON Web Token let tablesDB = TablesDB(client) let rows = try await tablesDB.listRows( databaseId: "642f358bf4084c662590", tableId: "642f3592aa5fc856ad1e" ) // ... More code to manipulate the results ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetJWT("eyJJ9.eyJ...886ca"); // Your secret JSON Web Token var tablesDB = new TablesDB(client); var rows = await databases.listRows( databaseId: "642f358bf4084c662590", tableId: "642f3592aa5fc856ad1e"); // ... More code to manipulate the results ``` {% /multicode %} Only Kevin's birthday is returned and rows where `user-A` has no permissions to access are not returned. ```js { "total": 1, "rows": [ { "name": "Kevin", "birthday": "2012-02-03T00:00:00.000+00:00", "$id": "ac5fc866ad1e", "$permissions": [ "read(\"user:user-a\")" ], "$tableId": "642f3592aa5fc856ad1e", "$databaseId": "642f358bf4084c662590", ... } ] } ``` If the same request is made where the [Server SDK](/docs/sdks#server)'s `client` is authenticated with an API key instead of a JWT, the results returned will be different. {% multicode %} ```js const { Client } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key const tablesDB = new sdk.TablesDB(client); const rows = await tablesDB.listRows({ databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' }); // ... More code to manipulate the results ``` ```php use Appwrite\Client; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('98fd4...a2ad2'); // Your secret API key $tablesDB = new TablesDB($client); $rows = $tablesDB->listRows( databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' ); // ... More code to manipulate the results ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .set_project('') // Your project ID .set_key('98fd4...a2ad2') // Your secret API key ) tablesDB = TablesDB(client) rows = tablesDB.list_rows( database_id='642f358bf4084c662590', table_id='642f3592aa5fc856ad1e' ) // ... More code to manipulate the results ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('98fd4...a2ad2') # Your secret API key tablesDB = TablesDB.new(client) rows = tablesDB.list_rows( database_id: '642f358bf4084c662590', table_id: '642f3592aa5fc856ad1e' ) ### ... More code to manipulate the results ``` ```deno import { Client } from "npm:node-appwrite"; let client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key let tablesDB = new sdk.TablesDB(client); let rows = await tablesDB.listRows({ databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e' }); // ... More code to manipulate the results ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key final tablesDB = TablesDB(client); final rows = await tablesDB.listRows( databaseId: '642f358bf4084c662590', tableId: '642f3592aa5fc856ad1e', ); // ... More code to manipulate the results ``` ```kotlin import io.appwrite.Client val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key val tablesDB = TablesDB(client) val rows = tablesDB.listRows( databaseId = "642f358bf4084c662590", tableId = "642f3592aa5fc856ad1e", ) // ... More code to manipulate the results ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key let tablesDB = TablesDB(client) let rows = try await tablesDB.listRows( databaseId: "642f358bf4084c662590", tableId: "642f3592aa5fc856ad1e" ) // ... More code to manipulate the results ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey('98fd4...a2ad2'); // Your secret API key var tablesDB = new TablesDB(client); var rows = await databases.listRows( databaseId: "642f358bf4084c662590", tableId: "642f3592aa5fc856ad1e"); // ... More code to manipulate the results ``` {% /multicode %} This will return every row regardless of permissions, which could lead to privacy and security problems. ```json { "total": 3, "rows": [ { "name": "Kevin", "birthday": "2012-02-03T00:00:00.000+00:00", "$id": "ac5fc866ad1e", "$permissions": [ "read(\"user:user-a\")" ], "$tableId": "642f3592aa5fc856ad1e", "$databaseId": "642f358bf4084c662590", ... }, { "name": "Laura", "birthday": "1999-09-22T11:21:23.334+00:00", "$id": "bc7fc866ad1e", "$permissions": [ "read(\"user:user-b\")" ], "$tableId": "642f3592aa5fc856ad1e", "$databaseId": "642f358bf4084c662590", ... }, { "name": "Bob", "birthday": "1982-05-11T12:31:39.381+00:00", "$id": "cc2fc886ad1e", "$permissions": [ "read(\"user:user-c\")" ], "$tableId": "642f3592aa5fc856ad1e", "$databaseId": "642f358bf4084c662590", ... } ] } ``` If you're integrating existing backend services with Appwrite or adding backend endpoints to perform more complex logic, JWT authentication helps them behave similarly to actual Appwrite endpoints. --- ## Labels https://appwrite.io/docs/products/auth/labels Labels are a good way to categorize a user to grant them access to resources. For example, a `subscriber` label can be added to a user once they've purchased a subscription. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key const users = new sdk.Users(client); const promise = users.updateLabels( '', [ 'subscriber' ] ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```php use Appwrite\Client; use Appwrite\Services\Users; use Appwrite\Role; $client = new Client(); $client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key $users = new Users($client); $result = $users->updateLabels( '', [ 'subscriber' ] ); ``` ```python from appwrite.client import Client from appwrite.services.users import Users from appwrite.role import Role client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('98fd4...a2ad2') # Your secret API key ) users = Users(client) result = users.update_labels( '', [ 'subscriber' ] ); ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('98fd4...a2ad2') # Your secret API key users = Users.new(client) response = users.update_labels( user_id: '', labels: [ 'subscriber' ] ); ``` ```deno import * as sdk from "npm:node-appwrite"; let client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key let users = new sdk.Users(client); const promise = users.updateLabels( '', [ 'subscriber' ] ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('98fd4...a2ad2'); // Your secret API key final users = Users(client); final result = await users.updateLabels( userId: '', labels: [ 'subscriber' ], ); ``` ```kotlin import io.appwrite.Client import io.appwrite.Role import io.appwrite.services.Users val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("98fd4...a2ad2") // Your secret API key val users = Users(client) val response = users.updateLabels( userId = "", labels = [ 'subscriber' ] ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("98fd4...a2ad2") // Your secret API key let users = Users(client) let response = try await users.updateLabels( userId: "", labels: [ 'subscriber' ] ); ``` ```csharp using Appwrite; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("98fd4...a2ad2"); // Your secret API key var users = new Users(client); var response = await users.UpdateLabels( userId: "", labels: [ 'subscriber' ] ); ``` {% /multicode %} This would correspond with the permissions below. | Description | Code Snippet | | ------------------------------------------- | ------------------------------------------- | | Read | `Permissions.read(Role.label('subscriber'))`| | Update | `Permissions.update(Role.label('subscriber'))` | | Delete | `Permissions.delete(Role.label('subscriber'))` | | Create | `Permissions.create(Role.label('subscriber'))` | {% arrow_link href="/docs/advanced/platform/permissions" %} Learn more about permissions {% /arrow_link %} --- ## Magic URL login https://appwrite.io/docs/products/auth/magic-url Magic URL is a password-less way to authenticate users. When a user logs in by providing their email, they will receive an email with a "magic" link that contains a secret used to log in the user. The user can simply click the link to be logged in. ### Send email {% #init %} Initialize the log in process with the [Create Magic URL Token](/docs/references/cloud/client-web/account#createMagicURLToken) route. If the email has never been used, a **new account is created** using the provided `userId`, then the user will receive an email. If the email is already attached to an account, the **user ID is ignored** and the user will receive a magic link in their email. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const token = await account.createMagicURLToken( ID.unique(), 'email@example.com', 'https://example.com/verify' ); ``` ```graphql mutation { accountCreateMagicURLToken( userId: "ID.unique()", email: "email@example.com", url: "https://example.com/verify" ) { _id _createdAt userId secret expire } } ``` {% /multicode %} The `url` parameter specifies where users will be redirected after clicking the magic link. The secret and userId will be automatically appended as query parameters to this URL. ### Login {% #login %} After the user clicks the magic link in their email, they will be redirected to your specified URL with the secret and userId as query parameters. Use these parameters to create a session. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const urlParams = new URLSearchParams(window.location.search); const secret = urlParams.get('secret'); const userId = urlParams.get('userId'); const user = await account.createSession({ userId, secret }); ``` ```graphql mutation { accountCreateSession( userId: "unique()", secret: "" ) { _id _createdAt userId expire provider } } ``` {% /multicode %} --- ## Multi-factor authentication https://appwrite.io/docs/products/auth/mfa Multi-factor authentication (MFA) greatly increases the security of your apps by adding additional layers of protection. When MFA is enabled, a malicious actor needs to compromise multiple authentication factors to gain unauthorized access. Appwrite Authentication lets you easily implement MFA in your apps, letting you build more securely and quickly. {% info title="Looking for MFA on your Console account?" %} This page covers MFA for your app's end-users. If you are looking for MFA on your Appwrite Console account, please refer to the [Console MFA page](/docs/advanced/security/mfa). {% /info %} Appwrite currently allows two factors of authentication. More factors of authentication will be available soon. Here are the steps to implement MFA in your application. {% section #display-recover-code step=1 title="Display recovery codes" %} Initialize your Appwrite SDK's `Client`, `Account`, and `Avatars`. You'll use Avatars API to generate a QR code for the TOTP authenticator app, you can skip this import if you're not using TOTP. {% multicode %} ```client-web import { Client, Account, Avatars } from "appwrite"; const client = new Client(); const account = new Account(client); const avatars = new Avatars(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Account account = Account(client); Avatars avatars = Avatars(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let avatars = Avatars(client) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val avatars = Avatars(client) ``` {% /multicode %} Before enabling MFA, you should display recovery codes to the user. The codes are single use passwords the user can use to access their account if they lose access to their MFA email, phone, or authenticator app. These codes can **only be generated once**, warn the users to save them. The code will look like this, display them to the user and remind them to save the codes in a secure place. ```json { "recoveryCodes": [ "b654562828", "a97c13d8c0", "311580b5f3", "c4262b3f88", "7f6761afb4", "55a09989be", ] } ``` These codes can be used to complete the [Complete challenge](#complete-challenge) step if the user loses access to their MFA factors. Generate the recovery codes by calling `account.createMfaRecoveryCodes()`. {% multicode %} ```client-web const response = await account.createMfaRecoveryCodes(); console.log(response.recoveryCodes); ``` ```client-flutter Future result = account.createMfaRecoveryCodes(); result.then((response) { print(response.recoveryCodes); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaRecoveryCodes() print(response.recoveryCodes) ``` ```client-android-kotlin val response = account.createMfaRecoveryCodes() println(response.recoveryCodes) ``` ```client-android-java account.createMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.recoveryCodes.toString()); })); ``` {% /multicode %} {% /section %} {% section #verify-factors step=2 title="Verify MFA factors" %} Any verified email, phone number, or TOTP authenticator app can be used as a factor for MFA. Before they can be used as a factor, they need to be verified. {% tabs %} {% tabsitem #email title="Email" %} First, set your user's email if they haven't already. {% multicode %} ```client-web const response = await account.updateEmail({ email: 'email@example.com', password: 'password' }); ``` ```client-flutter Future result = account.updateEmail( email: 'email@example.com', password: 'password', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updateEmail( email: "email@example.com", password: "password" ) ``` ```client-android-kotlin val response = account.updateEmail( email = "email@example.com", password = "password" ) ``` ```client-android-java account.updateEmail( "email@example.com", // email "password", // password new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} Then, initiate verification for the email by calling `account.createEmailVerification()`. Calling `createEmailVerification` will send a verification email to the user's email address with a link with the query parameter `userId` and `secret`. {% multicode %} ```client-web const res = await account.createVerification({ url: 'https://example.com/verify-email' }); ``` ```client-flutter Future result = account.createVerification( url: 'https://example.com/verify-email', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createVerification( url: "https://example.com/verify-email" ) ``` ```client-android-kotlin val response = account.createVerification( url = "https://example.com/verify-email" ) ``` ```client-android-java account.createVerification( "https://example.com/verify-email", // url new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} After the user clicks the link in the email, they will be redirected to your site with the query parameters `userId` and `secret`. If you're on a mobile platform, you will need to create the appropriate deep link to handle the verification. Finally, verify the email by calling `account.updateVerification()` with `userId` and `secret`. {% multicode %} ```client-web const response = await account.updateVerification({ userId: '', secret: '' }); ``` ```client-flutter Future result = account.updateVerification( userId: '', secret: '', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updateVerification( userId: "", secret: "" ) ``` ```client-android-kotlin val response = account.updateVerification( userId = "", secret = "" ) ``` ```client-android-java account.updateVerification( "", // userId "", // secret new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} {% /tabsitem %} {% tabsitem #phone title="Phone" %} First, set your user's phone number if they haven't already. {% multicode %} ```client-web const response = await account.updatePhone({ phone: '+12065550100', password: 'password' }); ``` ```client-flutter Future result = account.updatePhone( phone: '+12065550100', password: 'password', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updatePhone( phone: "+12065550100", password: "password" ) ``` ```client-android-kotlin val response = account.updatePhone( phone = "+12065550100", password = "password" ) ``` ```client-android-java account.updatePhone( "+12065550100", // phone "password", // password new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} Then, initiate verification for the phone number by calling `account.createPhoneVerification()`. {% multicode %} ```client-web const response = await account.createPhoneVerification(); ``` ```client-flutter Future result = account.createPhoneVerification(); result .then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createPhoneVerification() ``` ```client-android-kotlin val response = account.createPhoneVerification() ``` ```client-android-java account.createPhoneVerification(new CoroutineCallback<>((result, error) -> { if (error != null) error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); })); ``` {% /multicode %} After the user receives the verification code, they can verify their phone number by calling `account.updatePhoneVerification()`. {% multicode %} ```client-web const response = await account.updatePhoneVerification({ userId: '', secret: '' }); ``` ```client-flutter Future result = account.updatePhoneVerification( userId: '', secret: '', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updatePhoneVerification( userId: "", secret: "" ) ``` ```client-android-kotlin val response = account.updatePhoneVerification( userId = "", secret = "" ) ``` ```client-android-java account.updatePhoneVerification( "", // userId "", // secret new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} {% /tabsitem %} {% tabsitem #authenticator title="Authenticator" %} First, add a TOTP authenticator to the user's account by calling `account.addAuthenticator()`. {% multicode %} ```client-web const { secret, uri } = await account.createMfaAuthenticator({ type: 'totp' }); ``` ```client-flutter Future result = account.createMfaAuthenticator( type: 'totp', ); result.then((response) { print(response.secret); print(response.uri); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaAuthenticator( type: "totp" ) print(response.secret) print(response.uri) ``` ```client-android-kotlin val response = account.createMfaAuthenticator( type = "totp" ) println(response.secret) println(response.uri) ``` ```client-android-java account.createMfaAuthenticator( "totp", // type new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} This will create a secret and a URI. The URI is a URL that can be used to generate a QR code for the user to scan with their TOTP authenticator app. You can generate a QR code for the user to scan by calling `avatars.getQR()`. {% multicode %} ```client-web const result = await avatars.getQR({ text: uri, size: 800, // optional margin: 0, // optional download: false // optional }); console.log(result); // Resource URL ``` ```client-flutter // download QR code image Future result = avatars.getQR( text: authenticatorUrl, size: 800, // optional margin: 0, // optional download: false, // optional ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes); }).catchError((error) { print(error.response); }); // display QR code image FutureBuilder( future: avatars.getQR( text: authenticatorUrl, size: 800, // optional margin: 0, // optional download: false, // optional ), // works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple let byteBuffer = try await avatars.getQR( text: authenticatorUrl, size: 800, // optional margin: 0, // optional download: xfalse // optional ) ``` ```client-android-kotlin val result = avatars.getQR( text = authenticatorUrl, size = 800, // optional margin = 0, // optional download = false // optional ) ``` ```client-android-java avatars.getQR( authenticatorUrl, // text 800, // size (optional) 0, // margin (optional) false, // download (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} If the user is unable to scan QR codes, you can display the `secret` to the user. Finally prompt the user to enter a TOTP from their authenticator app, then verify the authenticator by calling `account.verifyMfaAuthenticator()`. {% multicode %} ```client-web const promise = account.updateMfaAuthenticator({ type: 'totp', otp: '' }); ``` ```client-flutter Future result = account.updateMfaAuthenticator( type: 'totp', otp: '', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updateMfaAuthenticator( type: "totp", otp: "" ) ``` ```client-android-kotlin val response = account.updateMfaAuthenticator( type = "totp", otp = "" ) ``` ```client-android-java account.updateMfaAuthenticator( "totp", // type "", // otp new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} {% section #enable-mfa step=3 title="Enable MFA on an account" %} You can enable MFA on your account by calling `account.updateMFA()`. You will need to have added more than 1 factors of authentication to an account before the MFA is enforced. {% multicode %} ```client-web const result = await account.updateMFA({ enabled: true }); ``` ```client-flutter Future result = account.updateMFA( mfa: true, ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updateMFA( mfa: xtrue ) ``` ```client-android-kotlin val response = account.updateMFA( mfa = true ) ``` {% /multicode %} {% /section %} {% section #init-login step=4 title="Initialize login" %} Begin your login flow with the default authentication method used by your app, for example, email password. {% multicode %} ```client-web const session = await account.createEmailPasswordSession({ email: 'email@example.com', password: 'password' }); ``` ```client-flutter Future session = account.createEmailPasswordSession( email: 'email@example.com', password: 'password', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createEmailPasswordSession( email: "email@example.com", password: "password" ) ``` ```client-android-kotlin val session = account.createEmailPasswordSession( email = "email@example.com", password = "password" ) ``` {% /multicode %} {% /section %} {% section #check-for-mfa step=5 title="Check for multi-factor" %} Upon successful login in the first authentication step, check the status of the login by calling `account.get()`. If more than one factors are required, you will receive the error `user_more_factors_required`. Redirect the user in your app to perform the MFA challenge. {% multicode %} ```client-web try { const response = await account.get(); console.log(response); } catch (error) { console.log(error); if (error.type === `user_more_factors_required`){ // redirect to perform MFA } else { // handle other errors } } ``` ```client-flutter const promise = account.get(); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure if (error.type === "user_more_factors_required"){ // redirect to perform MFA } else { // handle other errors } }); ``` ```client-flutter Future result = account.get(); result.then((response) { print(response); }).catchError((error) { print(error.response); if (error.type == 'user_more_factors_required') { // redirect to perform MFA } else { // handle other errors } }); ``` ```client-apple do { let response = try await account.get() } catch let error as AppwriteException { print(error.message) if error.type == "user_more_factors_required" { // redirect to perform MFA } else { // handle other errors } } ``` ```client-android-kotlin try { val response = account.get() println(response) } catch (error: AppwriteException) { println(error.message) if (error.type == "user_more_factors_required") { // redirect to perform MFA } else { // handle other errors } } ``` {% /multicode %} {% /section %} {% section #list-provider step=6 title="List factors" %} You can check which factors are enabled for an account using `account.listMfaFactors()`. The returned object will be formatted like this. ```client-web { totp: true, // time-based one-time password email: false, // email phone: true // phone } ``` {% multicode %} ```client-web const factors = await account.listMfaFactors(); // redirect based on factors returned. ``` ```client-flutter Future result = account.listMfaFactors(); result.then((response) { print(response); // redirect based on factors returned. }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.listMfaFactors() // redirect based on factors returned. ``` ```client-android-kotlin val response = account.listMfaFactors() // redirect based on factors returned. ``` {% /multicode %} {% /section %} {% section #create-challenge step=7 title="Create challenge" %} Based on the factors available, initialize an additional auth step. Calling these methods will send a challenge to the user. You will need to save the challenge ID to complete the challenge in a later step. {% tabs %} {% tabsitem #email title="Email" %} Appwrite will use a verified email on the user's account to send the challenge code via email. Note that this is only valid as a second factor if the user did not initialize their login with email OTP. {% multicode %} ```client-web const challenge = await account.createMfaChallenge({ factor: 'email' }); // Save the challenge ID to complete the challenge later const challengeId = challenge.$id; ``` ```client-flutter Future result = account.createMfaChallenge( factor: 'email', ); result.then((response) { print(response); // Save the challenge ID to complete the challenge later var challengeId = response.$id; }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaChallenge( factor: "email" ) // Save the challenge ID to complete the challenge later let challengeId = response.id ``` ```client-android-kotlin val response = account.createMfaChallenge( factor = "email" ) // Save the challenge ID to complete the challenge later val challengeId = response.id ``` {% /multicode %} {% /tabsitem %} {% tabsitem #phone title="Phone" %} Appwrite will use a verified phone number on the user's account to send the challenge code via SMS. You cannot use this method if the user initialized their login with phone OTP. {% multicode %} ```client-web const challenge = await account.createMfaChallenge({ factor: 'phone' }); // Save the challenge ID to complete the challenge later const challengeId = challenge.$id; ``` ```client-flutter Future result = account.createMfaChallenge( factor: 'phone', ); result.then((response) { print(response); // Save the challenge ID to complete the challenge later var challengeId = response.$id; }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaChallenge( factor: "phone" ) // Save the challenge ID to complete the challenge later let challengeId = response.id ``` ```client-android-kotlin val response = account.createMfaChallenge( factor = "phone" ) // Save the challenge ID to complete the challenge later val challengeId = response.id ``` {% /multicode %} {% /tabsitem %} {% tabsitem #totp title="TOTP" %} Initiate a challenge for users to complete using an authenticator app. {% multicode %} ```client-web const challenge = await account.createMfaChallenge({ factor: AuthenticationFactor.Totp }); // Save the challenge ID to complete the challenge later const challengeId = challenge.$id; ``` ```client-flutter Future result = account.createMfaChallenge( factor: 'totp', ); result.then((response) { print(response); // Save the challenge ID to complete the challenge later var challengeId = response.$id; }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaChallenge( factor: "totp" ) // Save the challenge ID to complete the challenge later let challengeId = response.id ``` ```client-android-kotlin val response = account.createMfaChallenge( factor = "totp" ) // Save the challenge ID to complete the challenge later val challengeId = response.id ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} {% section #complete-challenge step=8 title="Complete challenge" %} Once the user receives the challenge code, you can pass the code back to Appwrite to complete the challenge. {% multicode %} ```client-web const response = await account.updateMfaChallenge({ challengeId: '', otp: '' }); ``` ```client-flutter Future result = account.updateMfaChallenge( challengeId: '', otp: '', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple val response = account.updateMfaChallenge( challengeId = "", otp = "" ) ``` ```client-android-kotlin let result = try await account.updateMfaChallenge( challengeId: "", otp: "" ) ``` {% /multicode %} After completing the challenge, the user is now authenticated and all requests will be authorized. You can confirm this by running `account.get()` {% /section %} {% section #recovery step=9 title="Recovery" %} In case your user needs to recover their account, they can use the recovery codes generated in the first step with the recovery code factor. Initialize the challenge by calling `account.createMfaChallenge()` with the factor `recoverycode`. {% multicode %} ```client-web const challenge = await account.createMfaChallenge({ factor: AuthenticationFactor.Recoverycode }); // Save the challenge ID to complete the challenge later const challengeId = challenge.$id; ``` ```client-flutter Future result = account.createMfaChallenge( factor: 'recoverycode', ); result.then((response) { print(response); // Save the challenge ID to complete the challenge later var challengeId = response.$id; }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createMfaChallenge( factor: "recoverycode" ) // Save the challenge ID to complete the challenge later let challengeId = response.id ``` ```client-android-kotlin val response = account.createMfaChallenge( factor = "recoverycode" ) // Save the challenge ID to complete the challenge later val challengeId = response.id ``` {% /multicode %} Then complete the challenge by calling `account.updateMfaChallenge()` with the challenge ID and the recovery code. {% multicode %} ```client-web const response = await account.updateMfaChallenge({ challengeId: '', otp: '' }); ``` ```client-flutter Future result = account.updateMfaChallenge( challengeId: '', otp: '', ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple val response = account.updateMfaChallenge( challengeId = "", otp = "" ) ``` ```client-android-kotlin let result = try await account.updateMfaChallenge( challengeId: "", otp: "" ) ``` {% /multicode %} {% /section %} --- ## Multi-tenancy with Teams https://appwrite.io/docs/products/auth/multi-tenancy Appwrite Teams provides an effective way to implement multi-tenancy in your applications. Create a team for each tenant to handle multi-tenant apps with built-in data isolation. {% arrow_link href="/docs/products/auth/teams" %} Learn more about Teams {% /arrow_link %} ### What is multi-tenancy? Multi-tenancy is a design pattern where a single instance of software serves multiple user groups (tenants). With Appwrite Teams, you can: - Create a team for each tenant in your application - Control access to resources using team-based permissions - Define different roles within each tenant - Scale to unlimited tenants without code changes ### Common use cases - **SaaS applications**: Organizations that need isolated data and users - **Collaborative tools**: Projects with different access levels - **Educational platforms**: Schools with teachers and students - **Business software**: Companies with department-based access control ### Create teams for tenants When a new tenant signs up, create a dedicated team that serves as their isolated environment. {% multicode %} ```client-web import { Client, Teams, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); // Create team for a new tenant const tenantTeam = await teams.create( 'example_corp', // Team ID for tenant 'Example Corp', // Tenant name ['owner', 'admin', 'member'] // Tenant roles ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); // Create team for a new tenant final tenantTeam = await teams.create( teamId: 'example_corp', // Team ID for tenant name: 'Example Corp', // Tenant name roles: ['owner', 'admin', 'member'] // Tenant roles ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Create team for a new tenant let tenantTeam = try await teams.create( teamId: "example_corp", // Team ID for tenant name: "Example Corp", // Tenant name roles: ["owner", "admin", "member"] // Tenant roles ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.ID import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Create team for a new tenant val tenantTeam = teams.create( teamId = "example_corp", // Team ID for tenant name = "Example Corp", // Tenant name roles = listOf("owner", "admin", "member") // Tenant roles ) ``` {% /multicode %} ### Add members to tenants Invite users to join a tenant using team memberships. Each member can be assigned different roles for access control. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); // Invite a member to the tenant const membership = await teams.createMembership( 'example_corp', // Team/tenant ID ['admin'], // Member's role in the tenant 'user@example.com', // User's email undefined, // userId (optional) undefined, // phone (optional) 'https://example.com/accept-invite' // Redirect URL after accepting ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); // Invite a member to the tenant final membership = await teams.createMembership( teamId: 'example_corp', // Team/tenant ID roles: ['admin'], // Member's role in the tenant email: 'user@example.com', // User's email url: 'https://example.com/accept-invite' // Redirect URL after accepting ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Invite a member to the tenant let membership = try await teams.createMembership( teamId: "example_corp", // Team/tenant ID roles: ["admin"], // Member's role in the tenant email: "user@example.com", // User's email url: "https://example.com/accept-invite" // Redirect URL after accepting ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Invite a member to the tenant val membership = teams.createMembership( teamId = "example_corp", // Team/tenant ID roles = listOf("admin"), // Member's role in the tenant email = "user@example.com", // User's email url = "https://example.com/accept-invite" // Redirect URL after accepting ) ``` {% /multicode %} ### Secure resources with team permissions Control access to rows and resources using team-based permissions. This ensures data isolation between tenants. {% multicode %} ```client-web import { Client, TablesDB, ID, Permission, Role } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Create a row that only members of "Example Corp" tenant can access const row = await tablesDB.createRow( 'invoices_db', 'invoices', ID.unique(), { title: 'Q2 Invoice', amount: 2500.00, customer: 'Example Customer', status: 'pending', tenant_id: 'example_corp' }, [ // All Example Corp team members can read Permission.read(Role.team('example_corp')), // Only admins can update Permission.write(Role.team('example_corp', ['admin'])) ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); // Create a row that only members of "Example Corp" tenant can access final row = await tablesDB.createRow( databaseId: 'invoices_db', tableId: 'invoices', rowId: ID.unique(), data: { 'title': 'Q2 Invoice', 'amount': 2500.00, 'customer': 'Example Customer', 'status': 'pending', 'tenant_id': 'example_corp' }, permissions: [ // All Example Corp team members can read Permission.read(Role.team('example_corp')), // Only admins can update Permission.write(Role.team('example_corp', ['admin'])) ] ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) // Create a row that only members of "Example Corp" tenant can access let row = try await tablesDB.createRow( databaseId: "invoices_db", tableId: "invoices", rowId: ID.unique(), data: [ "title": "Q2 Invoice", "amount": 2500.00, "customer": "Example Customer", "status": "pending", "tenant_id": "example_corp" ], permissions: [ // All Example Corp team members can read Permission.read(Role.team("example_corp")), // Only admins can update Permission.write(Role.team("example_corp", ["admin"])) ] ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.ID import io.appwrite.services.TablesDB import io.appwrite.models.Permission import io.appwrite.models.Role val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) // Create a row that only members of "Example Corp" tenant can access val row = tablesDB.createRow( databaseId = "invoices_db", tableId = "invoices", rowId = ID.unique(), data = mapOf( "title" to "Q2 Invoice", "amount" to 2500.00, "customer" to "Example Customer", "status" to "pending", "tenant_id" to "example_corp" ), permissions = listOf( // All Example Corp team members can read Permission.read(Role.team("example_corp")), // Only admins can update Permission.write(Role.team("example_corp", listOf("admin"))) ) ) ``` {% /multicode %} ### Query tenant data When querying data, users will automatically only see rows they have permission to access based on their team memberships. {% multicode %} ```client-web import { Client, TablesDB, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Current user will only see invoices they have access to const rows = await tablesDB.listRows( 'invoices_db', 'invoices' ); // For specific tenant data, you can add a query filter const tenantDocuments = await tablesDB.listRows( 'invoices_db', 'invoices', [ Query.equal('tenant_id', 'example_corp') ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); // Current user will only see invoices they have access to final rows = await tablesDB.listRows( databaseId: 'invoices_db', tableId: 'invoices', ); // For specific tenant data, you can add a query filter final tenantDocuments = await tablesDB.listRows( databaseId: 'invoices_db', tableId: 'invoices', queries: [ Query.equal('tenant_id', 'example_corp') ] ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) // Current user will only see invoices they have access to let rows = try await tablesDB.listRows( databaseId: "invoices_db", tableId: "invoices" ) // For specific tenant data, you can add a query filter let tenantDocuments = try await tablesDB.listRows( databaseId: "invoices_db", tableId: "invoices", queries: [ Query.equal(key: "tenant_id", value: "example_corp") ] ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Query val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) // Current user will only see invoices they have access to val rows = tablesDB.listRows( databaseId = "invoices_db", tableId = "invoices" ) // For specific tenant data, you can add a query filter val tenantDocuments = tablesDB.listRows( databaseId = "invoices_db", tableId = "invoices", queries = listOf( Query.equal("tenant_id", "example_corp") ) ) ``` {% /multicode %} {% arrow_link href="/docs/products/auth/team-invites" %} Learn how to manage team invitations {% /arrow_link %} --- ## OAuth 2 login https://appwrite.io/docs/products/auth/oauth2 OAuth authentication allows users to log in using accounts from other popular services. This can be convenient for users because they can start using your app without creating a new account. It can also be more secure, because the user has one less password that could become vulnerable. When using OAuth to authenticate, the authentication request is initiated from the client application. The user is then redirected to an OAuth 2 provider to complete the authentication step, and finally, the user is redirected back to the client application. {% info title="Identities and OAuth2" %} OAuth2 login creates an **identity** in Appwrite, allowing users to connect multiple providers to a single account. Learn more in [Identities](/docs/products/auth/identities). {% /info %} ### Configure OAuth 2 login {% #configure %} Before using OAuth 2 login, you need to enable and configure an OAuth 2 login provider. 1. Navigate to your Appwrite project. 2. Navigate to **Auth** > **Settings**. 3. Find and open the OAuth provider. 4. In the OAuth 2 settings modal, use the toggle to enable the provider. 5. Create and OAuth 2 app on the provider's developer platform. 6. Copy information from your OAuth2 provider's developer platform to fill the **OAuth2 Settings** modal in the Appwrite Console. 7. Configure redirect URL in your OAuth 2 provider's developer platform. Set it to URL provided to you by **OAuth2 Settings** modal in Appwrite Console. ### Initialize OAuth 2 login {% #init %} To initialize the OAuth 2 login process, use the [Create OAuth 2 Session](/docs/references/cloud/client-web/account#createOAuth2Session) route. OAuth2 sessions allow you to specify the scope of the access you want to request from the OAuth2 provider. The requested scopes describe which resources a session can access. You can pass the scopes to request through the `scopes` parameter when creating a session. The scope is provider-specific and can be found in the provider's documentation. {% tabs %} {% tabsitem #js title="Javascript" %} ```client-web import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); // Go to OAuth provider login page account.createOAuth2Session({ provider: OAuthProvider.Github, success: 'https://example.com/success', // redirect here on success failure: 'https://example.com/failed', // redirect here on failure scopes: ['repo', 'user'] // scopes (optional) }); ``` {% /tabsitem %} {% tabsitem #flutter title="Flutter" %} For Android, add the following activity inside the `` tag in your `AndroidManifest.xml`. Replace `` with your actual Appwrite project ID. ```xml ``` No other configuration is required for iOS. ```client-flutter import 'package:appwrite/appwrite.dart'; import 'package:appwrite/enums.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); // Go to OAuth provider login page await account.createOAuth2Session({ provider: OAuthProvider.github, scopes: ['repo', 'user'] }); ``` {% /tabsitem %} {% tabsitem #apple title="Apple" %} For Apple, add the following URL scheme to your `Info.plist`. ```xml CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLName io.appwrite CFBundleURLSchemes appwrite-callback- ``` If you're using UIKit, you'll also need to add a hook to your `SceneDelegate.swift` file to ensure cookies work correctly. ```client-apple func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { guard let url = URLContexts.first?.url, url.absoluteString.contains("appwrite-callback") else { return } WebAuthComponent.handleIncomingCookie(from: url) } ``` ```client-apple import Appwrite import AppwriteEnums let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) // Go to OAuth provider login page try await account.createOAuth2Session( provider: .github, scopes: ['repo', 'user'] ) ``` {% /tabsitem %} {% tabsitem #android title="Android" %} For Android, add the following activity inside the `` tag in your `AndroidManifest.xml`. Replace `` with your actual Appwrite project ID. ```xml ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.enums.OAuthProvider val client = Client(context) // Activity or application context .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) // Go to OAuth provider login page account.createOAuth2Session( provider = OAuthProvider.GITHUB, scopes = listOf('repo', 'user') ) ``` {% /tabsitem %} {% tabsitem #react-native title="React Native" %} If using Expo, set the URL scheme to `appwrite-callback-` in your `app.json` file. ```json { "expo": { "scheme": "appwrite-callback-" } } ``` Then, create a deep link, pass it to `account.createOAuth2Token()` method to create the login URL, open the URL in a browser, listen for the redirect, and finally create a session with the secret. ```client-react-native import { Client, Account, OAuthProvider } from "appwrite"; import { makeRedirectUri } from 'expo-auth-session' import * as WebBrowser from 'expo-web-browser'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); // Create deep link that works across Expo environments // Ensure localhost is used for the hostname to validation error for success/failure URLs const deepLink = new URL(makeRedirectUri({ preferLocalhost: true })); const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-://' // Start OAuth flow const loginUrl = await account.createOAuth2Token({ provider, success: `${deepLink}`, failure: `${deepLink}`, }); // Open loginUrl and listen for the scheme redirect const result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme); // Extract credentials from OAuth redirect URL const url = new URL(result.url); const secret = url.searchParams.get('secret'); const userId = url.searchParams.get('userId'); // Create session with OAuth credentials await account.createSession({ userId, secret }); // Redirect as needed ``` {% /tabsitem %} {% /tabs %} You'll be redirected to the OAuth 2 provider's login page to log in. Once complete, your user will be redirected back to your app. You can optionally configure `success` or `failure` redirect links on web to handle success and failure scenarios. ### OAuth 2 profile {% #profile %} After authenticating a user through their OAuth 2 provider, you can fetch their profile information such as their avatar image or name. To do this you can use the access token from the OAuth 2 provider and make API calls to the provider. After creating an OAuth 2 session, you can fetch the session to get information about the provider. {% info title="Tip" %} Replace `[SESSION_ID]` with either `"current"` to get or update the active session, or with a specific session ID. {% /info %} {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client(); const account = new Account(client); const session = await account.getSession({ sessionId: 'current' }); // Provider information console.log(session.provider); console.log(session.providerUid); console.log(session.providerAccessToken); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final session = await getSession( sessionId : "" ); // Provider information print(session.provider); print(session.providerUid); print(session.providerAccessToken); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let session = try await account.getSession( sessionId: "" ) // Provider information print(session.provider); print(session.providerUid); print(session.providerAccessToken); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val response = account.getSession( sessionId = "" ) // Provider information print(session.provider); print(session.providerUid); print(session.providerAccessToken); ``` {% /multicode %} An OAuth 2 [session](/docs/references/cloud/models/session) will have the following columns. | Property | Description | | -------------------------- | --------------------------------------------------------------------------------------------------------- | | provider | The OAuth2 Provider. | | providerUid | User ID from the OAuth 2 Provider. | | providerAccessToken | Access token from the OAuth 2 provider. Use this to **make requests to the OAuth 2 provider** to fetch personal data. | | providerAccessTokenExpiry | Check this value to know if an access token is about to expire. | You can use the `providerAccessToken` to make requests to your OAuth 2 provider. Refer to the docs for the OAuth 2 provider you're using to learn about making API calls with the access token. ### Refresh tokens {% #refresh-tokens %} OAuth 2 sessions expire to protect from security risks. This means the OAuth 2 session with a provider may expire, even when an Appwrite session remains active. OAuth 2 sessions should be refreshed periodically so access tokens don't expire. Check the value of `providerAccessTokenExpiry` to know if the token is expired or is about to expire. You can refresh the provider session by calling the [Update OAuth Session](/docs/references/cloud/client-web/account#updateSession) endpoint whenever your user visits your app. Avoid refreshing before every request, which might cause rate limit problems. {% multicode %} ```client-web const promise = account.updateSession({ sessionId: '[SESSION_ID]' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID Account account = Account(client); final result = await account.updateSession( sessionId: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let session = try await account.updateSession( sessionId: "" ); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val response = account.updateSession( sessionId = "" ); ``` {% /multicode %} {% info title="GraphQL" %} OAuth 2 is not available through the GraphQL API. You can use the REST API or any Client SDK instead. {% /info %} --- ## Phone (SMS) login https://appwrite.io/docs/products/auth/phone-sms {% info title="Note" %} Paid plans receive 10 free SMS messages each month. Thereafter OTPs are billed per message, with rates varying by country. See the [phone OTP rates](/docs/advanced/platform/phone-otp#rates) for more information. {% /info %} Phone authentication lets users create accounts using their phone numbers and log in through SMS messages. Create and use [mock phone numbers](/docs/products/auth/security#mock-phone-numbers) to initiate a phone authentication process without an actual phone number. ### Send SMS message {% #init %} Phone authentication is done using a two-step authentication process. When using phone authentication, the authentication request is initiated from the client application and an SMS message is sent to the user's phone. The SMS message will contain a secret the user can use to log in. Send an SMS message to initiate the authentication process. If the phone number has never been used, a **new account is created** using the provided `userId`, then the user will receive an SMS. If the phone number is already attached to an account, the **user ID is ignored** and the user will receive an SMS with the authentication code. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const token = await account.createPhoneToken({ userId: ID.unique(), phone: '+14255550123' }); const userId = token.userId; ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final token = await account.createPhoneToken( userId: ID.unique(), phone: '+14255550123' ); final userId = token.userId; ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); let account = Account(client); let token = try await account.createPhoneToken( userId: ID.unique(), phone: "+14255550123" ); let userId = token.userId; ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); val account = Account(client); val token = account.createPhoneToken( userId = ID.unique(), phone = "+14255550123" ); val userId = token.userId; ``` ```graphql mutation { accountCreatePhoneToken(userId: "unique()", phone: "+14255550123") { _id userId secret expire } } ``` {% /multicode %} ### Login {% #login %} After initiating the phone authentication process, the returned user ID and secret are used to confirm the user. The secret will usually be a 6-digit number in the SMS message sent to the user. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const session = await account.createSession({ userId: userId, secret: '' }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final session = await account.createSession( userId: userId, secret: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); let account = Account(client); let session = try await account.createSession( userId: userId, secret: "" ); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); val account = Account(client); val session = account.createSession( userId = userId, secret = "" ); ``` ```graphql mutation { accountCreateSession(userId: "", secret: "") { _id userId provider expire } } ``` {% /multicode %} After the secret is verified, a session will be created. --- ## Preferences https://appwrite.io/docs/products/auth/preferences Preferences allow you to store settings like theme choice, language selection, or notification preferences that are specific to individual users or shared across teams. ### User preferences {% #user-preferences %} You can store user preferences on a user's account using Appwrite's [Update Preferences](/docs/references/cloud/client-web/account#updatePrefs) endpoint. Preferences are stored as a key-value JSON object. The maximum allowed size for preferences is 64kB, and an error will be thrown if this limit is exceeded. #### Update user preferences Use the `updatePrefs` method to store user preferences as a JSON object. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const promise = account.updatePrefs({darkTheme: true, language: 'en'}); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final user = await account.updatePrefs( prefs: { "darkTheme": true, "language": "en", } ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let user = try await account.updatePrefs( prefs: [ "darkTheme": true, "language": "en" ] ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val user = account.updatePrefs( prefs = mapOf( "darkTheme" to true, "language" to "en" ) ) ``` ```graphql mutation { accountUpdatePrefs( prefs: "{\"darkTheme\": true, \"language\": \"en\"}" ) { _id name prefs { data } } } ``` {% /multicode %} #### Get user preferences Retrieve stored preferences with the `getPrefs` method. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const promise = account.getPrefs(); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final prefs = await account.getPrefs(); ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val prefs = account.getPrefs() ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let prefs = try await account.getPrefs() ``` ```graphql query { accountGetPrefs { data } } ``` {% /multicode %} ### Team preferences {% #team-preferences %} Team preferences let you store settings that apply to an entire team of users. They are well-suited for collaborative features like team-wide themes, notification preferences, or feature toggles. Team preferences are stored as a JSON object in the team row and are limited to 64kB of data. All team members can access these shared preferences. {% arrow_link href="/docs/products/auth/teams" %} Learn more about Appwrite Teams {% /arrow_link %} #### Update team preferences Store team-wide settings using the `updatePrefs` method with a team ID. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const teams = new Teams(client); const promise = teams.updatePrefs( '', { theme: 'corporate', notificationsEnabled: true, defaultView: 'kanban' } ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final teams = Teams(client); final team = await teams.updatePrefs( teamId: '', prefs: { "theme": "corporate", "notificationsEnabled": true, "defaultView": "kanban" } ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let teams = Teams(client) let team = try await teams.updatePrefs( teamId: "", prefs: [ "theme": "corporate", "notificationsEnabled": true, "defaultView": "kanban" ] ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val teams = Teams(client) val team = teams.updatePrefs( teamId = "", prefs = mapOf( "theme" to "corporate", "notificationsEnabled" to true, "defaultView" to "kanban" ) ) ``` {% /multicode %} #### Get team preferences Fetch team preferences by passing a team ID to the `getPrefs` method. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const teams = new Teams(client); const promise = teams.getPrefs(''); promise.then(function (prefs) { console.log(prefs); // Team preferences }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final teams = Teams(client); final prefs = await teams.getPrefs( teamId: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let teams = Teams(client) let prefs = try await teams.getPrefs( teamId: "" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val teams = Teams(client) val prefs = teams.getPrefs( teamId = "" ) ``` {% /multicode %} ### Browser localStorage {% #localstorage %} For device-specific preferences that don't need to sync across devices, the browser's localStorage API is a simple option. - Device-specific: Settings are only available on the current device - No server-side processing required - Data persists even after browser sessions end - Limited to ~5MB per origin ```js // Store a preference localStorage.setItem('darkMode', 'true'); // Retrieve a preference const darkMode = localStorage.getItem('darkMode'); ``` {% info title="Storing larger data" %} For complex preference structures or when storing larger amounts of data, Appwrite Databases offer a flexible solution. [Learn more about Appwrite Databases](/docs/products/databases). {% /info %} --- ## Start with Authentication https://appwrite.io/docs/products/auth/quick-start You can get up and running with Appwrite Authentication in minutes. You can add basic email and password authentication to your app with just a few lines of code. {% section #sign-up step=1 title="Signup" %} You can use the Appwrite [Client SDKs](/docs/sdks#client) to create an account using email and password. {% multicode %} ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const user = await account.create({ userId: ID.unique(), email: 'email@example.com', password: 'password' }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final account = Account(client); final user = await account.create( userId: ID.unique(), email: 'email@example.com', password: 'password', ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let account = Account(client) let user = try await account.create( userId: ID.unique(), email: "email@example.com", password: "password" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val account = Account(client) val user = account.create( userId = ID.unique(), email = "email@example.com", password = "password" ) ``` ```graphql mutation { accountCreate(userId: "unique()", email: "email@example.com", password: "password") { _id email name } } ``` ```client-react-native import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); const user = await account.create({ userId: ID.unique(), email: 'email@example.com', password: 'password' }); ``` {% /multicode %} {% /section %} {% section #login step=2 title="Login" %} After you've created your account, users can be logged in using the [Create Email Session](/docs/references/cloud/client-web/account#createEmailPasswordSession) method. {% multicode %} ```client-web const session = await account.createEmailPasswordSession({ email: email, password: password }); ``` ```client-flutter final session = await account.createEmailPasswordSession( email: 'email@example.com', password: 'password' ); ``` ```client-apple let session = try await account.createEmailPasswordSession( email: "email@example.com", password: "password" ) ``` ```client-android-kotlin val session = account.createEmailPasswordSession( email = "email@example.com", password = "password" ) ``` ```graphql mutation { accountcreateEmailPasswordSession(email: "email@example.com", password: "password") { _id userId provider expire } } ``` ```client-react-native const session = await account.createEmailPasswordSession({ email: email, password: password }); ``` {% /multicode %} {% /section %} {% section #check-authentication-state step=3 title="Check authentication state" %} After logging in, you can check the authentication state of the user. Appwrite's SDKs are stateless, so you need to manage the session state in your app. You can use the [Get Account](/docs/references/cloud/client-web/account#get) method to check if the user is logged in. {% multicode %} ```client-web try { const user = await account.get(); // Logged in } catch (err) { // Not logged in } ``` ```client-flutter try { final user = await account.get(); // Logged in } catch(e) { // Not logged in } ``` ```client-apple do { let user = try account.get() // Logged in } catch { // Not logged in } ``` ```client-android-kotlin return try { val user = account.get() // Logged in } catch (e: AppwriteException) { // Not logged in } ``` ```graphql query { accountGet { _id _createdAt _updatedAt name registration status labels passwordUpdate email phone emailVerification phoneVerification prefs { data } accessedAt } } ``` ```client-react-native try { const user = await account.get(); // Logged in } catch (err) { // Not logged in } ``` {% /multicode %} {% /section %} {% section #auth-and-nav step=4 title="Navigation (Optional)" %} A common pattern is to use route guards to redirect users to the login page if they are not authenticated. You can check the authentication state on app launch and before entering a protected route by calling `get()`. Route guard implementations are **opinionated** and depend on the platform and frame you are using. Take a look at some example usages from different platforms as inspiration. {% accordion %} {% accordion_item title="Web frameworks" %} Before routing to a page, you can check if the user is logged in and redirect them to the login page if they are not. {% tabs %} {% tabsitem #react-router title="React router" %} You can use [React router loaders](https://reactrouter.com/en/main/route/loader) to check if the user is logged in before rendering a route. You can find a similar example in this [YouTube video](https://youtu.be/pyfwQUc5Ssk). ```client-web import * as React from "react"; import { createBrowserRouter, } from "react-router-dom"; import "./index.css"; import Login from "./Login"; import Protected from "./Protected"; import { account } from "./lib/appwrite"; import { redirect } from "react-router-dom"; const router = createBrowserRouter([ { path: "/protected", element: , loader: async () => { try{ // logged in? pass user to the route const user = await account.get(); return { user }; } catch { // not logged in? redirect to login throw redirect('/login') } } }, { path: "/login", element: , }, ]); export default router; ``` {% /tabsitem %} {% tabsitem #vue-router title="Vue router" %} You can use [Vue router](https://router.vuejs.org/) wiht a [Pinia store](https://pinia.vuejs.org/) to check if the user is logged in before rendering a route. First, create a simple Pinia store to manage the authentication state. ```client-web import { account, ID, type Models } from '@/lib/appwrite' import type { register } from 'module'; import { defineStore } from 'pinia' export const useAuthStore = defineStore({ id: 'auth', state: () => ({ user: null as null | Models.User, }), getters: { isLoggedIn(): boolean { return !!this.user; }, }, actions: { async init() { try { this.user = await account.get(); } catch (error) { this.user = null; } }, // ... other operations }, }) ``` Then, check the authentication state before routing to a protected route. ```client-web import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import router from './router' import { useAuthStore } from './stores/auth' const app = createApp(App) app.use(createPinia()) const auth = useAuthStore(); auth.init().then(() => { router.beforeEach((to, from, next) => { // Not logged in? if (to.name == 'protected' && auth.isLoggedIn == false) { // Redirect to login if going to a protected route next({ name: 'login' }) } else { next() } }) app.use(router) app.mount('#app') }) ``` {% /tabsitem %} {% tabsitem #angular-router title="Angular router" %} ```client-web import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; import { Observable, from, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { account } from './lib/appwrite'; @Injectable({ providedIn: 'root' }) export class AuthGuardGuard implements CanActivate { constructor(private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { if (route.routeConfig?.path === "protected") { return this.checkLogin(); } return of(true); } private checkLogin(): Observable { return from(account.get()).pipe( map(() => true), catchError(() => of(this.router.createUrlTree(['/login']))) ); } } ``` {% /tabsitem %} {% tabsitem #svelte title="Svelte" %} In the root level `+layout.svelte` file, you can check the authentication state before rendering a route. ```client-web // src/routes/+layout.js import { appwrite } from "$lib/appwrite"; // Turn off SSR globally, turning the project into a static site export const ssr = false; export const load = async () => { try { return { account: await appwrite.account.get(), }; } catch { return { account: null, }; } }; ``` This will be accessible in the `load` function of each child route. ```client-web // src/routes/protected/+page.js import { redirect } from '@sveltejs/kit'; /** @type {import('./$types').PageLoad} */ export async function load({ parent }) { const { account } = await parent(); if (!account) { throw redirect(303, '/login'); } } ``` {% /tabsitem %} {% /tabs %} {% /accordion_item %} {% accordion_item title="Mobile and native" %} With mobile apps, you can apply similar logic to check the authentication state before displaying a screen or view. {% tabs %} {% tabsitem #flutter-go-router title="Flutter Go router" %} This example uses the Flutter Go router as an example, but the same concepts apply to other routing libraries. First, create a `ChangeNotifier` to manage the authentication state. ```client-flutter import 'package:flutter/material.dart'; import 'package:appwrite/appwrite.dart' show Client, ID; import 'package:appwrite/appwrite.dart' as Appwrite; import 'package:appwrite/models.dart' show User; class Account extends ChangeNotifier { final Appwrite.Account _account; User? _user; User? get user => _user; Account(Client client) : _account = Appwrite.Account(client); Future init() async { try { _user = await _account.get(); notifyListeners(); } catch(e) { debugPrint(e.toString()); rethrow; } } // ... other operations } ``` You can then use this state to redirect users to the login page if they are not authenticated. ```client-flutter import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import './providers/account.dart'; import './pages/login.dart'; import './pages/protected.dart'; String Function(BuildContext context, GoRouterState state) redirect = (BuildContext context, GoRouterState state) => context.read().user == null && state.matchedLocation != '/login' ? '/login' : state.matchedLocation; final router = GoRouter( redirect: redirect, initialLocation: '/login', routes: [ GoRoute( path: '/login', pageBuilder: (context, state) => const MaterialPage(child: LoginPage()), ), GoRoute( path: '/protected', pageBuilder: (context, state) => const MaterialPage(child: ProtectedPage()), ) ], ); ``` {% /tabsitem %} {% tabsitem #apple title="Apple" %} For Apple platforms, this example uses a `NavigationStack` but you can use similar concepts with other navigation methods. Initialize Appwrite and create an `AppwriteService`. ```client-apple import Foundation import Appwrite import AppwriteModels import JSONCodable class Appwrite { var client: Client var account: Account var tablesDB: TablesDB let databaseId = "default" let tableId = "ideas-tracker" public init() { self.client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") self.account = Account(client) } public func getUser() async throws -> User<[String: AnyCodable]> { let user = try await account.get() // you can also store the user in a local store return user } } ``` On launch, you can display a `SplashView` while you verify the authentication state. ```client-apple import Foundation import SwiftUI struct SplashView: View { @EnvironmentObject private var router: Router @EnvironmentObject private var AppwriteService: AppwriteService var body: some View { NavigationStack(path: $router.routes) { VStack { Text("Example App") .font(.largeTitle) .fontWeight(.bold) .padding() }.task { let user = await self.AppwriteService.getUser(); if !user { router.pushReplacement(.login) } else { router.pushReplacement(.protected) } } .navigationDestination(for: Route.self, destination: { $0 }) } } } ``` In your router, you can also check the authentication state before rendering a route. ```client-apple final class Router: ObservableObject { @Published var routes = [Route]() func push(_ screen: Route) { // Make sure you've already stored the user and auth state in a local store if (screen == .protected && !isLoggedIn){ routes.append(.login) } routes.append(screen) } // ... other operations } ``` {% /tabsitem %} {% tabsitem #android title="Android" %} Create some Appwrite Service, for example, `AppwriteService` to manage the authentication state. You can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1). ```client-android-kotlin //... imports object Appwrite { private const val ENDPOINT = "https://.cloud.appwrite.io/v1" private const val PROJECT_ID = "" private lateinit var client: Client fun init(context: Context) { client = Client(context) .setEndpoint(ENDPOINT) .setProject(PROJECT_ID) } } ``` Then, create an auth service to manage the authentication state. ```client-android-kotlin //... imports class AccountService(client: Client) { private val account = Account(client) suspend fun getLoggedIn(): User>? { return try { account.get() } catch (e: AppwriteException) { null } } // ... other operations } ``` Wrap your routes in some view, for example, `AppContent`, to check the authentication state before rendering a route. ```client-android-kotlin @Composable private fun AppContent(accountService: AccountService) { val user = remember { mutableStateOf>?>(null) } val screen = remember { mutableStateOf(Screen.Protected) } LaunchedEffect(screen) { user.value = accountService.getLoggedIn() } Scaffold(bottomBar = { AppBottomBar(screen) }) { padding -> Column(modifier = Modifier.padding(padding)) { when (screen.value) { Screen.User -> LoginScreen(user, accountService) else -> ProtectedScreen(user.value) } } } } ``` In the `MainActivity` class, initialize the Appwrite service and display the `AppContent` based on the authentication state. ```client-android-kotlin // ...imports In the `MainActivity` class, initialize the Appwrite service. class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Appwrite.init(applicationContext) setContent { // Update this line 👇 AppContent(Appwrite.account) } } } ``` {% /tabsitem %} {% tabsitem #react-native title="React Native" %} This example will use `@react-navigation/native` and `@react-navigation/native-stack` to manage the authentication state and redirect users to the login page if they are not authenticated. You can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1). Create a `UserContext` to manage the authentication state. ```client-react-native import { StyleSheet, Text, View } from 'react-native'; import { UserProvider } from './contexts/UserContext'; import { Router } from './lib/Router'; export default function App() { return ( ); } ``` Then, consume the `UserContext` in your `Router` component to check the authentication state before rendering a route. ```client-react-native import { NavigationContainer } from '@react-navigation/native'; import LoginScreen from '../views/Login'; import ProtectedSCreen from '../views/Protected'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { useUser } from '../contexts/UserContext'; const Stack = createNativeStackNavigator(); export function Router() { const user = useUser(); return ( {user.current == null ? ( ) : ( )} ); } ``` {% /tabsitem %} {% /tabs %} {% /accordion_item %} {% /accordion %} {% /section %} --- ## Security https://appwrite.io/docs/products/auth/security Appwrite provides many security features to keep both your Appwrite project and your user's information secure. {% partial file="auth-security.md" /%} --- ## SSR login https://appwrite.io/docs/products/auth/server-side-rendering Server-side rendering (SSR) is fully supported with Appwrite. You can use Appwrite with many SSR-oriented frameworks, such as Next.js, SvelteKit, Nuxt, Gatsby, Remix, and more. SSR is a technique where the server renders a web page and sending the fully rendered page to the client's web browser. This is in contrast to client-side rendering (CSR), where the client's web browser renders the page using JavaScript. This guide will walk you through the process of implementing an SSR application with Appwrite. ### SSR authentication flow {% #ssr-auth-flow %} In client-side rendered web apps, a [Client SDK](/docs/sdks#client) is used to perform authentication directly from the client's web browser. With server-side rendered web apps, a [Server SDK](/docs/sdks#server) is used to handle authentication against Appwrite. Authentication data is passed from the client's web browser to your server, and then your server makes requests to Appwrite on behalf of the client. Here's a high-level overview of the authentication flow: 1. The user enters their credentials in their web browser. 2. The browser sends the credentials to your server. 3. Your server uses the Server SDK to authenticate the user with Appwrite. 4. If the authentication is successful, your server sends a session cookie to the client's web browser. 5. The client's web browser sends the session cookie to your server with subsequent request. 6. Your server uses the session cookie to make authenticated requests to Appwrite on behalf of the client. {% only_dark %} ![CSR vs SSR flow diagram](/images/docs/auth/ssr/dark/ssr.png) {% /only_dark %} {% only_light %} ![CSR vs SSR flow diagram](/images/docs/auth/ssr/ssr.png) {% /only_light %} ### Initialize clients {% #initialize-clients %} {% info title="Server SDK required" %} Server-side rendering requires a [Server SDK](/docs/sdks#server) instead of a Client SDK. {% /info %} In SSR, your server-side application will be making authentication requests to Appwrite and passing session cookies to your client-side app on the browser. We'll need to initialize two Appwrite clients, one for admin requests and one for session-based requests. #### Admin client {% #admin-client %} {% info title="Admin clients" %} Admin clients should only be used if you need to perform admin actions that bypass permissions or [unauthenticated requests that bypass rate limits](#rate-limits). {% /info %} To initialize the admin client, we'll need to first [generate an API key](/docs/advanced/platform/api-keys#create-api-key). The API key should have the following scope in order to perform authentication: | Category {% width=120 %} | Required scopes | Purpose | |-----------|---------------------|---------| | Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. | {% multicode %} ```server-nodejs import { Client } from "node-appwrite"; // Using the server SDK const adminClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key ``` ```php use Appwrite\Client; use Appwrite\Services\Account; $adminClient = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey(''); // Your secret API key ``` ```python from appwrite.client import Client admin_client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint \ .set_project('') # Your project ID .set_key('') # Your secret API key ) ``` {% /multicode %} It is important to use an API key, as this will allow your server requests to bypass [rate limits](/docs/advanced/platform/rate-limits). If you don't use an API key, your server will be rate limited as if it were a client from a single IP address. #### Session client {% #session-client %} The session client will be used to make requests to Appwrite on behalf of the end-user. It will be initialized with the session, usually stored within a cookie. You should create a new client for each request and **never** share the client between requests. Use `a_session_` as the [cookie name](/docs/apis/rest#client-integration) and a [custom domain](/docs/advanced/platform/custom-domains) for your Appwrite endpoint if you want the session to work client-side as well. {% multicode %} ```server-nodejs const sessionClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const session = req.cookies['a_session_']; // Get the session cookie from the request if (session) { sessionClient.setSession(session); } ``` ```php $sessionClient = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject(''); // Your project ID $session = $_COOKIE['a_session_']; // Get the session cookie from the request if ($session) { $sessionClient->setSession($session); } ``` ```python from flask import request from appwrite.client import Client session_client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID ) ### Get the session cookie from the request session = request.cookies.get('a_session_') if session: session_client.set_session(session) ``` {% /multicode %} ### Creating email/password sessions {% #creating-sessions %} The most straightforward type of session to integrate is email/password. Create an endpoint using your server's framework of choice that accepts a username and password, and then makes a request to Appwrite to create a session. Once you have a session object, you can store it in a cookie. This will allow your users make authenticated requests to the Appwrite API from your server. Use the `secret` property of the session object as the cookie value. The `expire` property of the session object should be used as the cookie's max age. Here's an example with Express and PHP, but the same concepts apply to most frameworks. {% multicode %} ```server-nodejs import express from 'express'; // Initialize admin client here // ... app.post('/login', async (req, res) => { // Get email and password from request const { email, password } = req.body; const account = new Account(adminClient); try { // Create the session using the Appwrite client const session = await account.createEmailPasswordSession({ email, password }); // Set the session cookie res.cookie('a_session_', session.secret, { // use the session secret as the cookie value httpOnly: true, secure: true, sameSite: 'strict', expires: new Date(session.expire), path: '/', }); res.status(200).json({ success: true }); } catch (e) { res.status(400).json({ success: false, error: e.message }); } }); ``` ```php createEmailPasswordSession($email, $password); // Set the session cookie setcookie('a_session_', $session['secret'], [ 'httpOnly' => true, 'secure' => true, 'sameSite' => 'strict', 'expires' => strtotime($session['expire']), 'path' => '/', ]); echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } ``` ```python from flask import Flask, request, jsonify, make_response ### Initialize admin client here ### ... @app.post('/login') def login(): body = request.json # Get email and password from request email = body['email'] password = body['password'] try: account = Account(admin_client) # Create the session using the Appwrite client session = account.create_email_password_session(email, password) resp = make_response(jsonify({'success': True})) # Set the session cookie resp.set_cookie('a_session_', session['secret'], httponly=True, secure=True, samesite='Strict', expires=session['expire'], path='/' ) return resp except Exception as e: return jsonify({'success': False, 'error': str(e)}), 400 ``` {% /multicode %} We also recommend using the `httpOnly`, `secure`, and `sameSite` cookie options to ensure that the cookie is only sent over HTTPS, and is not accessible to JavaScript. This will prevent XSS attacks. ### Making authenticated requests {% #making-authenticated-requests %} Once a user has a session cookie, which will be set by the browser when it receives the `/login` endpoint's response, they can use it to make authenticated requests to your server. To enable this, you will need to read the cookie value from the request, and then pass it to the Appwrite client, using the `setSession` helper. When the browser makes a request to your domain's endpoints, it will automatically include session cookies. {% multicode %} ```server-nodejs // Initialize the session client here app.get('/user', async (req, res) => { // First, read the session cookie from the request const session = req.cookies['a_session_']; // If the session cookie is not present, return an error if (!session) { return res.status(401).json({ success: false, error: 'Unauthorized' }); } // Pass the session cookie to the Appwrite client sessionClient.setSession(session); // Now, you can make authenticated requests to the Appwrite API const account = new Account(sessionClient); try { const user = await account.get(); res.status(200).json({ success: true, user }); } catch (e) { res.status(400).json({ success: false, error: e.message }); } }); ``` ```php ']; // If the session cookie is not present, return an error if (!$session) { return http_response_code(401); } // Pass the session cookie to the Appwrite client $sessionClient->setSession($session); $account = new Account($sessionClient); // Now, you can make authenticated requests to the Appwrite API try { $user = $account->get(); echo json_encode(['success' => true, 'user' => $user]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } ``` ```python ### Initialize the session client here @app.get('/user') def get_user(): # First, read the session cookie from the request session = request.cookies.get('a_session_') # If the session cookie is not present, return an error if not session: return jsonify({'success': False, 'error': 'Unauthorized'}), 401 # pass the session cookie to the Appwrite client session_client.set_session(session) account = Account(session_client) # Now, you can make authenticated requests to the Appwrite API try: user = account.get() return jsonify({'success': True, 'user': user}) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 400 ``` {% /multicode %} ### Rate limits {% #rate-limits %} Unauthenticated requests are subject to [rate limits](/docs/advanced/platform/rate-limits). Normally, rate limits are applied by an abuse key, which is usually a combination of IP and another factor like user ID. When you make unauthenticated requests from your server, however, all requests originate from the same IP and no user ID is provided. This means that all unauthenticated requests from your server will be **subject to the same rate limits**. These rate limits protect your Appwrite server from abuse, if you need to make unauthenticated requests from your server, there are ways to bypass rate limits. ### Making unauthenticated requests {% #making-unauthenticated-requests %} Unauthenticated requests are used for displaying information to users before they log in. For example some apps may display all public posts on the home page, and only show private posts to logged-in users. There are two ways to make unauthenticated requests: {% table %} * Guest sessions * Admin clients --- * Uses the `createAnonymousSession` method to create a guest session. * Uses an API key to bypass rate limits. --- * Creates a session for unauthenticated users so each user has their own rate limit. * Bypasses rate limits completely because API keys are not limited. --- * Still respects access permissions. * Also bypasses access permissions. --- * Can be turned into a full session later by creating an account. * Cannot be later turned into a full session. {% /table %} You can create a guest session using the `createAnonymousSession` method. This will create a session for unauthenticated users, and each user will have their own rate limit. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const account = new sdk.Account(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; const promise = account.createAnonymousSession(); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ; $account = new Account($client); $result = $account->createAnonymousSession(); ``` ```python from appwrite.client import Client from appwrite.services.account import Account client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID ) account = Account(client) result = account.create_anonymous_session() ``` {% /multicode %} ### Forwarding user agent {% #forwarding-user-agent %} Appwrite sessions record some information about the client. To set this information in a server-side context use the `setForwardedUserAgent` to set the end-user's user agent. While optional, these can be useful for debugging and security purposes. {% multicode %} ```server-nodejs client.setForwardedUserAgent(req.headers['user-agent']); ``` ```php setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']); ``` ```python client.set_forwarded_user_agent(request.headers.get('user-agent')) ``` {% /multicode %} ### OAuth2 {% #oauth2 %} Server-side OAuth2 authentication requires two server endpoints: Create an initial endpoint that redirects the user to the OAuth2 provider's authentication page using Appwrite's `createOAuth2Token` method. After authenticating with the provider, the user will be redirected to the `success` URL with `userId` and `secret` URL parameters. {% multicode %} ```server-nodejs import { Client, Account, OAuthProvider } from "node-appwrite"; // Using the server SDK const adminClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key app.get('/oauth', async (req, res) => { const account = new Account(adminClient); const redirectUrl = await account.createOAuth2Token({ provider: OAuthProvider.Github, // Provider success: 'https://example.com/oauth/success', // Success URL failure: 'https://example.com/oauth/failure', // Failure URL }); res.redirect(redirectUrl); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey(''); // Your secret API key $account = new Account($adminClient); $redirectUrl = $account->createOAuth2Token( OAuthProvider::GITHUB(), 'https://example.com/oauth/success', // Success URL 'https://example.com/oauth/failure', // Failure URL ); header('Location' . $redirectUrl); ``` ```python from appwrite.client import Client from appwrite.services.account import Account, OAuthProvider from flask import Flask, request ,redirect, make_response, jsonify admin_client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') ) @app.get('/oauth') def oauth(): account = Account(admin_client) redirect_url = account.create_o_auth2_token( OAuthProvider.Github, # Provider 'https://example.com/oauth/success', # Success URL 'https://example.com/oauth/failure', # Failure URL ) return redirect(redirect_url) ``` {% /multicode %} Next, create a success callback endpoint that receives the `userId` and `secret` URL parameters, and then calls `createSession` on the server side. This endpoint returns a session object, which you can store in a cookie. {% multicode %} ```server-nodejs app.get('/oauth/success', async (req, res) => { const account = new Account(adminClient); // Get the userId and secret from the URL parameters const { userId, secret } = req.query; try { // Create the session using the Appwrite client const session = await account.createSession({ userId, secret }); // Set the session cookie res.cookie('a_session_', session.secret, { // Use the session secret as the cookie value httpOnly: true, secure: true, sameSite: 'strict', maxAge: sesion.expire path: '/', }); res.status(200).json({ success: true }); } catch (e) { res.status(400).json({ success: false, error: e.message }); } }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey(''); // Your secret API key $account = new Account($adminClient); // Get the userId and secret from the URL parameters $userId = $_GET['userId']; $secret = $_GET['secret']; try { // Create the session using the Appwrite client $session = $account->createSession($userId, $secret); // Set the session cookie setcookie('a_session_', $session['secret'], [ 'httpOnly' => true, 'secure' => true, 'sameSite' => 'strict', 'expires' => strtotime($session['expire']), 'path' => '/', ]); echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } ``` ```python @app.get('/oauth/success') def oauth_success(): account = Account(admin_client) # Get the userId and secret from the URL parameters user_id = request.args.get('userId') secret = request.args.get('secret') try: # Create the session using the Appwrite client session = account.create_session(user_id, secret) # Set the session cookie res = make_response(jsonify({'success': True})) # Set session cookie res.set_cookie( 'a_session_', session['secret'], httponly=True, secure=True, samesite='Strict', max_age=session['expire'], path='/' ) return res except Exception as e: return jsonify({'success': False, 'error': str(e)}), 400 ``` {% /multicode %} Now the cookie is set, it will be passed to the server with subsequent requests, and you can use it to make authenticated requests to the Appwrite API on behalf of the end-user. ### Tutorials {% #tutorials %} If you'd like to see SSR authentication implemented in a full auth example, see these tutorials. {% cards %} {% cards_item href="/docs/tutorials/nextjs-ssr-auth" title="Next.js SSR" icon="icon-nextjs"%} {% /cards_item %} {% cards_item href="/docs/tutorials/sveltekit-ssr-auth" title="SvelteKit SSR" icon="icon-svelte" %} {% /cards_item %} {% cards_item href="/docs/tutorials/nuxt-ssr-auth" title="Nuxt SSR" icon="icon-nuxt" %} {% /cards_item %} {% cards_item href="/docs/tutorials/astro-ssr-auth" title="Astro SSR" icon="icon-astro" %} {% /cards_item %} {% /cards %} --- ## Team invites https://appwrite.io/docs/products/auth/team-invites Appwrite provides two approaches for adding members to teams: client-side email invites and server-side custom flows. Each approach serves different use cases and offers unique benefits. ### Invite client-side Client-side email invites are perfect for implementing user-to-user invitations, allowing your users to invite others to join their teams, organizations, or shared resources. When creating a membership, Appwrite: 1. Creates a new user account if one doesn't exist for the email address 2. Sends an automated email invitation to the user 3. Creates a pending membership 4. Activates the membership when the user accepts Client-side invites are ideal when you want a simple, automated process that lets your users manage their own team invitations. Appwrite handles the email delivery with built-in templates and localization support, making it easy to implement a standard invite acceptance flow with email verification. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); // Create membership with email invite const membership = await teams.createMembership( '', ['developer'], // roles 'user@example.com', // email undefined, // userId (optional) undefined, // phone (optional) 'https://yourapp.com/accept-invite' // url - redirect after email click ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); // Create membership with email invite final membership = await teams.createMembership( teamId: '', roles: ['developer'], email: 'user@example.com', url: 'https://yourapp.com/accept-invite' // redirect after email click ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let teams = Teams(client) // Create membership with email invite let membership = try await teams.createMembership( teamId: "", roles: ["developer"], email: "user@example.com", url: "https://yourapp.com/accept-invite" // redirect after email click ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val teams = Teams(client) // Create membership with email invite val response = teams.createMembership( teamId = "", roles = listOf("developer"), email = "user@example.com", url = "https://yourapp.com/accept-invite" // redirect after email click ) ``` {% /multicode %} #### Accept invitations For client-side email invites, users must accept the invitation to join the team. The acceptance flow: 1. User receives an email with an invitation link containing a secret token 2. Clicking the link redirects to your app 3. Your app calls the acceptance endpoint 4. Upon success, the user gains immediate access {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); // Accept the invitation using the membership ID and secret const response = await teams.updateMembershipStatus( '', '', '', '' ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); // Accept the invitation using the membership ID and secret final response = await teams.updateMembershipStatus( teamId: '', membershipId: '', userId: '', secret: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Accept the invitation using the membership ID and secret let response = try await teams.updateMembershipStatus( teamId: "", membershipId: "", userId: "", secret: "" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Accept the invitation using the membership ID and secret val response = teams.updateMembershipStatus( teamId = "", membershipId = "", userId = "", secret = "" ) ``` {% /multicode %} ### Server-side custom flows Server-side membership creation bypasses the email invitation process, allowing direct member addition. This approach: 1. Creates an active membership immediately 2. Doesn't require user acceptance 3. Gives you complete control over the invitation workflow This makes them perfect for scenarios requiring custom workflows, such as bulk user management, automated team assignments, or integration with external systems. Since memberships are created directly, users gain immediate access without waiting for email acceptance. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') .setKey(''); const teams = new sdk.Teams(client); // Create membership directly with userId const membership = await teams.createMembership( '', ['developer'], '', 'John Doe' // optional name ); ``` ```server-python from appwrite.client import Client from appwrite.services.teams import Teams client = Client() client.set_endpoint('https://cloud.appwrite.io/v1') client.set_project('') client.set_key('') teams = Teams(client) ### Create membership directly with userId membership = teams.create_membership( team_id='', roles=['developer'], user_id='', name='John Doe' # optional ) ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key let teams = Teams(client) // Create membership directly with userId let membership = try await teams.createMembership( teamId: "", roles: ["developer"], userId: "", name: "John Doe" // optional ) ``` ```server-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key val teams = Teams(client) // Create membership directly with userId val response = teams.createMembership( teamId = "", roles = listOf("developer"), userId = "", name = "John Doe" // optional ) ``` {% /multicode %} ### Manage memberships Once team memberships are created, you'll need to manage their lifecycle. This includes checking status, updating roles, and removing members when necessary. #### Check membership status Before performing actions on team memberships, you often need to verify a user's current status within a team. The process differs between client-side and server-side implementations. ##### Client-side To check membership status client-side, first list the teams and then get the memberships for a specific team: {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); // Get list of teams the user is part of const teamsList = await teams.list(); // For a specific team, get all memberships const response = await teams.listMemberships({ teamId: '' }); // Find membership for specific user const userMembership = response.memberships.find( membership => membership.userId === '' ); if (userMembership) { console.log(userMembership.confirm); // false = invited, true = joined console.log(userMembership.roles); // ['developer', etc] } ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); // Get list of teams the user is part of final teamsList = await teams.list(); // For a specific team, get all memberships final response = await teams.listMemberships( teamId: '' ); // Find membership for specific user final userMembership = response.memberships.firstWhere( (membership) => membership.userId == '', orElse: () => null ); if (userMembership != null) { print(userMembership.confirm); // false = invited, true = joined print(userMembership.roles); // ['developer', etc] } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Get list of teams the user is part of let teamsList = try await teams.list() // For a specific team, get all memberships let response = try await teams.listMemberships( teamId: "" ) // Find membership for specific user if let userMembership = response.memberships.first(where: { $0.userId == "" }) { print(userMembership.confirm) // false = invited, true = joined print(userMembership.roles) // ['developer', etc] } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Get list of teams the user is part of val teamsList = teams.list() // For a specific team, get all memberships val response = teams.listMemberships( teamId = "team_id" ) // Find membership for specific user val userMembership = response.memberships.find { it.userId == "" } userMembership?.let { println(it.confirm) // false = invited, true = joined println(it.roles) // ['developer', etc] } ``` {% /multicode %} ##### Server-side Server-side requires iterating through teams and memberships since the data isn't filtered for a specific user: {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') .setKey(''); const teams = new sdk.Teams(client); // Get all teams const teamsList = await teams.list(); // Iterate through teams to find memberships for (const team of teamsList.teams) { const response = await teams.listMemberships({ teamId: team.$id }); // Find membership for specific user const userMembership = response.memberships.find( membership => membership.userId === '' ); if (userMembership) { console.log(`Team: ${team.name}`); console.log(`Joined: ${userMembership.joined}`); // null if invited, timestamp if joined console.log(`Roles: ${userMembership.roles}`); } } ``` ```server-python from appwrite.client import Client from appwrite.services.teams import Teams client = Client() client.set_endpoint('https://cloud.appwrite.io/v1') client.set_project('') client.set_key('') teams = Teams(client) // Get all teams teams_list = teams.list() // Iterate through teams to find memberships for team in teams_list['teams']: response = teams.list_memberships(team['$id']) // Find membership for specific user user_membership = next( (m for m in response['memberships'] if m['userId'] == ''), None ) if user_membership: print(f"Team: {team['name']}") print(f"Joined: {user_membership['joined']}") # null if invited, timestamp if joined print(f"Roles: {user_membership['roles']}") ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") .setKey("") let teams = Teams(client) // Get all teams let teamsList = try await teams.list() // Iterate through teams to find memberships for team in teamsList.teams { let response = try await teams.listMemberships( teamId: team.$id ) // Find membership for specific user if let userMembership = response.memberships.first(where: { $0.userId == "" }) { print("Team: \(team.name)") print("Joined: \(userMembership.joined)") # null if invited, timestamp if joined print("Roles: \(userMembership.roles)") } } ``` ```server-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") .setKey("") val teams = Teams(client) // Get all teams val teamsList = teams.list() // Iterate through teams to find memberships teamsList.teams.forEach { team -> val response = teams.listMemberships(teamId = team.$id) // Find membership for specific user val userMembership = response.memberships.find { it.userId == "" } userMembership?.let { println("Team: ${team.name}") println("Joined: ${it.joined}") # null if invited, timestamp if joined println("Roles: ${it.roles}") } } ``` {% /multicode %} #### Remove members Team owners can remove members or users can leave teams: {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const teams = new Teams(client); await teams.deleteMembership( '', '' ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); final teams = Teams(client); await teams.deleteMembership( teamId: '', membershipId: '' ); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) try await teams.deleteMembership( teamId: "", membershipId: "" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) teams.deleteMembership( teamId = "", membershipId = "" ) ``` {% /multicode %} ### Manage team permissions Teams in Appwrite use a role-based access control (RBAC) system. Each team member can be assigned one or more roles that define their permissions within the team. #### Update roles You can assign roles when creating a membership or update them later. Note that only team members with the owner role can update other members' roles: {% multicode %} ```client-web import { Client, Teams } from "appwrite" const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') const teams = new Teams(client) // Update member roles await teams.updateMembership( '', '', ['admin', 'developer'] ) ``` ```client-flutter import 'package:appwrite/appwrite.dart' final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') final teams = Teams(client) // Update member roles await teams.updateMembership( teamId: '', membershipId: '', roles: ['admin', 'developer'] ) ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Update member roles try await teams.updateMembership( teamId: "", membershipId: "", roles: ["admin", "developer"] ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Update member roles teams.updateMembership( teamId = "", membershipId = "", roles = listOf("admin", "developer") ) ``` {% /multicode %} #### Check role access You can verify if a user has specific roles: {% multicode %} ```client-web import { Client, Teams } from "appwrite" const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') const teams = new Teams(client) // Get team memberships const response = await teams.listMemberships({ teamId: '' }); // Check if user has specific role const membership = response.memberships.find(m => m.userId === '') const isAdmin = membership?.roles.includes('admin') ?? false ``` ```client-flutter import 'package:appwrite/appwrite.dart' final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') final teams = Teams(client) // Get team memberships final response = await teams.listMemberships( teamId: '' ) // Check if user has specific role final membership = response.memberships.firstWhere( (m) => m.userId == '', orElse: () => null ) final isAdmin = membership?.roles.contains('admin') ?? false ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let teams = Teams(client) // Get team memberships let response = try await teams.listMemberships( teamId: "" ) // Check if user has specific role let membership = response.memberships.first { $0.userId == "" } let isAdmin = membership?.roles.contains("admin") ?? false ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val teams = Teams(client) // Get team memberships val response = teams.listMemberships( teamId = "" ) // Check if user has specific role val membership = response.memberships.find { it.userId == "" } val isAdmin = membership?.roles?.contains("admin") ?: false ``` {% /multicode %} See how to grant row and file access to team roles in the [permissions](/docs/advanced/platform/permissions#example-2-team-roles) guide. {% arrow_link href="/docs/products/auth/teams" %} Learn more about team management {% /arrow_link %} --- ## Teams https://appwrite.io/docs/products/auth/teams Teams are a good way to allow users to share access to resources. For example, in a todo app, a user can [create a team](/docs/references/cloud/client-web/teams#create) for one of their todo lists and [invite another user](/docs/references/cloud/client-web/teams#createMembership) to the team to grant the other user access. You can further give special rights to parts of a team using team roles. The invited user can [accept the invitation](/docs/references/cloud/client-web/teams#updateMembershipStatus) to gain access. If the user's ever removed from the team, they'll lose access again. {% arrow_link href="/docs/products/auth/multi-tenancy" %} Learn about using Teams for multi-tenancy {% /arrow_link %} ### Create team {% #create %} For example, we can create a team called `teachers` with roles `maths`, `sciences`, `arts`, and `literature`. The creator of the team is also granted the `owner` role. **Only those with the `owner` role can invite and remove members**. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client(); const teams = new Teams(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; const promise = teams.create( 'teachers', 'Teachers', ['maths', 'sciences', 'arts', 'literature'] ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Teams teams = Teams(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; Future result = teams.create( teamId: 'teachers', name: 'Teachers', roles: ['maths', 'sciences', 'arts', 'literature'] ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let teams = Teams(client) let team = try await teams.create( teamId: "teachers", name: "Teachers", roles: ["maths", "sciences", "arts", "literature"] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val teams = Teams(client) val response = teams.create( teamId = "teachers", name = "Teachers", roles = listOf("maths", "sciences", "arts", "literature") ) ``` {% /multicode %} ### Invite a member {% #create-membership %} You can invite members to a team by creating team memberships. For example, inviting "David" a math teacher, to the teachers team. {% multicode %} ```client-web import { Client, Teams } from "appwrite"; const client = new Client(); const teams = new Teams(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; const promise = teams.createMembership( 'teachers', ["maths"], "david@example.com" ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Teams teams = Teams(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; Future result = teams.createMembership( teamId: 'teachers', roles: ['maths'], email: 'david@example.com' ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let teams = Teams(client) let membership = try await teams.createMembership( teamId: "teachers", roles: ["maths"], email: "david@example.com" ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Teams val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val teams = Teams(client) val response = teams.createMembership( teamId = "teachers", roles = listOf("maths"), email = "david@example.com" ) ``` {% /multicode %} ### Using the CLI {% #using-the-CLI %} {% partial file="cli-disclaimer.md" /%} Use the CLI command `appwrite teams create-membership [options]` to invite a new member into your team. ```sh appwrite teams create-membership --team-id "" --roles --phone "+12065550100" --name "" --user-id "" ``` You can also get, update, and delete a user's membership. However, you cannot use the CLI to configure permissions for team members. {% arrow_link href="/docs/tooling/command-line/teams#commands" %} Learn more about the CLI teams commands {% /arrow_link %} ### Permissions {% #permissions %} You can grant permissions to all members of a team using the `Role.team()` role or individual roles in the team using the `Role.team(, [, , ...])` role. | Description | Role | | ------------------------------------------- | ------------------------------------------- | | All members | `Role.team()`| | Select roles | `Role.team(, [, , ...])`| {% arrow_link href="/docs/advanced/platform/permissions" %} Learn more about permissions {% /arrow_link %} ### Memberships privacy {% #memberships-privacy %} In certain use cases, your app may not need to share members' personal information with others. You can safeguard privacy by marking specific membership details as private. To configure this setting, navigate to **Auth** > **Security** > **Memberships privacy** These details can be made private: - `userName` - The member's name - `userEmail` - The member's email address - `mfa` - Whether the member has enabled multi-factor authentication --- ## Tokens https://appwrite.io/docs/products/auth/tokens Tokens are short-lived secrets created by an [Appwrite Server SDK](/docs/sdks#server) that can be exchanged for session by a [Client SDK](/docs/sdks#client) to log in users. Some auth methods like [Magic URL login](/docs/products/auth/magic-url), [Email OTP login](/docs/products/auth/email-otp), or [Phone (SMS) login](/docs/products/auth/phone-sms) already generate tokens. You can also create custom tokens using the [Create token](/docs/products/auth/custom-token) endpoint of the [Users API](/docs/products/auth/users). This can be used to implement **custom authentication flows**. Tokens are created with the following properties: | Property | Type | Description | | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `$id` | string | Token ID. | | `$createdAt` | string | Token creation date in ISO 8601 format. | | `userId` | string | User ID. | | `secret` | string | Token secret key. This will return an empty string unless the response is returned using an API key or as part of a webhook payload. | | `expire` | string | Token expiration date in ISO 8601 format. | Many Appwrite authentication methods use a token-base flow to authenticate users. For token-based authentication methods, there are two high level steps to authenticate a user: ### Token login {% #token-login %} You can find different usage of tokens in the Appwrite. {% cards %} {% cards_item href="/docs/products/auth/custom-token" title="Custom token login" %} {% /cards_item %} {% cards_item href="/docs/products/auth/email-otp" title="Email OTP login" %} {% /cards_item %} {% cards_item href="/docs/products/auth/magic-url" title="Email magic URL" %} {% /cards_item %} {% cards_item href="/docs/products/auth/phone-sms" title="Phone (SMS) OTP" %} {% /cards_item %} {% /cards %} --- ## Manage users https://appwrite.io/docs/products/auth/users Appwrite Users API is used for managing users in server applications. Users API can only be used with an API key with the [Server SDK](/docs/sdks#server), to manage all users. If you need to act on behalf of users through an Appwrite Function or your own backend, use [JWT login](/docs/products/auth/jwt). {% partial file="account-vs-user.md" /%} The users API can be used to create users, import users, update user info, get user audit logs, and remove users. {% arrow_link href="/docs/references/cloud/server-nodejs/users" %} Learn more in the Users API references {% /arrow_link %} --- ## Verify user https://appwrite.io/docs/products/auth/verify-user User verification in Appwrite allows you to verify user email addresses and phone numbers. Users don't need to be verified to log in, but you can restrict resource access to verified users only using permissions. ### Verify email {% #verify-email %} To verify a user's email, first ensure the user is logged in so that the verification email can be sent to the user who created the account. Then, send the verification email specifying a redirect URL. The verification secrets will be appended as query parameters to the redirect URL. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') // Your project ID const account = new Account(client); const promise = account.createVerification({ url: 'https://example.com/verify' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { Client client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); Account account = Account(client); Future result = account.createVerification( url: 'https://example.com/verify' ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let account = Account(client) let token = try await account.createVerification( url: "https://example.com/verify" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val account = Account(client) val response = account.createVerification( url = "https://example.com/verify" ) ``` {% /multicode %} After the user clicks the link in the email, they will be redirected to your site with the query parameters `userId` and `secret`. If you're on a mobile platform, you will need to create the appropriate deep link to handle the verification. Next, implement the verification page that handles the redirect. {% multicode %} ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const urlParams = new URLSearchParams(window.location.search); const secret = urlParams.get('secret'); const userId = urlParams.get('userId'); const promise = account.updateVerification({ userId, secret }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { Client client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject(''); Account account = Account(client); Future result = account.updateVerification( userId: '', secret: '' ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") let account = Account(client) let response = try await account.updateVerification( userId: "", secret: "" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Account val client = Client(context) .setEndpoint("https://cloud.appwrite.io/v1") .setProject("") val account = Account(client) val response = account.updateVerification( userId = "", secret = "" ) ``` {% /multicode %} ### Verify phone {% #verify-phone %} To verify a phone number, first ensure the user is logged in and has a phone number set on their account. {% multicode %} ```client-web const response = await account.updatePhone({ phone: '+12065550100', password: 'password' }); ``` ```client-flutter Future result = account.updatePhone( phone: '+12065550100', password: 'password' ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updatePhone( phone: "+12065550100", password: "password" ) ``` ```client-android-kotlin val response = account.updatePhone( phone = "+12065550100", password = "password" ) ``` {% /multicode %} Then initiate verification by calling `createPhoneVerification`. {% multicode %} ```client-web const response = await account.createPhoneVerification(); ``` ```client-flutter Future result = account.createPhoneVerification(); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.createPhoneVerification() ``` ```client-android-kotlin val response = account.createPhoneVerification() ``` {% /multicode %} After the user receives the verification code, complete verification by calling `updatePhoneVerification`. {% multicode %} ```client-web const response = await account.updatePhoneVerification({ userId: '[USER_ID]', secret: '[SECRET]' }); ``` ```client-flutter Future result = account.updatePhoneVerification( userId: '', secret: '' ); result.then((response) { print(response); }).catchError((error) { print(error.response); }); ``` ```client-apple let response = try await account.updatePhoneVerification( userId: "", secret: "" ) ``` ```client-android-kotlin val response = account.updatePhoneVerification( userId = "", secret = "" ) ``` {% /multicode %} ### Restrict access {% #restrict-access %} You can restrict resource access to verified users in two ways: - Use `user([USER_ID], "verified")` to restrict access to a specific verified user - Use `users("verified")` to restrict access to any verified user ### Verification events {% #verification-events %} The following events are triggered during the verification process: - `users.*.verification.*` - Triggers on any user's verification token event - `users.*.verification.*.create` - Triggers when a verification token for a user is created - `users.*.verification.*.update` - Triggers when a verification token for a user is validated Each event returns a Token Object. --- ## Avatars https://appwrite.io/docs/products/avatars Appwrite **Avatars** provides a comprehensive set of utilities for generating and manipulating images, icons, and avatars for your applications. The Avatars service helps you complete everyday tasks related to app images, icons, and avatars without managing complex image processing infrastructure. All Avatars endpoints support image transformations including resizing, cropping, and quality adjustments to optimize performance and ensure images display correctly across different devices and screen sizes. {% arrow_link href="/docs/products/avatars/quick-start" %} Get started with Avatars in minutes {% /arrow_link %} ### Capabilities {% #capabilities %} Appwrite Avatars supports multiple image generation and manipulation features to enhance your application's visual elements. {% cards %} {% cards_item href="/docs/products/avatars/initials" title="User initials" %} Generate avatar images from user names or initials with customizable colors and sizes. {% /cards_item %} {% cards_item href="/docs/products/avatars/qr-codes" title="QR codes" %} Create QR codes for authentication, sharing, and other use cases with customizable size and margin. {% /cards_item %} {% cards_item href="/docs/products/avatars/flags" title="Country flags" %} Fetch country flag icons for displaying user locations and regional information. {% /cards_item %} {% cards_item href="/docs/products/avatars/browsers" title="Browser icons" %} Retrieve browser icons for displaying user agent information and device compatibility. {% /cards_item %} {% cards_item href="/docs/products/avatars/payment-methods" title="Payment methods" %} Get payment method logos for checkout flows and transaction displays. {% /cards_item %} {% cards_item href="/docs/products/avatars/favicons" title="Favicons" %} Fetch favicons from remote websites for link previews and bookmark displays. {% /cards_item %} {% cards_item href="/docs/products/avatars/screenshots" title="Screenshots" %} Capture webpage screenshots with customizable viewport, theme, and browser settings. {% /cards_item %} {% cards_item href="/docs/products/avatars/image-manipulation" title="Image proxy" %} Transform remote images with resizing, cropping, and quality adjustments. {% /cards_item %} {% /cards %} ### Image transformations {% #image-transformations %} All Avatars endpoints support consistent image transformation parameters to ensure optimal display across your application. You can resize images, adjust quality, and apply cropping to match your design requirements while maintaining performance. ### No authentication required {% #no-authentication %} The Avatars service is publicly accessible and does not require user authentication or API keys. All endpoints can be called directly from client applications, making it easy to integrate avatar generation into any part of your application. To prevent abuse, you can also disable it from your project's settings. --- ## Browser icons https://appwrite.io/docs/products/avatars/browsers The browser icon endpoint provides access to icons for popular web browsers. This is useful for displaying user agent information, browser compatibility indicators, and device compatibility in your application. ### Get browser icon {% #get-browser %} Retrieve a browser icon by browser code. {% multicode %} ```client-web import { Client, Avatars, Browser } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getBrowser({ code: Browser.GoogleChrome, width: 100, height: 100 }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getBrowser( code: Browser.googleChrome, width: 100, height: 100 ).then((bytes) { // Use the browser icon bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getBrowser( code: Browser.googleChrome, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars import io.appwrite.enums.Browser val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getBrowser( code = Browser.GOOGLE_CHROME, width = 100, height = 100 ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getBrowser({ code: 'chrome', width: 100, height: 100 }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getBrowser` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | code | string | The browser code. Supported codes include `chrome`, `firefox`, `safari`, `edge`, `opera`, and others. | | width | integer | The width of the output image in pixels. Accepts values between `0-2000`. | | height | integer | The height of the output image in pixels. Accepts values between `0-2000`. | ### Supported browsers {% #supported-browsers %} Common browser codes include: {% multicode %} ```client-web import { Browser } from "appwrite"; // Chrome const chrome = avatars.getBrowser({ code: Browser.GoogleChrome, width: 100, height: 100 }); // Firefox const firefox = avatars.getBrowser({ code: Browser.MozillaFirefox, width: 100, height: 100 }); // Safari const safari = avatars.getBrowser({ code: Browser.Safari, width: 100, height: 100 }); // Edge const edge = avatars.getBrowser({ code: Browser.MicrosoftEdge, width: 100, height: 100 }); // Opera const opera = avatars.getBrowser({ code: Browser.Opera, width: 100, height: 100 }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // Chrome Future chrome = avatars.getBrowser( code: Browser.googleChrome, width: 100, height: 100 ); // Firefox Future firefox = avatars.getBrowser( code: Browser.mozillaFirefox, width: 100, height: 100 ); // Safari Future safari = avatars.getBrowser( code: Browser.safari, width: 100, height: 100 ); // Edge Future edge = avatars.getBrowser( code: Browser.microsoftEdge, width: 100, height: 100 ); // Opera Future opera = avatars.getBrowser( code: Browser.opera, width: 100, height: 100 ); ``` ```client-apple import Appwrite // Chrome let chrome = try await avatars.getBrowser( code: Browser.googleChrome, width: 100, height: 100 ) // Firefox let firefox = try await avatars.getBrowser( code: Browser.mozillaFirefox, width: 100, height: 100 ) // Safari let safari = try await avatars.getBrowser( code: Browser.safari, width: 100, height: 100 ) // Edge let edge = try await avatars.getBrowser( code: Browser.microsoftEdge, width: 100, height: 100 ) // Opera let opera = try await avatars.getBrowser( code: Browser.opera, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.enums.Browser // Chrome val chrome = avatars.getBrowser( code = Browser.GOOGLE_CHROME, width = 100, height = 100 ) // Firefox val firefox = avatars.getBrowser( code = Browser.MOZILLA_FIREFOX, width = 100, height = 100 ) // Safari val safari = avatars.getBrowser( code = Browser.SAFARI, width = 100, height = 100 ) // Edge val edge = avatars.getBrowser( code = Browser.MICROSOFT_EDGE, width = 100, height = 100 ) // Opera val opera = avatars.getBrowser( code = Browser.OPERA, width = 100, height = 100 ) ``` {% /multicode %} ### Use cases {% #use-cases %} Browser icons are commonly used for: - **Session information**: Display user session details visually alongside browser icons. Browser codes from session data match perfectly with the browser codes used in this endpoint, allowing you to create cohesive visual session displays - **Analytics dashboards**: Display browser usage statistics and user agent information - **Session management**: Show active sessions with browser information - **Compatibility indicators**: Display supported browsers for features or content - **Security logs**: Visualize login attempts and session information by browser - **User activity**: Display browser information in activity feeds and audit logs --- ## Favicons https://appwrite.io/docs/products/avatars/favicons The favicon endpoint retrieves favicons from remote websites. This is useful for displaying website icons in link previews, bookmarks, and social sharing interfaces. ### Get favicon {% #get-favicon %} Retrieve a favicon from a remote website URL. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getFavicon({ url: 'https://example.com', width: 100, height: 100 }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getFavicon( url: 'https://example.com', width: 100, height: 100 ).then((bytes) { // Use the favicon image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getFavicon( url: "https://example.com", width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getFavicon( url = "https://example.com", width = 100, height = 100 ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getFavicon({ url: 'https://example.com', width: 100, height: 100 }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getFavicon` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | url | string | The URL of the website to fetch the favicon from. Must be a valid HTTP or HTTPS URL. | | width | integer | The width of the output image in pixels. Accepts values between `0-2000`. | | height | integer | The height of the output image in pixels. Accepts values between `0-2000`. | ### Favicon retrieval {% #favicon-retrieval %} The service automatically attempts to retrieve favicons from common locations on the target website, including standard favicon paths and HTML meta tags. {% multicode %} ```client-web // Standard website const exampleFavicon = avatars.getFavicon({ url: 'https://example.com', width: 64, height: 64 }); // Website with subdomain const subdomainFavicon = avatars.getFavicon({ url: 'https://blog.example.com', width: 64, height: 64 }); // Website with path const pathFavicon = avatars.getFavicon({ url: 'https://example.com/page', width: 64, height: 64 }); ``` ```client-flutter // Standard website Future exampleFavicon = avatars.getFavicon( url: 'https://example.com', width: 64, height: 64 ); // Website with subdomain Future subdomainFavicon = avatars.getFavicon( url: 'https://blog.example.com', width: 64, height: 64 ); // Website with path Future pathFavicon = avatars.getFavicon( url: 'https://example.com/page', width: 64, height: 64 ); ``` ```client-apple // Standard website let exampleFavicon = try await avatars.getFavicon( url: "https://example.com", width: 64, height: 64 ) // Website with subdomain let subdomainFavicon = try await avatars.getFavicon( url: "https://blog.example.com", width: 64, height: 64 ) // Website with path let pathFavicon = try await avatars.getFavicon( url: "https://example.com/page", width: 64, height: 64 ) ``` ```client-android-kotlin // Standard website val exampleFavicon = avatars.getFavicon( url = "https://example.com", width = 64, height = 64 ) // Website with subdomain val subdomainFavicon = avatars.getFavicon( url = "https://blog.example.com", width = 64, height = 64 ) // Website with path val pathFavicon = avatars.getFavicon( url = "https://example.com/page", width = 64, height = 64 ) ``` {% /multicode %} ### Use cases {% #use-cases %} Favicons are commonly used for: - **Link previews**: Display website icons in link preview cards and social sharing - **Bookmarks**: Show website icons in bookmark lists and collections - **Referral tracking**: Display source website icons in analytics and referral reports - **Content aggregation**: Show website icons in RSS feed readers and content aggregators - **Social media**: Display website icons in social media link previews and embeds --- ## Country flags https://appwrite.io/docs/products/avatars/flags The country flag endpoint provides access to flag icons for all countries. This is useful for displaying user locations, regional settings, and country-specific information in your application. ### Get country flag {% #get-flag %} Retrieve a country flag icon by its [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code. {% multicode %} ```client-web import { Client, Avatars, Flag } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getFlag({ code: Flag.UnitedStates, width: 100, height: 100 }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ).then((bytes) { // Use the flag image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars import io.appwrite.enums.Flag val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getFlag( code = Flag.UNITED_STATES, width = 100, height = 100 ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getFlag({ code: 'US', width: 100, height: 100 }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getFlag` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | code | string | The ISO [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code (e.g., `US`, `GB`, `FR`). | | width | integer | The width of the output image in pixels. Accepts values between `0-2000`. | | height | integer | The height of the output image in pixels. Accepts values between `0-2000`. | ### Country codes {% #country-codes %} Use ISO 3166-1 alpha-2 country codes to specify the country. These are two-letter codes that uniquely identify countries. {% multicode %} ```client-web import { Flag } from "appwrite"; // United States const usFlag = avatars.getFlag({ code: Flag.UnitedStates, width: 100, height: 100 }); // United Kingdom const ukFlag = avatars.getFlag({ code: Flag.UnitedKingdom, width: 100, height: 100 }); // France const frFlag = avatars.getFlag({ code: Flag.France, width: 100, height: 100 }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // United States Future usFlag = avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ); // United Kingdom Future ukFlag = avatars.getFlag( code: Flag.unitedKingdom, width: 100, height: 100 ); // France Future frFlag = avatars.getFlag( code: Flag.france, width: 100, height: 100 ); ``` ```client-apple import Appwrite // United States let usFlag = try await avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ) // United Kingdom let ukFlag = try await avatars.getFlag( code: Flag.unitedKingdom, width: 100, height: 100 ) // France let frFlag = try await avatars.getFlag( code: Flag.france, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.enums.Flag // United States val usFlag = avatars.getFlag( code = Flag.UNITED_STATES, width = 100, height = 100 ) // United Kingdom val ukFlag = avatars.getFlag( code = Flag.UNITED_KINGDOM, width = 100, height = 100 ) // France val frFlag = avatars.getFlag( code = Flag.FRANCE, width = 100, height = 100 ) ``` {% /multicode %} ### Use cases {% #use-cases %} Country flags are commonly used for: - **Session information**: Display user session location visually alongside flag icons. Country codes from session data match perfectly with flags codes, allowing you to create cohesive visual session displays - **User profiles**: Display user country or location in profile pages - **Regional settings**: Show available regions or languages by country - **Analytics dashboards**: Visualize geographic data and user distribution - **Localization**: Indicate content availability or regional restrictions - **Shipping information**: Display origin and destination countries in shipping interfaces --- ## Image proxy https://appwrite.io/docs/products/avatars/image-manipulation The image proxy endpoint allows you to fetch and transform images from remote URLs. You can resize, crop, and adjust the quality of images to optimize them for your application's display requirements and performance needs. ### Proxy remote image {% #get-image %} Fetch and transform an image from a remote URL with various transformation options. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getImage({ url: 'https://example.com/image.jpg', width: 800, height: 600, quality: 90 }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getImage( url: 'https://example.com/image.jpg', width: 800, height: 600, quality: 90 ).then((bytes) { // Use the transformed image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getImage( url: "https://example.com/image.jpg", width: 800, height: 600, quality: 90 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getImage( url = "https://example.com/image.jpg", width = 800, height = 600, quality = 90 ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getImage({ url: 'https://example.com/image.jpg', width: 800, height: 600, quality: 90 }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getImage` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | url | string | The URL of the remote image to fetch and transform. Must be a valid HTTP or HTTPS URL. | | width | integer | The width of the output image in pixels. The image will be resized maintaining aspect ratio. Accepts values between `0-4000`. | | height | integer | The height of the output image in pixels. The image will be resized maintaining aspect ratio. Accepts values between `0-4000`. | | quality | integer | The quality of the output image. Accepts values between `0-100`, where `100` is the highest quality. Defaults to `100` if not provided. | | gravity | string | The gravity point for cropping when both width and height are provided. Accepts: `center`, `top-left`, `top`, `top-right`, `left`, `right`, `bottom-left`, `bottom`, `bottom-right`. Defaults to `center`. | | output | string | The output image format. Supported formats: `jpg`, `jpeg`, `png`, `gif`, `webp`, `avif`, `heic`. If not provided, uses the original image format. | ### Resizing {% #resizing %} Resize images to specific dimensions while maintaining aspect ratio. When only width or height is provided, the other dimension is calculated automatically. {% multicode %} ```client-web // Resize by width only (height calculated automatically) const widthOnly = avatars.getImage({ url: 'https://example.com/image.jpg', width: 800 }); // Resize by height only (width calculated automatically) const heightOnly = avatars.getImage({ url: 'https://example.com/image.jpg', height: 600 }); // Resize to specific dimensions const specificSize = avatars.getImage({ url: 'https://example.com/image.jpg', width: 800, height: 600 }); ``` ```client-flutter // Resize by width only (height calculated automatically) Future widthOnly = avatars.getImage( url: 'https://example.com/image.jpg', width: 800 ); // Resize by height only (width calculated automatically) Future heightOnly = avatars.getImage( url: 'https://example.com/image.jpg', height: 600 ); // Resize to specific dimensions Future specificSize = avatars.getImage( url: 'https://example.com/image.jpg', width: 800, height: 600 ); ``` ```client-apple // Resize by width only (height calculated automatically) let widthOnly = try await avatars.getImage( url: "https://example.com/image.jpg", width: 800 ) // Resize by height only (width calculated automatically) let heightOnly = try await avatars.getImage( url: "https://example.com/image.jpg", height: 600 ) // Resize to specific dimensions let specificSize = try await avatars.getImage( url: "https://example.com/image.jpg", width: 800, height: 600 ) ``` ```client-android-kotlin // Resize by width only (height calculated automatically) val widthOnly = avatars.getImage( url = "https://example.com/image.jpg", width = 800 ) // Resize by height only (width calculated automatically) val heightOnly = avatars.getImage( url = "https://example.com/image.jpg", height = 600 ) // Resize to specific dimensions val specificSize = avatars.getImage( url = "https://example.com/image.jpg", width = 800, height = 600 ) ``` {% /multicode %} ### Cropping {% #cropping %} When both width and height are specified, you can control how the image is cropped using the gravity parameter. {% multicode %} ```client-web // Crop from center const centerCrop = avatars.getImage({ url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'center' }); // Crop from top-left const topLeftCrop = avatars.getImage({ url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'top-left' }); // Crop from bottom-right const bottomRightCrop = avatars.getImage({ url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'bottom-right' }); ``` ```client-flutter // Crop from center Future centerCrop = avatars.getImage( url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'center' ); // Crop from top-left Future topLeftCrop = avatars.getImage( url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'top-left' ); // Crop from bottom-right Future bottomRightCrop = avatars.getImage( url: 'https://example.com/image.jpg', width: 400, height: 400, gravity: 'bottom-right' ); ``` ```client-apple // Crop from center let centerCrop = try await avatars.getImage( url: "https://example.com/image.jpg", width: 400, height: 400, gravity: "center" ) // Crop from top-left let topLeftCrop = try await avatars.getImage( url: "https://example.com/image.jpg", width: 400, height: 400, gravity: "top-left" ) // Crop from bottom-right let bottomRightCrop = try await avatars.getImage( url: "https://example.com/image.jpg", width: 400, height: 400, gravity: "bottom-right" ) ``` ```client-android-kotlin // Crop from center val centerCrop = avatars.getImage( url = "https://example.com/image.jpg", width = 400, height = 400, gravity = "center" ) // Crop from top-left val topLeftCrop = avatars.getImage( url = "https://example.com/image.jpg", width = 400, height = 400, gravity = "top-left" ) // Crop from bottom-right val bottomRightCrop = avatars.getImage( url = "https://example.com/image.jpg", width = 400, height = 400, gravity = "bottom-right" ) ``` {% /multicode %} ### Quality and format {% #quality-format %} Adjust image quality and output format to optimize file size and performance. {% multicode %} ```client-web // High quality JPEG const highQuality = avatars.getImage({ url: 'https://example.com/image.jpg', width: 1200, quality: 95, output: 'jpg' }); // Optimized WebP const webpOptimized = avatars.getImage({ url: 'https://example.com/image.jpg', width: 1200, quality: 85, output: 'webp' }); // Compressed for mobile const mobileOptimized = avatars.getImage({ url: 'https://example.com/image.jpg', width: 800, quality: 75, output: 'jpg' }); ``` ```client-flutter // High quality JPEG Future highQuality = avatars.getImage( url: 'https://example.com/image.jpg', width: 1200, quality: 95, output: 'jpg' ); // Optimized WebP Future webpOptimized = avatars.getImage( url: 'https://example.com/image.jpg', width: 1200, quality: 85, output: 'webp' ); // Compressed for mobile Future mobileOptimized = avatars.getImage( url: 'https://example.com/image.jpg', width: 800, quality: 75, output: 'jpg' ); ``` ```client-apple // High quality JPEG let highQuality = try await avatars.getImage( url: "https://example.com/image.jpg", width: 1200, quality: 95, output: "jpg" ) // Optimized WebP let webpOptimized = try await avatars.getImage( url: "https://example.com/image.jpg", width: 1200, quality: 85, output: "webp" ) // Compressed for mobile let mobileOptimized = try await avatars.getImage( url: "https://example.com/image.jpg", width: 800, quality: 75, output: "jpg" ) ``` ```client-android-kotlin // High quality JPEG val highQuality = avatars.getImage( url = "https://example.com/image.jpg", width = 1200, quality = 95, output = "jpg" ) // Optimized WebP val webpOptimized = avatars.getImage( url = "https://example.com/image.jpg", width = 1200, quality = 85, output = "webp" ) // Compressed for mobile val mobileOptimized = avatars.getImage( url = "https://example.com/image.jpg", width = 800, quality = 75, output = "jpg" ) ``` {% /multicode %} ### Use cases {% #use-cases %} Image proxy is commonly used for: - **Responsive images**: Generate multiple sizes for different screen sizes and devices - **Thumbnail generation**: Create thumbnails from full-size images for galleries and lists - **Format optimization**: Convert images to modern formats like WebP or AVIF for better compression - **Performance optimization**: Reduce image file sizes while maintaining acceptable quality - **Content delivery**: Transform images on-the-fly for CDN delivery and caching - **Secure content serving**: Serve untrusted user content securely through Appwrite's trusted proxy with proper SSL certification, protecting your application from mixed content warnings and security vulnerabilities --- ## User initials https://appwrite.io/docs/products/avatars/initials The user initials endpoint generates avatar images from names or initials. This is particularly useful for displaying user profiles when no profile picture is available, creating a consistent visual identity across your application. ### Generate initials {% #generate-initials %} Generate an avatar image from a user's name. The service automatically extracts initials from the name and displays them on a colored background. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getInitials({ name: 'John Doe', width: 200, height: 200, background: '000000' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getInitials( name: 'John Doe', width: 200, height: 200, background: '000000' ).then((bytes) { // Use the image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getInitials( name: "John Doe", width: 200, height: 200, background: "000000" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getInitials( name = "John Doe", width = 200, height = 200, background = "000000" ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getInitials({ name: 'John Doe', width: 200, height: 200, background: '000000' }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getInitials` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | name | string | The name to generate initials from. The service extracts the first letter of each word. | | width | integer | The width of the output image in pixels. Accepts values between `0-2000`. | | height | integer | The height of the output image in pixels. Accepts values between `0-2000`. | | background | string | The background color in hexadecimal format without the leading `#`. Defaults to a random color if not provided. | ### Customization {% #customization %} You can customize the appearance of the initials avatar by adjusting the dimensions and background color. The service automatically selects appropriate text color based on the background for optimal contrast. {% multicode %} ```client-web // Square avatar with custom background const squareAvatar = avatars.getInitials({ name: 'Jane Smith', width: 150, height: 150, background: 'FF5733' }); // Rectangular avatar const rectangularAvatar = avatars.getInitials({ name: 'Bob Johnson', width: 200, height: 100, background: '3498DB' }); // Random background color (omit background parameter) const randomBackground = avatars.getInitials({ name: 'Alice Williams', width: 180, height: 180 }); ``` ```client-flutter // Square avatar with custom background Future squareAvatar = avatars.getInitials( name: 'Jane Smith', width: 150, height: 150, background: 'FF5733' ); // Rectangular avatar Future rectangularAvatar = avatars.getInitials( name: 'Bob Johnson', width: 200, height: 100, background: '3498DB' ); // Random background color (omit background parameter) Future randomBackground = avatars.getInitials( name: 'Alice Williams', width: 180, height: 180 ); ``` ```client-apple // Square avatar with custom background let squareAvatar = try await avatars.getInitials( name: "Jane Smith", width: 150, height: 150, background: "FF5733" ) // Rectangular avatar let rectangularAvatar = try await avatars.getInitials( name: "Bob Johnson", width: 200, height: 100, background: "3498DB" ) // Random background color (omit background parameter) let randomBackground = try await avatars.getInitials( name: "Alice Williams", width: 180, height: 180 ) ``` ```client-android-kotlin // Square avatar with custom background val squareAvatar = avatars.getInitials( name = "Jane Smith", width = 150, height = 150, background = "FF5733" ) // Rectangular avatar val rectangularAvatar = avatars.getInitials( name = "Bob Johnson", width = 200, height = 100, background = "3498DB" ) // Random background color (omit background parameter) val randomBackground = avatars.getInitials( name = "Alice Williams", width = 180, height = 180 ) ``` {% /multicode %} ### Use cases {% #use-cases %} User initials avatars are commonly used in: - **User profiles**: Display avatars in user lists, comments, and profile pages when no profile picture is available - **Team members**: Show team member avatars in collaboration interfaces - **Notifications**: Display sender avatars in notification systems - **Activity feeds**: Show user avatars in activity and timeline views --- ## Payment methods https://appwrite.io/docs/products/avatars/payment-methods The payment method endpoint provides access to logos for popular payment methods and credit card brands. This is useful for displaying accepted payment methods in checkout flows, transaction history, and payment settings. ### Get payment method logo {% #get-credit-card %} Retrieve a payment method or credit card logo by code. {% multicode %} ```client-web import { Client, Avatars, CreditCard } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getCreditCard({ code: CreditCard.Visa, width: 100, height: 100 }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getCreditCard( code: CreditCard.visa, width: 100, height: 100 ).then((bytes) { // Use the payment method logo bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getCreditCard( code: CreditCard.visa, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars import io.appwrite.enums.CreditCard val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getCreditCard( code = CreditCard.VISA, width = 100, height = 100 ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getCreditCard({ code: 'visa', width: 100, height: 100 }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getCreditCard` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | code | string | The payment method or credit card code. Supported codes include `visa`, `mastercard`, `amex`, `discover`, `paypal`, and others. | | width | integer | The width of the output image in pixels. Accepts values between `0-2000`. | | height | integer | The height of the output image in pixels. Accepts values between `0-2000`. | ### Supported payment methods {% #supported-payment-methods %} Common payment method codes include: {% multicode %} ```client-web import { CreditCard } from "appwrite"; // Visa const visa = avatars.getCreditCard({ code: CreditCard.Visa, width: 100, height: 100 }); // Mastercard const mastercard = avatars.getCreditCard({ code: CreditCard.Mastercard, width: 100, height: 100 }); // American Express const amex = avatars.getCreditCard({ code: CreditCard.AmericanExpress, width: 100, height: 100 }); // Discover const discover = avatars.getCreditCard({ code: CreditCard.Discover, width: 100, height: 100 }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // Visa Future visa = avatars.getCreditCard( code: CreditCard.visa, width: 100, height: 100 ); // Mastercard Future mastercard = avatars.getCreditCard( code: CreditCard.mastercard, width: 100, height: 100 ); // American Express Future amex = avatars.getCreditCard( code: CreditCard.americanExpress, width: 100, height: 100 ); // Discover Future discover = avatars.getCreditCard( code: CreditCard.discover, width: 100, height: 100 ); ``` ```client-apple import Appwrite // Visa let visa = try await avatars.getCreditCard( code: CreditCard.visa, width: 100, height: 100 ) // Mastercard let mastercard = try await avatars.getCreditCard( code: CreditCard.mastercard, width: 100, height: 100 ) // American Express let amex = try await avatars.getCreditCard( code: CreditCard.americanExpress, width: 100, height: 100 ) // Discover let discover = try await avatars.getCreditCard( code: CreditCard.discover, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.enums.CreditCard // Visa val visa = avatars.getCreditCard( code = CreditCard.VISA, width = 100, height = 100 ) // Mastercard val mastercard = avatars.getCreditCard( code = CreditCard.MASTERCARD, width = 100, height = 100 ) // American Express val amex = avatars.getCreditCard( code = CreditCard.AMERICAN_EXPRESS, width = 100, height = 100 ) // Discover val discover = avatars.getCreditCard( code = CreditCard.DISCOVER, width = 100, height = 100 ) ``` {% /multicode %} ### Use cases {% #use-cases %} Payment method logos are commonly used for: - **Checkout flows**: Display accepted payment methods during checkout - **Payment settings**: Show saved payment methods in user account settings - **Transaction history**: Display payment method used for each transaction - **Subscription management**: Show payment methods associated with subscriptions - **Billing information**: Display payment method logos in invoices and receipts --- ## QR codes https://appwrite.io/docs/products/avatars/qr-codes The QR code endpoint generates QR code images from any text string. QR codes are commonly used for two-factor authentication, sharing links, encoding data, and enabling quick access to information. ### Generate QR code {% #generate-qr %} Generate a QR code image from a text string. The QR code can be scanned by any standard QR code reader. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getQR({ text: 'https://example.com', size: 300, margin: 1, download: false }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getQR( text: 'https://example.com', size: 300, margin: 1, download: false ).then((bytes) { // Use the QR code image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getQR( text: "https://example.com", size: 300, margin: 1, download: false ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getQR( text = "https://example.com", size = 300, margin = 1, download = false ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getQR({ text: 'https://example.com', size: 300, margin: 1, download: false }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getQR` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | text | string | The text string to encode in the QR code. Can be a URL, authentication URI, or any text data. | | size | integer | The size of the QR code in pixels. Accepts values between `0-1000`. Defaults to `200` if not provided. | | margin | integer | The margin around the QR code in pixels. Accepts values between `0-10`. Defaults to `1` if not provided. | | download | boolean | Whether to download the image or return a URL. Defaults to `false`. | ### Two-factor authentication {% #two-factor-authentication %} QR codes are commonly used for two-factor authentication (2FA). When setting up TOTP authentication, you can generate a QR code from the authenticator URI. {% multicode %} ```client-web // After creating an MFA authenticator const authenticator = await account.createMfaAuthenticator('totp'); const qrCode = avatars.getQR({ text: authenticator.uri, size: 400, margin: 2 }); ``` ```client-flutter // After creating an MFA authenticator final authenticator = await account.createMfaAuthenticator('totp'); final qrCode = avatars.getQR( text: authenticator.uri, size: 400, margin: 2 ); ``` ```client-apple // After creating an MFA authenticator let authenticator = try await account.createMfaAuthenticator(type: "totp") let qrCode = try await avatars.getQR( text: authenticator.uri, size: 400, margin: 2 ) ``` ```client-android-kotlin // After creating an MFA authenticator val authenticator = account.createMfaAuthenticator("totp") val qrCode = avatars.getQR( text = authenticator.uri, size = 400, margin = 2 ) ``` {% /multicode %} ### Customization {% #customization %} Adjust the size and margin of QR codes to match your design requirements. Larger sizes improve scanability, while appropriate margins ensure the QR code is properly recognized by scanners. {% multicode %} ```client-web // Small QR code with minimal margin const smallQR = avatars.getQR({ text: 'https://example.com', size: 150, margin: 0 }); // Large QR code for printing const largeQR = avatars.getQR({ text: 'https://example.com', size: 800, margin: 3 }); // Standard size for web display const standardQR = avatars.getQR({ text: 'https://example.com', size: 300, margin: 1 }); ``` ```client-flutter // Small QR code with minimal margin Future smallQR = avatars.getQR( text: 'https://example.com', size: 150, margin: 0 ); // Large QR code for printing Future largeQR = avatars.getQR( text: 'https://example.com', size: 800, margin: 3 ); // Standard size for web display Future standardQR = avatars.getQR( text: 'https://example.com', size: 300, margin: 1 ); ``` ```client-apple // Small QR code with minimal margin let smallQR = try await avatars.getQR( text: "https://example.com", size: 150, margin: 0 ) // Large QR code for printing let largeQR = try await avatars.getQR( text: "https://example.com", size: 800, margin: 3 ) // Standard size for web display let standardQR = try await avatars.getQR( text: "https://example.com", size: 300, margin: 1 ) ``` ```client-android-kotlin // Small QR code with minimal margin val smallQR = avatars.getQR( text = "https://example.com", size = 150, margin = 0 ) // Large QR code for printing val largeQR = avatars.getQR( text = "https://example.com", size = 800, margin = 3 ) // Standard size for web display val standardQR = avatars.getQR( text = "https://example.com", size = 300, margin = 1 ) ``` {% /multicode %} ### Use cases {% #use-cases %} QR codes are commonly used for: - **Two-factor authentication**: Generate QR codes for TOTP authenticator setup - **Team invitations**: Create QR codes for team invite links to enable quick member onboarding - **Link sharing**: Create QR codes for URLs to enable quick access - **Event tickets**: Generate QR codes for event registration and check-in - **Wi-Fi credentials**: Encode Wi-Fi network information for easy connection - **Contact information**: Share vCard data encoded in QR codes --- ## Start with Avatars https://appwrite.io/docs/products/avatars/quick-start You can start using Appwrite Avatars immediately. The service is publicly accessible and does not require authentication or API keys. ### Initialize the client {% #initialize-client %} First, initialize the Appwrite client with your project endpoint and project ID. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); ``` {% /multicode %} ### Generate user initials {% #generate-initials %} Generate an avatar image from a user's name or initials. This is useful for displaying user profiles when no profile picture is available. {% multicode %} ```client-web const result = avatars.getInitials({ name: 'John Doe', width: 200, height: 200, background: '000000' }); console.log(result); // Resource URL ``` ```client-flutter Future result = avatars.getInitials( name: 'John Doe', width: 200, height: 200, background: '000000' ).then((bytes) { // Use the image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple let byteBuffer = try await avatars.getInitials( name: "John Doe", width: 200, height: 200, background: "000000" ) ``` ```client-android-kotlin val result = avatars.getInitials( name = "John Doe", width = 200, height = 200, background = "000000" ) ``` ```client-react-native const result = avatars.getInitials({ name: 'John Doe', width: 200, height: 200, background: '000000' }); console.log(result); // Resource URL ``` {% /multicode %} ### Generate QR code {% #generate-qr %} Create a QR code from any text string. This is commonly used for two-factor authentication, sharing links, or encoding data. {% multicode %} ```client-web const result = avatars.getQR({ text: 'https://example.com', size: 300, margin: 1, download: false }); console.log(result); // Resource URL ``` ```client-flutter Future result = avatars.getQR( text: 'https://example.com', size: 300, margin: 1, download: false ).then((bytes) { // Use the QR code image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple let byteBuffer = try await avatars.getQR( text: "https://example.com", size: 300, margin: 1, download: false ) ``` ```client-android-kotlin val result = avatars.getQR( text = "https://example.com", size = 300, margin = 1, download = false ) ``` ```client-react-native const result = avatars.getQR({ text: 'https://example.com', size: 300, margin: 1, download: false }); console.log(result); // Resource URL ``` {% /multicode %} ### Get country flag {% #get-flag %} Retrieve a country flag icon by country code. This is useful for displaying user locations or regional information. {% multicode %} ```client-web import { Flag } from "appwrite"; const result = avatars.getFlag({ code: Flag.UnitedStates, width: 100, height: 100 }); console.log(result); // Resource URL ``` ```client-flutter Future result = avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ).then((bytes) { // Use the flag image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple let byteBuffer = try await avatars.getFlag( code: Flag.unitedStates, width: 100, height: 100 ) ``` ```client-android-kotlin import io.appwrite.enums.Flag val result = avatars.getFlag( code = Flag.UNITED_STATES, width = 100, height = 100 ) ``` ```client-react-native const result = avatars.getFlag({ code: 'US', width: 100, height: 100 }); console.log(result); // Resource URL ``` {% /multicode %} ### Next steps {% #next-steps %} Now that you've generated your first avatars, explore more advanced features: - Learn about [user initials](/docs/products/avatars/initials) customization - Discover [QR code](/docs/products/avatars/qr-codes) options - Explore [image manipulation](/docs/products/avatars/image-manipulation) capabilities --- ## Screenshots https://appwrite.io/docs/products/avatars/screenshots The screenshots endpoint allows you to capture full webpage screenshots with extensive customization options. You can control the browser viewport size, theme, user agent, geolocation, permissions, and other browser settings to capture web pages exactly as they would appear in different scenarios. This is valuable for various [use cases](#use-cases), including generating visual documentation, creating link previews, automating QA testing across different devices and browsers, or archiving web pages for compliance and record-keeping. Instead of manually taking screenshots or setting up complex headless browser infrastructure, you can generate high-quality screenshots on-demand through a simple API call, ensuring consistency and saving development time. ### Get webpage screenshot {% #get-screenshot %} Capture a screenshot of a remote webpage with customizable browser settings and viewport options. {% multicode %} ```client-web import { Client, Avatars } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getScreenshot({ url: 'https://example.com' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final avatars = Avatars(client); Future result = avatars.getScreenshot( url: 'https://example.com' ).then((bytes) { // Use the screenshot image bytes return bytes; }).catchError((error) { print(error.response); }); ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let avatars = Avatars(client) let byteBuffer = try await avatars.getScreenshot( url: "https://example.com" ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val avatars = Avatars(client) val result = avatars.getScreenshot( url = "https://example.com" ) ``` ```client-react-native import { Client, Avatars } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const avatars = new Avatars(client); const result = avatars.getScreenshot({ url: 'https://example.com' }); console.log(result); // Resource URL ``` {% /multicode %} ### Parameters {% #parameters %} The `getScreenshot` method accepts the following parameters: | Parameter | Type | Description | | --------- | ---- | ----------- | | url | string | The URL of the website to capture. Must be a valid HTTP or HTTPS URL. | | headers | object | HTTP headers to send with the browser request. Pass a key-value object with custom headers. Defaults to `{}` if not provided. | | viewportWidth | integer | The width of the browser viewport in pixels. Accepts values between `1-1920`. Defaults to `1280` if not provided. | | viewportHeight | integer | The height of the browser viewport in pixels. Accepts values between `1-1080`. Defaults to `720` if not provided. | | scale | float | The device pixel ratio for the screenshot. Accepts values between `0.1-3` for different DPI settings. Defaults to `1` if not provided. | | theme | string | The browser color scheme theme. Accepts: `light` or `dark`. Defaults to `light` if not provided. | | userAgent | string | A custom user agent string for the browser request. Defaults to browser default if not provided. | | fullpage | boolean | Capture the full scrollable page (`true`) or only the viewport (`false`). Defaults to `false` if not provided. | | locale | string | The browser locale code (e.g., `en-US`, `fr-FR`). Defaults to browser default if not provided. | | timezone | string | IANA timezone identifier (e.g., `America/New_York`, `Europe/London`). Defaults to browser default if not provided. | | latitude | float | Geolocation latitude for the screenshot. Accepts values between `-90` to `90`. Defaults to `0` if not provided. | | longitude | float | Geolocation longitude for the screenshot. Accepts values between `-180` to `180`. Defaults to `0` if not provided. | | accuracy | float | Geolocation accuracy in meters. Accepts values between `0-100000`. Defaults to `0` if not provided. | | touch | boolean | Enable touch device support (`true`) or disable it (`false`). Defaults to `false` if not provided. | | permissions | array | Array of browser permissions to grant. Accepts: `geolocation`, `camera`, `microphone`, `notifications`, `midi`, `push`, `clipboard-read`, `clipboard-write`, `payment-handler`, `usb`, `bluetooth`, `accelerometer`, `gyroscope`, `magnetometer`, `ambient-light-sensor`, `background-sync`, `persistent-storage`, `screen-wake-lock`, `web-share`, `xr-spatial-tracking`. Defaults to `[]` if not provided. | | sleep | integer | Wait time in seconds before taking the screenshot. Accepts values between `0-10`. Defaults to `0` if not provided. | | width | integer | The output image width in pixels. Pass `0` to use original width, or `1-2000` for custom width. Defaults to `0` (original width) if not provided. | | height | integer | The output image height in pixels. Pass `0` to use original height, or `1-2000` for custom height. Defaults to `0` (original height) if not provided. | | quality | integer | Screenshot quality. Accepts values between `0-100`. `-1` preserves original image quality. Defaults to `-1` if not provided. | | output | string | Output image format. Supported formats: `jpg`, `jpeg`, `png`, `gif`, `webp`. Defaults to `png` if not provided. | ### Viewport customization {% #viewport-customization %} Control the browser viewport to capture web pages as they would appear on different devices and screen sizes. {% multicode %} ```client-web // Desktop viewport const desktopScreenshot = avatars.getScreenshot({ url: 'https://example.com', viewportWidth: 1920, viewportHeight: 1080 }); // Tablet viewport const tabletScreenshot = avatars.getScreenshot({ url: 'https://example.com', viewportWidth: 768, viewportHeight: 1024 }); // Mobile viewport const mobileScreenshot = avatars.getScreenshot({ url: 'https://example.com', viewportWidth: 375, viewportHeight: 667 }); // Full page capture const fullpageScreenshot = avatars.getScreenshot({ url: 'https://example.com', fullpage: true }); ``` ```client-flutter // Desktop viewport Future desktopScreenshot = avatars.getScreenshot( url: 'https://example.com', viewportWidth: 1920, viewportHeight: 1080 ); // Tablet viewport Future tabletScreenshot = avatars.getScreenshot( url: 'https://example.com', viewportWidth: 768, viewportHeight: 1024 ); // Mobile viewport Future mobileScreenshot = avatars.getScreenshot( url: 'https://example.com', viewportWidth: 375, viewportHeight: 667 ); // Full page capture Future fullpageScreenshot = avatars.getScreenshot( url: 'https://example.com', fullpage: true ); ``` ```client-apple // Desktop viewport let desktopScreenshot = try await avatars.getScreenshot( url: "https://example.com", viewportWidth: 1920, viewportHeight: 1080 ) // Tablet viewport let tabletScreenshot = try await avatars.getScreenshot( url: "https://example.com", viewportWidth: 768, viewportHeight: 1024 ) // Mobile viewport let mobileScreenshot = try await avatars.getScreenshot( url: "https://example.com", viewportWidth: 375, viewportHeight: 667 ) // Full page capture let fullpageScreenshot = try await avatars.getScreenshot( url: "https://example.com", fullpage: true ) ``` ```client-android-kotlin // Desktop viewport val desktopScreenshot = avatars.getScreenshot( url = "https://example.com", viewportWidth = 1920, viewportHeight = 1080 ) // Tablet viewport val tabletScreenshot = avatars.getScreenshot( url = "https://example.com", viewportWidth = 768, viewportHeight = 1024 ) // Mobile viewport val mobileScreenshot = avatars.getScreenshot( url = "https://example.com", viewportWidth = 375, viewportHeight = 667 ) // Full page capture val fullpageScreenshot = avatars.getScreenshot( url = "https://example.com", fullpage = true ) ``` {% /multicode %} ### Browser customization {% #browser-customization %} Customize browser settings like theme, user agent, locale, and timezone to simulate different browser environments. {% multicode %} ```client-web import { Client, Avatars, Theme, Timezone } from "appwrite"; // Dark theme screenshot const darkThemeScreenshot = avatars.getScreenshot({ url: 'https://example.com', theme: Theme.Dark }); // Custom locale and timezone const localizedScreenshot = avatars.getScreenshot({ url: 'https://example.com', locale: 'fr-FR', timezone: Timezone.EuropeParis }); // Custom user agent const chromeScreenshot = avatars.getScreenshot({ url: 'https://example.com', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }); // Touch device const touchScreenshot = avatars.getScreenshot({ url: 'https://example.com', touch: true }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // Dark theme screenshot Future darkThemeScreenshot = avatars.getScreenshot( url: 'https://example.com', theme: Theme.dark ); // Custom locale and timezone Future localizedScreenshot = avatars.getScreenshot( url: 'https://example.com', locale: 'fr-FR', timezone: Timezone.europeParis ); // Custom user agent Future chromeScreenshot = avatars.getScreenshot( url: 'https://example.com', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' ); // Touch device Future touchScreenshot = avatars.getScreenshot( url: 'https://example.com', touch: true ); ``` ```client-apple import Appwrite // Dark theme screenshot let darkThemeScreenshot = try await avatars.getScreenshot( url: "https://example.com", theme: Theme.dark ) // Custom locale and timezone let localizedScreenshot = try await avatars.getScreenshot( url: "https://example.com", locale: "fr-FR", timezone: Timezone.europeParis ) // Custom user agent let chromeScreenshot = try await avatars.getScreenshot( url: "https://example.com", userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15" ) // Touch device let touchScreenshot = try await avatars.getScreenshot( url: "https://example.com", touch: true ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Avatars import io.appwrite.enums.Theme import io.appwrite.enums.Timezone // Dark theme screenshot val darkThemeScreenshot = avatars.getScreenshot( url = "https://example.com", theme = Theme.DARK ) // Custom locale and timezone val localizedScreenshot = avatars.getScreenshot( url = "https://example.com", locale = "fr-FR", timezone = Timezone.EUROPE_PARIS ) // Custom user agent val chromeScreenshot = avatars.getScreenshot( url = "https://example.com", userAgent = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36" ) // Touch device val touchScreenshot = avatars.getScreenshot( url = "https://example.com", touch = true ) ``` {% /multicode %} ### Geolocation simulation {% #geolocation-simulation %} Simulate different geographic locations to see how web pages render with location-based content and features. {% multicode %} ```client-web import { Client, Avatars, Timezone } from "appwrite"; // New York location const nyScreenshot = avatars.getScreenshot({ url: 'https://example.com', permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, accuracy: 100, timezone: Timezone.AmericaNewYork }); // London location const londonScreenshot = avatars.getScreenshot({ url: 'https://example.com', permissions: ["geolocation"], latitude: 51.5074, longitude: -0.1278, accuracy: 100, timezone: Timezone.EuropeLondon }); // Tokyo location const tokyoScreenshot = avatars.getScreenshot({ url: 'https://example.com', permissions: ["geolocation"], latitude: 35.6762, longitude: 139.6503, accuracy: 100, timezone: Timezone.AsiaTokyo }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // New York location Future nyScreenshot = avatars.getScreenshot( url: 'https://example.com', permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, accuracy: 100, timezone: Timezone.americaNewYork ); // London location Future londonScreenshot = avatars.getScreenshot( url: 'https://example.com', permissions: ["geolocation"], latitude: 51.5074, longitude: -0.1278, accuracy: 100, timezone: Timezone.europeLondon ); // Tokyo location Future tokyoScreenshot = avatars.getScreenshot( url: 'https://example.com', permissions: ["geolocation"], latitude: 35.6762, longitude: 139.6503, accuracy: 100, timezone: Timezone.asiaTokyo ); ``` ```client-apple import Appwrite // New York location let nyScreenshot = try await avatars.getScreenshot( url: "https://example.com", permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, accuracy: 100, timezone: Timezone.americaNewYork ) // London location let londonScreenshot = try await avatars.getScreenshot( url: "https://example.com", permissions: ["geolocation"], latitude: 51.5074, longitude: -0.1278, accuracy: 100, timezone: Timezone.europeLondon ) // Tokyo location let tokyoScreenshot = try await avatars.getScreenshot( url: "https://example.com", permissions: ["geolocation"], latitude: 35.6762, longitude: 139.6503, accuracy: 100, timezone: Timezone.asiaTokyo ) ``` ```client-android-kotlin import io.appwrite.services.Avatars import io.appwrite.enums.Timezone // New York location val nyScreenshot = avatars.getScreenshot( url = "https://example.com", permissions = listOf("geolocation"), latitude = 40.7128, longitude = -74.0060, accuracy = 100, timezone = Timezone.AMERICA_NEW_YORK ) // London location val londonScreenshot = avatars.getScreenshot( url = "https://example.com", permissions = listOf("geolocation"), latitude = 51.5074, longitude = -0.1278, accuracy = 100, timezone = Timezone.EUROPE_LONDON ) // Tokyo location val tokyoScreenshot = avatars.getScreenshot( url = "https://example.com", permissions = listOf("geolocation"), latitude = 35.6762, longitude = 139.6503, accuracy = 100, timezone = Timezone.ASIA_TOKYO ) ``` {% /multicode %} ### Wait time before capture {% #wait-time %} Use the `sleep` parameter to wait for a specified duration before capturing the screenshot. This is useful when pages need time to fully load dynamic content, animations, or async resources. {% multicode %} ```client-web // Wait for page to load fully const delayedScreenshot = avatars.getScreenshot({ url: 'https://example.com', sleep: 5 // Wait 5 seconds before capture }); // Immediate capture const instantScreenshot = avatars.getScreenshot({ url: 'https://example.com', sleep: 0 // No wait time }); ``` ```client-flutter // Wait for page to load fully Future delayedScreenshot = avatars.getScreenshot( url: 'https://example.com', sleep: 5 // Wait 5 seconds before capture ); // Immediate capture Future instantScreenshot = avatars.getScreenshot( url: 'https://example.com', sleep: 0 // No wait time ); ``` ```client-apple // Wait for page to load fully let delayedScreenshot = try await avatars.getScreenshot( url: "https://example.com", sleep: 5 // Wait 5 seconds before capture ) // Immediate capture let instantScreenshot = try await avatars.getScreenshot( url: "https://example.com", sleep: 0 // No wait time ) ``` ```client-android-kotlin // Wait for page to load fully val delayedScreenshot = avatars.getScreenshot( url = "https://example.com", sleep = 5 // Wait 5 seconds before capture ) // Immediate capture val instantScreenshot = avatars.getScreenshot( url = "https://example.com", sleep = 0 // No wait time ) ``` {% /multicode %} ### Browser permissions {% #browser-permissions %} Grant specific browser permissions to allow web pages to access features like geolocation, camera, microphone, and other APIs during screenshot capture. When you visit a web page that requires certain browser capabilities, it typically prompts users to grant permissions. With the `permissions` parameter, you can pre-grant these permissions to the browser session, allowing you to capture screenshots of pages that depend on these features without manual intervention. #### Available permissions The following permissions can be granted to the browser session: | Permission | Description | | ---------- | ----------- | | `geolocation` | Access device location coordinates | | `camera` | Access camera for video/photo capture | | `microphone` | Access microphone for audio recording | | `notifications` | Send browser notifications | | `midi` | Access MIDI devices for music applications | | `push` | Receive push notifications | | `clipboard-read` | Read from clipboard | | `clipboard-write` | Write to clipboard | | `payment-handler` | Handle payment requests | | `usb` | Access USB devices | | `bluetooth` | Access Bluetooth devices | | `accelerometer` | Access device accelerometer sensor | | `gyroscope` | Access device gyroscope sensor | | `magnetometer` | Access device magnetometer sensor | | `ambient-light-sensor` | Access ambient light sensor | | `background-sync` | Sync data in the background | | `persistent-storage` | Use persistent storage | | `screen-wake-lock` | Prevent screen from sleeping | | `web-share` | Use Web Share API | | `xr-spatial-tracking` | Track spatial positioning for XR/VR | By pre-granting permissions, you can capture screenshots of fully functional pages in their active state, rather than showing permission prompts or degraded experiences. {% multicode %} ```client-web import { Client, Avatars, Output } from "appwrite"; // Grant geolocation permission const geoScreenshot = avatars.getScreenshot({ url: 'https://example.com/map', permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, output: Output.Png }); // Grant multiple permissions const multiPermScreenshot = avatars.getScreenshot({ url: 'https://example.com/video-call', permissions: ["camera", "microphone", "notifications"], output: Output.Webp }); // Grant storage and sync permissions const storageScreenshot = avatars.getScreenshot({ url: 'https://example.com/offline-app', permissions: ["persistent-storage", "background-sync"], output: Output.Jpg }); // No special permissions const defaultScreenshot = avatars.getScreenshot({ url: 'https://example.com', permissions: [] }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // Grant geolocation permission Future geoScreenshot = avatars.getScreenshot( url: 'https://example.com/map', permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, output: Output.png ); // Grant multiple permissions Future multiPermScreenshot = avatars.getScreenshot( url: 'https://example.com/video-call', permissions: ["camera", "microphone", "notifications"], output: Output.webp ); // Grant storage and sync permissions Future storageScreenshot = avatars.getScreenshot( url: 'https://example.com/offline-app', permissions: ["persistent-storage", "background-sync"], output: Output.jpg ); // No special permissions Future defaultScreenshot = avatars.getScreenshot( url: 'https://example.com', permissions: [] ); ``` ```client-apple import Appwrite // Grant geolocation permission let geoScreenshot = try await avatars.getScreenshot( url: "https://example.com/map", permissions: ["geolocation"], latitude: 40.7128, longitude: -74.0060, output: Output.png ) // Grant multiple permissions let multiPermScreenshot = try await avatars.getScreenshot( url: "https://example.com/video-call", permissions: ["camera", "microphone", "notifications"], output: Output.webp ) // Grant storage and sync permissions let storageScreenshot = try await avatars.getScreenshot( url: "https://example.com/offline-app", permissions: ["persistent-storage", "background-sync"], output: Output.jpg ) // No special permissions let defaultScreenshot = try await avatars.getScreenshot( url: "https://example.com", permissions: [] ) ``` ```client-android-kotlin import io.appwrite.services.Avatars import io.appwrite.enums.Output // Grant geolocation permission val geoScreenshot = avatars.getScreenshot( url = "https://example.com/map", permissions = listOf("geolocation"), latitude = 40.7128, longitude = -74.0060, output = Output.PNG ) // Grant multiple permissions val multiPermScreenshot = avatars.getScreenshot( url = "https://example.com/video-call", permissions = listOf("camera", "microphone", "notifications"), output = Output.WEBP ) // Grant storage and sync permissions val storageScreenshot = avatars.getScreenshot( url = "https://example.com/offline-app", permissions = listOf("persistent-storage", "background-sync"), output = Output.JPG ) // No special permissions val defaultScreenshot = avatars.getScreenshot( url = "https://example.com", permissions = emptyList() ) ``` {% /multicode %} ### Use cases {% #use-cases %} Screenshots are commonly used for: - **Web documentation**: Automatically generate visual documentation of web applications and websites - **Link previews**: Display website previews in link cards and social sharing interfaces - **Performance testing**: Capture pages in different browser environments to verify responsive design - **Accessibility testing**: Generate screenshots with different viewport sizes and accessibility settings - **Localization testing**: Verify how pages render in different locales and timezones - **Browser compatibility**: Test how pages appear with different user agents and browser settings - **Content archival**: Create visual snapshots of web pages for archival and reference - **A/B testing**: Compare visual renderings of different page variants - **Automated reporting**: Generate visual reports and dashboards with live web page screenshots - **QA automation**: Verify visual consistency across different browser and device configurations --- ## Databases https://appwrite.io/docs/products/databases Appwrite Databases let you store and query structured data. Databases provide high-performance and scalable data storage for your key application, business, and user data. {% info title="Looking for file storage?" %} Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). {% /info %} You can organize data into databases, tables, and rows. You can also paginate, order, and query rows. For complex business logic, Appwrite supports relationships to help you model your data. {% arrow_link href="/docs/products/databases/quick-start" %} Quick start {% /arrow_link %} --- ## AI suggestions https://appwrite.io/docs/products/databases/ai-suggestions AI suggestions generate columns and indexes for your tables based on the table name, existing database structure, and optional context you provide. This feature analyzes your database to recommend appropriate schema designs that follow best practices. {% section #create-table step=1 title="Create table" %} Navigate to **Databases** in the Appwrite Console, select your database, and click **Create table**. Enter a descriptive table name. AI suggestions will use this name to generate relevant columns and indexes. {% /section %} {% section #enable-suggestions step=2 title="Enable AI suggestions" %} In the table creation dialog, enable **AI suggestions**. {% only_dark %} ![Enable AI suggestions](/images/docs/databases/dark/ai-suggestions-enable.png) {% /only_dark %} {% only_light %} ![Enable AI suggestions](/images/docs/databases/ai-suggestions-enable.png) {% /only_light %} Optionally, provide additional context about your use case to refine the suggestions. For example, if creating an `Orders` table, you might add context like "e-commerce orders with payment tracking." Click **Generate suggestions** to analyze your table name and database structure. {% /section %} {% section #review-suggestions step=3 title="Review suggestions" %} AI suggestions will generate recommended columns and indexes for your table. {% only_dark %} ![Review AI suggestions](/images/docs/databases/dark/ai-suggestions-review.png) {% /only_dark %} {% only_light %} ![Review AI suggestions](/images/docs/databases/ai-suggestions-review.png) {% /only_light %} Review each suggested column: - Column name and type - Whether it's required or optional - Default values - Array configurations Review suggested indexes to optimize query performance. {% /section %} {% section #apply-changes step=4 title="Apply or modify" %} You can modify any suggestion before applying: - Edit column names, types, or configurations - Remove suggestions you don't need - Add additional columns manually Click **Apply** to apply your schema. The table will be created with your approved columns and indexes. {% info title="Manual columns" %} You can always add, modify, or remove [columns](/docs/products/databases/tables#columns) after table creation by navigating to your table's **Columns** tab. {% /info %} {% /section %} --- ## Atomic numeric operations https://appwrite.io/docs/products/databases/atomic-numeric-operations Atomic numeric operations allow you to safely increase or decrease numeric fields without fetching the full row. This eliminates race conditions and reduces bandwidth usage when updating any numeric values that need to be modified atomically, such as counters, scores, balances, and other fast-moving numeric data. ### How atomic operations work {% #how-atomic-operations-work %} Instead of the traditional read-modify-write pattern, atomic numeric operations use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. **Traditional approach:** 1. Fetch row → `{ likes: 42 }` 2. Update client-side → `likes: 43` 3. Write back → `{ likes: 43 }` **Atomic approach:** 1. Call `incrementRowColumn()` with the column name and the value to increment by 2. Server applies atomically → `likes: 43` ### When to use atomic operations {% #when-to-use-atomic-operations %} Atomic numeric operations work well for: - **Social features**: Likes, follows, comment counts - **Usage metering**: API credits, storage quotas, request limits - **Game state**: Scores, lives, currency, experience points - **E-commerce**: Stock counts, inventory levels - **Workflow tracking**: Retry counts, progress indicators - **Rate limiting**: Request counters, usage tracking ### Perform atomic operations {% #perform-atomic-operations %} Use the `incrementRowColumn` and `decrementRowColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control. #### Increment a field {% #increment-field %} {% multicode %} ```client-web import { Client, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); const result = await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', // column value: 1 // value }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); final row = await tablesDB.incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1 ); ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) let row = try await tablesDB.incrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) val row = tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "likes", value = 1 ) ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', // column value: 1 // value }); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key tablesDB = TablesDB(client) result = tablesDB.increment_row_column( database_id = '', table_id = '', row_id = '', column = 'likes', # column value = 1 # value ) ``` ```graphql mutation { databasesIncrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1 ) { _id _tableId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} #### Decrement a field {% #decrement-field %} Use the `decrementRowColumn` method to decrease numeric fields: {% multicode %} ```client-web import { Client, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); const result = await tablesDB.decrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 5 // value }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); final row = await tablesDB.decrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'credits', value: 5 ); ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) let row = try await tablesDB.decrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "credits", value: 5 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) val row = tablesDB.decrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "credits", value = 5 ) ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.decrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 5 // value }); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key tablesDB = TablesDB(client) result = tablesDB.decrement_row_column( database_id = '', table_id = '', row_id = '', column = 'credits', # column value = 5 # value ) ``` ```graphql mutation { databasesDecrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "credits", value: 5 ) { _id _tableId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} ### Set constraints and bounds {% #set-constraints-and-bounds %} You can set minimum and maximum bounds for individual operations to prevent invalid values. Use the optional `min` and `max` parameters to ensure the final value stays within acceptable limits: #### Example with constraints {% #example-with-constraints %} {% multicode %} ```client-web // Increment with maximum constraint const result = await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 100, // value max: 1000 // max (optional) }); // Decrement with minimum constraint const result2 = await tablesDB.decrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 50, // value min: 0 // min (optional) }); ``` ```client-flutter // Increment with maximum constraint final row = await tablesDB.incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'credits', value: 100, max: 1000 ); // Decrement with minimum constraint final row2 = await tablesDB.decrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'credits', value: 50, min: 0 ); ``` ```client-apple // Increment with maximum constraint let row = try await tablesDB.incrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "credits", value: 100, max: 1000 ) // Decrement with minimum constraint let row2 = try await tablesDB.decrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "credits", value: 50, min: 0 ) ``` ```client-android-kotlin // Increment with maximum constraint val row = tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "credits", value = 100, max = 1000 ) // Decrement with minimum constraint val row2 = tablesDB.decrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "credits", value = 50, min = 0 ) ``` ```server-nodejs // Increment with maximum constraint const result = await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 100, // value max: 1000 // max (optional) }); // Decrement with minimum constraint const result2 = await tablesDB.decrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'credits', // column value: 50, // value min: 0 // min (optional) }); ``` ```server-python ### Increment with maximum constraint result = tablesDB.increment_row_column( database_id = '', table_id = '', row_id = '', column = 'credits', # column value = 100, # value max = 1000 # max (optional) ) ### Decrement with minimum constraint result2 = tablesDB.decrement_row_column( database_id = '', table_id = '', row_id = '', column = 'credits', # column value = 50, # value min = 0 # min (optional) ) ``` {% /multicode %} ### Follow best practices {% #follow-best-practices %} #### Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %} Atomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously. #### Combine with regular updates {% #combine-with-regular-updates %} For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls: {% multicode %} ```client-web // First, increment the likes atomically const likeResult = await tablesDB.incrementRowColumn( '', '', '', 'likes', // column 1 // value ); // Then, update other fields const updateResult = await tablesDB.updateRow( '', '', '', { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } ); ``` ```client-flutter // First, increment the likes atomically final likeResult = await tablesDB.incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1 ); // Then, update other fields final updateResult = await tablesDB.updateRow( databaseId: '', tableId: '', rowId: '', data: { 'lastLikedBy': userId, 'lastLikedAt': DateTime.now().toIso8601String() } ); ``` ```client-apple // First, increment the likes atomically let likeResult = try await tablesDB.incrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1 ) // Then, update other fields let updateResult = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "lastLikedBy": userId, "lastLikedAt": ISO8601DateFormatter().string(from: Date()) ] ) ``` ```client-android-kotlin // First, increment the likes atomically val likeResult = tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "likes", value = 1 ) // Then, update other fields val updateResult = tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "lastLikedBy" to userId, "lastLikedAt" to Instant.now().toString() ) ) ``` ```server-nodejs // First, increment the likes atomically const likeResult = await tablesDB.incrementRowColumn( '', '', '', 'likes', // column 1 // value ); // Then, update other fields const updateResult = await tablesDB.updateRow( '', '', '', { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } ); ``` ```server-python ### First, increment the likes atomically like_result = tablesDB.increment_row_column( database_id = '', table_id = '', row_id = '', column = 'likes', # column value = 1 # value ) ### Then, update other fields update_result = tablesDB.update_row( database_id = '', table_id = '', row_id = '', data = { 'lastLikedBy': user_id, 'lastLikedAt': datetime.now().isoformat() } ) ``` {% /multicode %} ### Use transactions {% #use-transactions %} Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit. {% multicode %} ```client-web await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' }); ``` ```client-flutter await tablesDB.incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' ); ``` ```client-apple try await tablesDB.incrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1, transactionId: "" ) ``` ```client-android-kotlin tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "likes", value = 1, transactionId = "" ) ``` ```client-android-java tablesDB.incrementRowColumn( "", "", "", "likes", 1, "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` ```client-react-native await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' }); ``` ```server-nodejs await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' }); ``` ```server-deno await tablesDB.incrementRowColumn({ databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' }); ``` ```server-python tablesDB.increment_row_column( database_id = '', table_id = '', row_id = '', column = 'likes', value = 1, transaction_id = '' ) ``` ```server-php $tablesDB->incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' ); ``` ```server-ruby tablesDB.increment_row_column( database_id: '', table_id: '', row_id: '', column: 'likes', value: 1, transaction_id: '' ) ``` ```server-dotnet await tablesDB.IncrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1, transactionId: "" ); ``` ```server-dart await tablesDB.incrementRowColumn( databaseId: '', tableId: '', rowId: '', column: 'likes', value: 1, transactionId: '' ); ``` ```server-swift try await tablesDB.incrementRowColumn( databaseId: "", tableId: "", rowId: "", column: "likes", value: 1, transactionId: "" ) ``` ```server-kotlin tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", column = "likes", value = 1, transactionId = "" ) ``` ```server-java tablesDB.incrementRowColumn( "", "", "", "likes", 1, "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` {% /multicode %} #### Explore related features - [Bulk operations](/docs/products/databases/bulk-operations) - Update multiple rows at once - [Permissions](/docs/products/databases/permissions) - Control access to rows - [Queries](/docs/products/databases/queries) - Find rows to update - [Relationships](/docs/products/databases/relationships) - Update related rows --- ## Backups https://appwrite.io/docs/products/databases/backups Appwrite Backups enable seamless, **encrypted** database backups on Cloud. All backups are **hot** backups, ensuring zero downtime and fast recovery. Learn how to efficiently back up your databases to ensure data security and smooth recovery. {% info title="Backups are available on Appwrite Cloud for all Pro, Scale, and Enterprise customers." %} {% /info %} Appwrite Backups allow you to automate database backups using backup policies, supporting pre-defined, custom retention & other options. You can also create manual backups whenever necessary. ### Backup policies {% #backup-policies %} Backup policies allow you to automate your backup process. The Scale and Enterprise plans allow for more customization and offer options like how often backups should occur, how long they should be retained, and when they should run. #### Creating a backup policy {% #creating-backup-policy %} To automate your database backups, you need to create backup policies that run at scheduled intervals. {% only_dark %} ![Create databases screen](/images/docs/databases/dark/databases.png) {% /only_dark %} {% only_light %} ![Create databases screen](/images/docs/databases/databases.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Databases** 2. Create or select & navigate to your database and click on the **Backups** Tab 3. Click on **Create Policies** & select a pre-defined policy\   * On a **Pro** plan, you get access to a Daily backup policy {% only_dark %} ![Pro plan policy](/images/docs/databases/dark/pro-policy.png) {% /only_dark %} {% only_light %} ![Pro plan policy](/images/docs/databases/pro-policy.png) {% /only_light %} * On **Scale** and **Enterprise** plans, you get access to more & custom policies\   * Select a pre-defined policy {% only_dark %} ![Scale plan policies](/images/docs/databases/dark/scale-policies.png) {% /only_dark %} {% only_light %} ![Scale plan policies](/images/docs/databases/scale-policies.png) {% /only_light %} * Or create a custom policy and adjust the settings as you like {% only_dark %} ![Custom policies for Scale plan](/images/docs/databases/dark/scale-custom-policies.png) {% /only_dark %} {% only_light %} ![Custom policies for Scale plan](/images/docs/databases/scale-custom-policies.png) {% /only_light %} 4. Click on **Create** Your database is now set up for automated backups with just a few clicks. Note that you can always navigate to the same tab and click **Create Manual** to create a backup on-demand. ### Manual backups {% #manual-backups %} You can always create an on-demand backup whenever necessary. {% only_dark %} ![Manual backup](/images/docs/databases/dark/manual-backup.png) {% /only_dark %} {% only_light %} ![Manual backup](/images/docs/databases/manual-backup.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Databases** 2. Select & navigate to your database and click on the **Backups** Tab 3. Click on **Manual Backup** Depending on the size of your database, the backup process may take some time to complete. You can monitor its progress via the floating status bar at the bottom of your screen. ### Restoring backups {% #restoring-backups %} To restore a database, you must have a backup of the database you want to restore. {% only_dark %} ![Restore databases](/images/docs/databases/dark/restore.png) {% /only_dark %} {% only_light %} ![Restore databases](/images/docs/databases/restore.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Databases** 2. Select & navigate to your database and click on the **Backups** Tab 3. Click on the options menu in the far corner of your backup 4. In the dropdown menu, click **Restore**. 5. Enter the new database name and an optional database ID 6. Click **Restore** Depending on the size of your database, the restoration process may take some time. You can observe its status in a floating bar across your project. ### Backup security & performance {% #backup-security-and-performance %} All backups created with Appwrite are: 1. **Encrypted**: All backups are securely encrypted to ensure your data remains protected at all times. 2. **Remotely stored**: Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable. 3. **Hot backups**: Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services. ### Best practices {% #best-practices %} To ensure your backups are robust and effective, consider the following best practices: 1. **Schedule regular backups**: Add multiple backup policies based on the frequency of database changes. Daily or weekly backups are often sufficient for most use cases. 2. **Retain critical backups longer**: Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed. 3. **Optimize backup policies based on data sensitivity**: Tailor your backup frequency and retention settings according to the sensitivity and importance of the data. Critical data may require more frequent backups, while less essential data can have longer retention and fewer backups. --- ## Bulk operations https://appwrite.io/docs/products/databases/bulk-operations Appwrite Databases supports bulk operations for rows, allowing you to create, update, or delete multiple rows in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. ### Atomic behavior {% #atomic-behavior %} Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all rows in your bulk request succeed, or all rows fail. This atomicity ensures: - **Data consistency**: Your database remains in a consistent state even if some operations would fail. - **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. - **Simplified error handling**: You only need to handle complete success or complete failure scenarios. For example, if you attempt to create 100 rows and one fails due to a validation error, none of the 100 rows will be created. ### Create rows {% #create-rows %} You can create multiple rows in a single request using the `createRows` method. {% info title="Custom timestamps" %} When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each document in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically. {% /info %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.createRows( '', '', [ { $id: sdk.ID.unique(), name: 'Row 1' }, { $id: sdk.ID.unique(), name: 'Row 2' } ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') tablesDB = TablesDB(client) result = tablesDB.create_rows( database_id = '', table_id = '', rows = [ { '$id': appwrite.ID.unique(), 'name': 'Row 1' }, { '$id': appwrite.ID.unique(), 'name': 'Row 2' } ] ) ``` {% /multicode %} ### Update rows {% #update-rows %} {% info title="Permissions required" %} You must grant **update** permissions to users at the **table level** before users can update rows. [Learn more about permissions](/docs/products/databases/permissions) {% /info %} You can update multiple rows in a single request using the `updateRows` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.updateRows( '', '', { status: 'published' }, [ sdk.Query.equal('status', 'draft') ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') tablesDB = TablesDB(client) result = tablesDB.update_rows( database_id = '', table_id = '', data = { 'status': 'published' }, queries = [ Query.equal('status', 'draft') ] ) ``` {% /multicode %} ### Upsert rows {% #upsert-rows %} {% info title="Permissions required" %} You must grant **create** and **update** permissions to users at the **table level** before users can create rows. [Learn more about permissions](/docs/products/databases/permissions) {% /info %} You can upsert multiple rows in a single request using the `upsertRows(` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.upsertRows( '', '', [ { $id: sdk.ID.unique(), name: 'New Row 1' }, { $id: 'row-id-2', // Existing row ID name: 'New Row 2' } ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.id import ID client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') tablesDB = TablesDB(client) result = tablesDB.upsert_rows( database_id = '', table_id = '', rows = [ { '$id': appwrite.ID.unique(), 'name': 'Row 1' }, { '$id': 'row-id-2', # Existing row ID 'name': 'New Row 2' } ] ) ``` {% /multicode %} ### Delete rows {% #delete-rows %} {% info title="Permissions required" %} You must grant **delete** permissions to users at the **table level** before users can delete rows. [Learn more about permissions](/docs/products/databases/permissions) {% /info %} You can delete multiple rows in a single request using the `deleteRows` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.deleteRows( '', '', [ sdk.Query.equal('status', 'archived') ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.query import Query client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') tablesDB = TablesDB(client) result = tablesDB.delete_rows( database_id = '', table_id = '', queries = [ Query.equal('status', 'archived') ] ) ``` {% /multicode %} {% info title="Queries for deletion" %} When deleting rows, you must specify queries to filter which rows to delete. If no queries are provided, all rows in the table will be deleted. [Learn more about queries](/docs/products/databases/queries). {% /info %} ### Use transactions {% #use-transactions %} All bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/transactions). {% multicode %} ```server-nodejs await tablesDB.createRows({ databaseId: '', tableId: '', rows: [ { $id: sdk.ID.unique(), name: 'One' }, { $id: sdk.ID.unique(), name: 'Two' } ], transactionId: '' }); ``` ```server-python tablesDB.create_rows( database_id = '', table_id = '', rows = [ { '$id': appwrite.ID.unique(), 'name': 'One' }, { '$id': appwrite.ID.unique(), 'name': 'Two' } ], transaction_id = '' ) ``` ```server-deno await tablesDB.createRows({ databaseId: '', tableId: '', rows: [ { $id: sdk.ID.unique(), name: 'One' }, { $id: sdk.ID.unique(), name: 'Two' } ], transactionId: '' }); ``` ```server-php $tablesDB->createRows( databaseId: '', tableId: '', rows: [ [ '$id' => ID::unique(), 'name' => 'One' ], [ '$id' => ID::unique(), 'name' => 'Two' ] ], transactionId: '' ); ``` ```server-ruby tablesDB.create_rows( database_id: '', table_id: '', rows: [ { '$id' => ID.unique(), 'name' => 'One' }, { '$id' => ID.unique(), 'name' => 'Two' } ], transaction_id: '' ) ``` ```server-dotnet await tablesDB.CreateRows( databaseId: "", tableId: "", rows: new List> { new Dictionary { ["$id"] = ID.Unique(), ["name"] = "One" }, new Dictionary { ["$id"] = ID.Unique(), ["name"] = "Two" } }, transactionId: "" ); ``` ```server-dart await tablesDB.createRows( databaseId: '', tableId: '', rows: [ { '\$id': ID.unique(), 'name': 'One' }, { '\$id': ID.unique(), 'name': 'Two' } ], transactionId: '' ); ``` ```server-swift try await tablesDB.createRows( databaseId: "", tableId: "", rows: [ ["$id": ID.unique(), "name": "One"], ["$id": ID.unique(), "name": "Two"] ], transactionId: "" ) ``` ```server-kotlin tablesDB.createRows( databaseId = "", tableId = "", rows = listOf( mapOf("\$id" to ID.unique(), "name" to "One"), mapOf("\$id" to ID.unique(), "name" to "Two") ), transactionId = "" ) ``` ```server-java tablesDB.createRows( "", "", Arrays.asList( Map.of( "$id", ID.unique(), "name", "One" ), Map.of( "$id", ID.unique(), "name", "Two" ) ), "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` {% /multicode %} --- ## CSV imports https://appwrite.io/docs/products/databases/csv-imports Appwrite's CSV Import feature allows you to create multiple rows in a table by uploading a single CSV file. This is especially useful for importing existing data, seeding test environments, or migrating from other systems. This feature is available in both Appwrite Cloud and the self-hosted version. ### Prepare your table {% #prepare-table %} To get started, create a table in your database and define its columns. Your CSV file must match the structure of this table. All required columns must be present in the CSV. Each row is validated before being imported. Each column in the CSV should map to a column key in your table, and each row should represent a new row. {% info title="Good to know" %} You can optionally include the `$id` column to define custom row IDs. If not provided, Appwrite will generate unique IDs for each row automatically. Appwrite imports rows in batches of 100 rows at a time. If a provided ID already exists in the table, the entire batch containing that row will fail, but rows in other batches will continue to be imported successfully. {% /info %} An example of a valid CSV file for a table with the following columns: - `title` (string) - `author` (string) - `year` (integer) - `available` (boolean) ```text $id,title,author,year,available f3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true mz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,true x0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,false ``` ### Empty values {% #empty-values %} Different column types handle empty values differently: - **String columns:** Empty values are interpreted as empty strings. To add null values to a string column, use the value `null` without quotes. - **Integer columns:** Empty values are interpreted as `null`. - **Boolean columns:** Empty values are interpreted as `null`. - **Array of any type:** Empty values are interpreted as empty arrays. ### Create and update timestamps {% #created-at-and-updated-at %} You can also optionally include `$createdAt` and `$updatedAt` columns to set custom timestamps for imported rows. If omitted, Appwrite sets these automatically during import. An example of a valid CSV file with `$createdAt` and `$updatedAt` timestamps: ```text $id,$createdAt,$updatedAt,title,author,year,available f3k91x8b2q,2025-08-10T12:34:56.000Z,2025-08-10T12:34:56.000Z,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true mz7lq3dp5c,2025-08-11T09:15:00.000Z,2025-08-11T10:00:00.000Z,The Fellowship of the Ring,J.R.R. Tolkien,1954,true x0v4p8ncq2,2025-08-12T08:00:00.000Z,2025-08-12T08:30:00.000Z,To Kill a Mockingbird,Harper Lee,1960,false ``` {% info title="Timestamps format" %} `$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`. {% /info %} ### Arrays {% #arrays %} You can also include data for array columns in your CSV file. For any column configured as an array, you can include a comma-separated list of values within double quotes (`"one,two"`). An example of a valid CSV file with an array column: ```text $id,title,author,year,available,categories f3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true,"fiction,fantasy" mz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,true,"fiction,fantasy" x0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,false,"fiction,nonfiction" ``` ### Relationships {% #relationships %} If you want to create relationships between rows in different tables, you can provide the `$id` of the related row in the related table. An example of a valid CSV file with relationships between tables where `related_id` is the `$id` of the related row in the related table: ```text $id,title,author,year,related_id f3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,id_1 mz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,id_2 x0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,id_3 ``` ### Permissions {% #permissions %} You can set permissions for rows in your CSV file by adding data for the `$permissions` column. Make sure row-level security is enabled for your table. An example of a valid permissions string: ```text "read(""any""),update(""users""),delete(""user:user_id"")" ``` The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles). A full example of a valid CSV file with row permissions: ```text name,$id,$permissions blog-post-1,post-1,"read(""any""),update(""user:user_id""),delete(""user:user_id"")" blog-post-2,post-2,"read(""guests""),update(""user:user_id"")" blog-post-3,post-3,"read(""users""),update(""user:user_id""),delete(""user:user_id"")" blog-post-4,post-4,"read(""any""),update(""team:team_id""),delete(""team:team_id"")" ``` ### Special characters {% #special-characters %} If your CSV file contains characters like double quotes (`"`) or commas (`,`), you need to escape them. #### Comma There are different ways to escape commas based on different scenarios. - If your column type is a **string**, you can enclose the value in double quotes (`"comma,used,here"`). - If your column type is a **string array**, you need to escape double quotes within the array to use a comma (`"one,two,""comma,allowed,here"""`). #### Double quotes If you want to add double quotes to a string column, you need to escape them by doubling them (`""`), otherwise it will be treated as an array. ### Import rows from the Console {% #import-console %} To import rows using the Appwrite Console: 1. Go to your project -> Databases 2. Navigate to your target Table 3. Click on the **Import CSV** button in the action area 4. Upload a new CSV file or choose an existing file from your Storage bucket {% only_dark %} ![CSV import screen](/images/docs/databases/dark/csv-import.png) {% /only_dark %} {% only_light %} ![CSV import screen](/images/docs/databases/csv-import.png) {% /only_light %} CSV imports run as background tasks. The Console displays a floating progress bar while the import is active. ### Additional resources {% #additional-resources %} - [Appwrite CLI](/docs/command-line) - [Database Permissions](/docs/products/databases/permissions) - [Database API Reference](/docs/references/cloud/client-web/databases) --- ## Databases https://appwrite.io/docs/products/databases/databases Databases are the largest organizational unit in Appwrite. Each database contains a group of [tables](/docs/products/databases/tables). In future versions, different databases may be backed by a different database technology of your choosing. ### Create in Console {% #create-in-console %} The easiest way to create a database using the Appwrite Console. You can create a database by navigating to the **Databases** page and clicking **Create database**. ### Create using Server SDKs {% #create-using-server-sdks %} You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const tablesDB = new sdk.TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = tablesDB.create({ databaseId: '', name: '[NAME]' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let tablesDB = new sdk.TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; let promise = tablesDB.create({ databaseId: '', name: '[NAME]' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $tablesDB = new TablesDB($client); $result = $tablesDB->create('', ''); ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) tablesDB = TablesDB(client) result = tablesDB.create('', '') ``` ```ruby require 'Appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key tablesDB = TablesDB.new(client) response = tablesDB.create(database_id: '', name: '') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var tablesDB = new TablesDB(client); Database result = await tablesDB.Create( databaseId: "", name: ""); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); TablesDB tablesDB = TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = tablesDB.create( databaseId: '', name: '', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val tablesDB = TablesDB(client) val response = tablesDB.create( databaseId = "", name = "", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key TablesDB tablesDB = new TablesDB(client); tablesDB.create( "", "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let tablesDB = TablesDB(client) let response = try await tablesDB.create( databaseId: "", name: "" ) ``` {% /multicode %} --- ## Geo queries https://appwrite.io/docs/products/databases/geo-queries Geo queries let you perform location-based operations on geographic data stored in your database. Find nearby locations, check if coordinates fall within boundaries, calculate distances between points, and more. Appwrite supports geo queries through spatial columns that store coordinates, shapes, and areas as first-class data types. {% info title="Also called spatial queries" %} In database terminology, these could also be known as **spatial queries**. {% /info %} Coordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees. ### Use cases {% #use-cases %} Use geo queries for location-based features: - **Search nearby**: Find all bus stops within 200 meters of a location - **Geofencing**: Check if delivery vehicles enter or exit designated zones - **Routing coverage**: Determine service areas and delivery radiuses - **Region lookups**: Match addresses to administrative boundaries - **Asset tracking**: Monitor equipment locations and movements - **Compliance zones**: Verify operations within permitted areas ### Spatial columns {% #spatial-columns %} Appwrite provides first-class geo types that can be stored as table columns: - **Point**: Represents a single geographic coordinate as `[longitude, latitude]` - **Line**: Represents a series of connected geographic coordinates forming a path or route - **Polygon**: Represents a closed area defined by a series of coordinates forming a boundary and optional hole punches within that boundary. Polygons must be **closed** (first and last points are the same), and holes must lie completely inside the boundary, with no overlaps. ### Spatial index {% #spatial-index %} For optimal performance, create spatial indexes on columns you'll query. Spatial indexes use optimized data structures designed for geographic operations. Spatial indexes are **strongly recommended** if you plan to query your spatial data in any way. Create spatial indexes through the [Appwrite Console](/docs/products/databases/tables#indexes), [Server SDK](/docs/sdks#server), or [CLI](/docs/tooling/command-line/tables#commands). ### Query operations {% #geo-queries %} Appwrite supports these geo query operations: - **Distance queries**: Find locations within, beyond, or exactly at a specified distance - **Geometric relationships**: Check if shapes intersect, overlap, touch, or cross each other - **Boundary queries**: Determine if points fall within defined areas For complete documentation and examples of all geo query operations, see [Queries](/docs/products/databases/queries#geo-queries). --- ## Atomic numeric operations https://appwrite.io/docs/products/databases/legacy/atomic-numeric-operations Atomic numeric operations allow you to safely increase or decrease numeric fields without fetching the full document. This eliminates race conditions and reduces bandwidth usage when updating any numeric values that need to be modified atomically, such as counters, scores, balances, and other fast-moving numeric data. ### How atomic operations work {% #how-atomic-operations-work %} Instead of the traditional read-modify-write pattern, atomic numeric operations use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. **Traditional approach:** 1. Fetch document → `{ likes: 42 }` 2. Update client-side → `likes: 43` 3. Write back → `{ likes: 43 }` **Atomic approach:** 1. Call `incrementDocumentColumn()` with the column name and the value to increment by 2. Server applies atomically → `likes: 43` ### When to use atomic operations {% #when-to-use-atomic-operations %} Atomic numeric operations work well for: - **Social features**: Likes, follows, comment counts - **Usage metering**: API credits, storage quotas, request limits - **Game state**: Scores, lives, currency, experience points - **E-commerce**: Stock counts, inventory levels - **Workflow tracking**: Retry counts, progress indicators - **Rate limiting**: Request counters, usage tracking ### Perform atomic operations {% #perform-atomic-operations %} Use the `incrementDocumentColumn` and `decrementDocumentColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control. #### Increment a field {% #increment-field %} {% multicode %} ```client-web import { Client, Databases } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); const result = await databases.incrementDocumentColumn( '', '', '', 'likes', // column 1 // value ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); final document = await databases.incrementDocumentColumn( databaseId: '', collectionId: '', documentId: '', column: 'likes', value: 1 ); ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) let document = try await databases.incrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "likes", value: 1 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Databases val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) val document = databases.incrementDocumentColumn( databaseId = "", collectionId = "", documentId = "", column = "likes", value = 1 ) ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key const databases = new sdk.Databases(client); const result = await databases.incrementDocumentColumn( '', '', '', 'likes', // column 1 // value ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key databases = Databases(client) result = databases.increment_document_column( database_id = '', collection_id = '', document_id = '', column = 'likes', # column value = 1 # value ) ``` ```graphql mutation { databasesIncrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "likes", value: 1 ) { _id _collectionId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} #### Decrement a field {% #decrement-field %} Use the `decrementDocumentColumn` method to decrease numeric fields: {% multicode %} ```client-web import { Client, Databases } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); const result = await databases.decrementDocumentColumn( '', '', '', 'credits', // column 5 // value ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); final document = await databases.decrementDocumentColumn( databaseId: '', collectionId: '', documentId: '', column: 'credits', value: 5 ); ``` ```client-apple import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) let document = try await databases.decrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "credits", value: 5 ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Databases val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) val document = databases.decrementDocumentColumn( databaseId = "", collectionId = "", documentId = "", column = "credits", value = 5 ) ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key const databases = new sdk.Databases(client); const result = await databases.decrementDocumentColumn( '', '', '', 'credits', // column 5 // value ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key databases = Databases(client) result = databases.decrement_document_column( database_id = '', collection_id = '', document_id = '', column = 'credits', # column value = 5 # value ) ``` ```graphql mutation { databasesDecrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "credits", value: 5 ) { _id _collectionId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} ### Set constraints and bounds {% #set-constraints-and-bounds %} You can set minimum and maximum bounds for individual operations to prevent invalid values. Use the optional `min` and `max` parameters to ensure the final value stays within accepcollection limits: #### Example with constraints {% #example-with-constraints %} {% multicode %} ```client-web // Increment with maximum constraint const result = await databases.incrementDocumentColumn( '', '', '', 'credits', // column 100, // value 1000 // max (optional) ); // Decrement with minimum constraint const result2 = await databases.decrementDocumentColumn( '', '', '', 'credits', // column 50, // value 0 // min (optional) ); ``` ```client-flutter // Increment with maximum constraint final document = await databases.incrementDocumentColumn( databaseId: '', collectionId: '', documentId: '', column: 'credits', value: 100, max: 1000 ); // Decrement with minimum constraint final document2 = await databases.decrementDocumentColumn( databaseId: '', collectionId: '', documentId: '', column: 'credits', value: 50, min: 0 ); ``` ```client-apple // Increment with maximum constraint let document = try await databases.incrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "credits", value: 100, max: 1000 ) // Decrement with minimum constraint let document2 = try await databases.decrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "credits", value: 50, min: 0 ) ``` ```client-android-kotlin // Increment with maximum constraint val document = databases.incrementDocumentColumn( databaseId = "", collectionId = "", documentId = "", column = "credits", value = 100, max = 1000 ) // Decrement with minimum constraint val document2 = databases.decrementDocumentColumn( databaseId = "", collectionId = "", documentId = "", column = "credits", value = 50, min = 0 ) ``` ```server-nodejs // Increment with maximum constraint const result = await databases.incrementDocumentColumn( '', '', '', 'credits', // column 100, // value 1000 // max (optional) ); // Decrement with minimum constraint const result2 = await databases.decrementDocumentColumn( '', '', '', 'credits', // column 50, // value 0 // min (optional) ); ``` ```server-python ### Increment with maximum constraint result = databases.increment_document_column( database_id = '', collection_id = '', document_id = '', column = 'credits', # column value = 100, # value max = 1000 # max (optional) ) ### Decrement with minimum constraint result2 = databases.decrement_document_column( database_id = '', collection_id = '', document_id = '', column = 'credits', # column value = 50, # value min = 0 # min (optional) ) ``` {% /multicode %} ### Follow best practices {% #follow-best-practices %} #### Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %} Atomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously. #### Combine with regular updates {% #combine-with-regular-updates %} For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls: {% multicode %} ```client-web // First, increment the likes atomically const likeResult = await databases.incrementDocumentColumn( '', '', '', 'likes', // column 1 // value ); // Then, update other fields const updateResult = await databases.updateDocument( '', '', '', { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } ); ``` ```client-flutter // First, increment the likes atomically final likeResult = await databases.incrementDocumentColumn( databaseId: '', collectionId: '', documentId: '', column: 'likes', value: 1 ); // Then, update other fields final updateResult = await databases.updateDocument( databaseId: '', collectionId: '', documentId: '', data: { 'lastLikedBy': userId, 'lastLikedAt': DateTime.now().toIso8601String() } ); ``` ```client-apple // First, increment the likes atomically let likeResult = try await databases.incrementDocumentColumn( databaseId: "", collectionId: "", documentId: "", column: "likes", value: 1 ) // Then, update other fields let updateResult = try await databases.updateDocument( databaseId: "", collectionId: "", documentId: "", data: [ "lastLikedBy": userId, "lastLikedAt": ISO8601DateFormatter().string(from: Date()) ] ) ``` ```client-android-kotlin // First, increment the likes atomically val likeResult = databases.incrementDocumentColumn( databaseId = "", collectionId = "", documentId = "", column = "likes", value = 1 ) // Then, update other fields val updateResult = databases.updateDocument( databaseId = "", collectionId = "", documentId = "", data = mapOf( "lastLikedBy" to userId, "lastLikedAt" to Instant.now().toString() ) ) ``` ```server-nodejs // First, increment the likes atomically const likeResult = await databases.incrementDocumentColumn( '', '', '', 'likes', // column 1 // value ); // Then, update other fields const updateResult = await databases.updateDocument( '', '', '', { lastLikedBy: userId, lastLikedAt: new Date().toISOString() } ); ``` ```server-python ### First, increment the likes atomically like_result = databases.increment_document_column( database_id = '', collection_id = '', document_id = '', column = 'likes', # column value = 1 # value ) ### Then, update other fields update_result = databases.update_document( database_id = '', collection_id = '', document_id = '', data = { 'lastLikedBy': user_id, 'lastLikedAt': datetime.now().isoformat() } ) ``` {% /multicode %} #### Explore related features - [Bulk operations](/docs/products/databases/legacy/bulk-operations) - Update multiple documents at once - [Permissions](/docs/products/databases/legacy/permissions) - Control access to documents - [Queries](/docs/products/databases/legacy/queries) - Find documents to update - [Relationships](/docs/products/databases/legacy/relationships) - Update related documents --- ## Bulk operations https://appwrite.io/docs/products/databases/legacy/bulk-operations Appwrite Databases supports bulk operations for documents, allowing you to create, update, or delete multiple documents in a single request. This can significantly improve performance for apps as it allows you to reduce the number of API calls needed while working with large data sets. Bulk operations can only be performed via the server-side SDKs. The client-side SDKs do not support bulk operations by design to prevent abuse and protect against unexpected costs. This ensures that only trusted server environments can perform large-scale data operations. For client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation. ### Atomic behavior {% #atomic-behavior %} Bulk operations in Appwrite are **atomic**, meaning they follow an all-or-nothing approach. Either all documents in your bulk request succeed, or all documents fail. This atomicity ensures: - **Data consistency**: Your database remains in a consistent state even if some operations would fail. - **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously. - **Simplified error handling**: You only need to handle complete success or complete failure scenarios. For example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created. ### Create documents {% #create-documents %} You can create multiple documents in a single request using the `createDocuments` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const databases = new sdk.Databases(client); const result = await databases.createDocuments( '', '', [ { $id: sdk.ID.unique(), name: 'Document 1' }, { $id: sdk.ID.unique(), name: 'Document 2' } ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases from appwrite.id import ID client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') databases = Databases(client) result = databases.create_documents( database_id = '', collection_id = '', documents = [ { '$id': ID.unique(), 'name': 'Document 1' }, { '$id': ID.unique(), 'name': 'Document 2' } ] ) ``` {% /multicode %} ### Update documents {% #update-documents %} {% info title="Permissions required" %} You must grant **update** permissions to users at the **collection level** before users can update documents. [Learn more about permissions](/docs/products/databases/legacy/permissions) {% /info %} You can update multiple documents in a single request using the `updateDocuments` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const databases = new sdk.Databases(client); const result = await databases.updateDocuments( '', '', { status: 'published' }, [ sdk.Query.equal('status', 'draft') ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases from appwrite.query import Query client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') databases = Databases(client) result = databases.update_documents( database_id = '', collection_id = '', data = { 'status': 'published' }, queries = [ Query.equal('status', 'draft') ] ) ``` {% /multicode %} ### Upsert documents {% #upsert-documents %} {% info title="Permissions required" %} You must grant **create** and **update** permissions to users at the **collection level** before users can create documents. [Learn more about permissions](/docs/products/databases/legacy/permissions) {% /info %} You can upsert multiple documents in a single request using the `upsertDocuments` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const databases = new sdk.Databases(client); const result = await databases.upsertDocuments( '', '', [ { $id: sdk.ID.unique(), name: 'New Document 1' }, { $id: 'document-id-2', // Existing document ID name: 'New Document 2' } ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases from appwrite.id import ID client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') databases = Databases(client) result = databases.upsert_documents( database_id = '', collection_id = '', documents = [ { '$id': ID.unique(), 'name': 'New Document 1' }, { '$id': 'document-id-2', # Existing document ID 'name': 'New Document 2' } ] ) ``` {% /multicode %} ### Delete documents {% #delete-documents %} {% info title="Permissions required" %} You must grant **delete** permissions to users at the **collection level** before users can delete documents. [Learn more about permissions](/docs/products/databases/legacy/permissions) {% /info %} You can delete multiple documents in a single request using the `deleteDocuments` method. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const databases = new sdk.Databases(client); const result = await databases.deleteDocuments( '', '', [ sdk.Query.equal('status', 'archived') ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases from appwrite.query import Query client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') databases = Databases(client) result = databases.delete_documents( database_id = '', collection_id = '', queries = [ Query.equal('status', 'archived') ] ) ``` {% /multicode %} {% info title="Queries for deletion" %} When deleting documents, you must specify queries to filter which documents to delete. If no queries are provided, all documents in the collection will be deleted. [Learn more about queries](/docs/products/databases/legacy/queries). {% /info %} --- ## Collections https://appwrite.io/docs/products/databases/legacy/collections Appwrite uses collections as containers of documents. Each collection contains many documents identical in structure. The terms collections and documents are used because the Appwrite JSON REST API resembles the API of a traditional NoSQL database, making it intuitive and user-friendly, even though Appwrite uses SQL under the hood. That said, Appwrite is designed to support both SQL and NoSQL database adapters like MariaDB, MySQL, or MongoDB in future versions. ### Create collection {% #create-collection %} You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). {% tabs %} {% tabsitem #console title="Console" %} You can create a collection by heading to the **Databases** page, navigate to a [database](/docs/products/databases/legacy/databases), and click **Create collection**. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const databases = new sdk.Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = databases.createCollection({ databaseId: '', collectionId: '[COLLECTION_ID]', name: '[NAME]' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let databases = new sdk.Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; let promise = databases.createCollection({ databaseId: '', collectionId: '[COLLECTION_ID]', name: '[NAME]' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $databases = new Databases($client); $result = $databases->createCollection('', '', ''); ``` ```python from appwrite.client import Client from appwrite.services.databases import Databases client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) databases = Databases(client) result = databases.create_collection('', '', '') ``` ```ruby require 'Appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key databases = Databases.new(client) response = databases.create_collection(database_id: '', collection_id: '', name: '') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var databases = new Databases(client); Collection result = await databases.CreateCollection( databaseId: "", collectionId: "", name: "[NAME]"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Databases databases = Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = databases.createCollection( databaseId: '', collectionId: '', name: '', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Databases val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val databases = Databases(client) val response = databases.createCollection( databaseId = "", collectionId = "", name = "[NAME]", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Databases databases = new Databases(client); databases.createCollection( "", "", "[NAME]", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let databases = Databases(client) let collection = try await databases.createCollection( databaseId: "", collectionId: "", name: "[NAME]" ) ``` {% /multicode %} You can also configure **permissions** in the `createCollection` method, learn more about the `createCollection` in the [API references](/docs/references). {% /tabsitem %} {% tabsitem #cli title="CLI" %} {% partial file="cli-disclaimer.md" /%} To create your collection using the CLI, first use the `appwrite init collections` command to initialize your collection. ```sh appwrite init collections ``` Then push your collection using the `appwrite push collections` command. ```sh appwrite push collections ``` This will create your collection in the Console with all of your `appwrite.config.json` configurations. {% arrow_link href="/docs/tooling/command-line/collections#commands" %} Learn more about the CLI collections commands {% /arrow_link %} {% /tabsitem %} {% /tabs %} ### Permissions {% #permissions %} Appwrite uses permissions to control data access. For security, only users that are granted permissions can access a resource. This helps prevent accidental data leaks by forcing you to make more concious decisions around permissions. By default, Appwrite doesn't grant permissions to any users when a new collection is created. This means users can't create new documents or read, update, and delete existing documents. [Learn about configuring permissions](/docs/products/databases/legacy/permissions). ### Attributes {% #attributes %} All documents in a collection follow the same structure. Attributes are used to define the structure of your documents and help the Appwrite's API validate your users' input. Add your first attribute by clicking the **Add attribute** button. You can choose between the following types. | Attribute | Description | |--------------|------------------------------------------------------------------| | `string` | String attribute. | | `integer` | Integer attribute. | | `float` | Float attribute. | | `boolean` | Boolean attribute. | | `datetime` | Datetime attribute formatted as an ISO 8601 string. | | `enum` | Enum attribute. | | `ip` | IP address attribute for IPv4 and IPv6. | | `email` | Email address attribute. | | `url` | URL attribute. | | `relationship` | Relationship attribute relates one collection to another. [Learn more about relationships.](/docs/products/databases/legacy/relationships) | If an attribute must be populated in all documents, set it as `required`. If not, you may optionally set a default value. Additionally, decide if the attribute should be a single value or an array of values. If needed, you can change an attribute's key, default value, size (for strings), and whether it is required or not after creation. You can increase a string attribute's size without any restrictions. When decreasing size, you must ensure that your existing data is less than or equal to the new size, or the operation will fail. ### Indexes {% #indexes %} Databases use indexes to quickly locate data without having to search through every document for matches. To ensure the best performance, Appwrite recommends an index for every attribute queried. If you plan to query multiple attributes in a single query, creating an index with **all** queried attributes will yield optimal performance. The following indexes are currently supported: | Type | Description | |------------|--------------------------------------------------------------------------------------------------------------| | `key` | Plain Index to allow queries. | | `unique` | Unique Index to disallow duplicates. | | `fulltext` | For searching within string attributes. Required for the [search query method](/docs/products/databases/legacy/queries#query-class). | You can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). --- ## Databases https://appwrite.io/docs/products/databases/legacy/databases Databases are the largest organizational unit in Appwrite. Each database contains a group of [collections](/docs/products/databases/legacy/collections). In future versions, different databases may be backed by a different database technology of your choosing. ### Create in Console {% #create-in-console %} The easiest way to create a database using the Appwrite Console. You can create a database by navigating to the **Databases** page and clicking **Create database**. ### Create using Server SDKs {% #create-using-server-sdks %} You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const databases = new sdk.Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = databases.create('', ''); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let databases = new sdk.Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; let promise = databases.create('', ''); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $databases = new Databases($client); $result = $databases->create('', ''); ``` ```python from appwrite.client import Client from appwrite.services.databases import Databases client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) databases = Databases(client) result = databases.create('', '') ``` ```ruby require 'Appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key databases = Databases.new(client) response = databases.create(database_id: '', name: '') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var databases = new Databases(client); Database result = await databases.Create( databaseId: "", name: ""); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Databases databases = Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = databases.create( databaseId: '', name: '', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Databases val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val databases = Databases(client) val response = databases.create( databaseId = "", name = "", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Databases databases = new Databases(client); databases.create( "", "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let databases = Databases(client) let response = try await databases.create( databaseId: "", name: "" ) ``` {% /multicode %} --- ## Documents https://appwrite.io/docs/products/databases/legacy/documents Each piece of data or information in Appwrite Databases is a document. Documents have a structure defined by the parent collection. ### Create documents {% #create-documents %} {% info title="Permissions required" %} You must grant **create** permissions to users at the **collection level** before users can create documents. [Learn more about permissions](#permissions) {% /info %} In most use cases, you will create documents programmatically. {% multicode %} ```client-web import { Client, Databases, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); const promise = databases.createDocument( '', '', ID.unique(), {} ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final document = databases.createDocument( databaseId: '', collectionId: '', documentId: ID.unique(), data: {} ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let document = try await databases.createDocument( databaseId: "", collectionId: "", documentId: ID.unique(), data: [:] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val document = databases.createDocument( databaseId = "", collectionId = "", documentId = ID.unique(), data = mapOf("a" to "b"), ) } catch (e: Exception) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql mutation { databasesCreateDocument( databaseId: "", collectionId: "", documentId: "", data: "{}" ) { _id _collectionId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} During testing, you might prefer to create documents in the Appwrite Console. To do so, navigate to the **Documents** tab of your collection and click the **Add document** button. ### List documents {% #list-documents %} {% info title="Permissions required" %} You must grant **read** permissions to users at the **collection level** before users can read documents. [Learn more about permissions](#permissions) {% /info %} Documents can be retrieved using the [List Document](/docs/references/cloud/client-web/databases#listDocuments) endpoint. Results can be filtered, sorted, and paginated using Appwrite's shared set of query methods. You can find a full guide on querying in the [Queries Guide](/docs/products/databases/legacy/queries). By default, results are limited to the **first 25 items**. You can change this through [pagination](/docs/products/databases/legacy/pagination). {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); const databases = new Databases(client); let promise = databases.listDocuments( "", "", [ Query.equal('title', 'Avatar') ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") final databases = Databases(client); try { final documents = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.equal('title', 'Avatar') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.equal("title", value: "Avatar") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = listOf( Query.equal("title", "Avatar") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql query { databasesListDocuments( databaseId: "", collectionId: "", queries: ["equal(\"title\", [\"Avatar\"])"] ) { total documents { _id data } } } ``` {% /multicode %} ### Upsert documents {% #upsert-documents %} {% info title="Permissions required" %} You must grant **create** and **update** permissions to users at the **collection level** before users can upsert documents. You can also grant **update** permissions at the document level instead. [Learn more about permissions](#permissions) {% /info %} In most use cases, you will upsert documents programmatically. {% multicode %} ```client-web import { Client, Databases, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); const promise = databases.upsertDocument( '', '', ID.unique(), {} ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final document = databases.upsertDocument( databaseId: '', collectionId: '', documentId: ID.unique(), data: {} ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let document = try await databases.upsertDocument( databaseId: "", collectionId: "", documentId: ID.unique(), data: [:] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val document = databases.upsertDocument( databaseId = "", collectionId = "", documentId = ID.unique(), data = mapOf("a" to "b"), ) } catch (e: Exception) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql mutation { databasesUpsertDocument( databaseId: "", collectionId: "", documentId: "", data: "{}" ) { _id _collectionId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} ### Permissions {% #permissions %} In Appwrite, permissions can be granted at the collection level and the document level. Before a user can create a document, you need to grant create permissions to the user. Read, update, and delete permissions can be granted at both the collection and document level. Users only need to be granted access at either the collection or document level to access documents. [Learn about configuring permissions](/docs/products/databases/legacy/permissions). ### Next steps {% #next-steps %} Continue learning with these related guides: {% cards %} {% cards_item href="/docs/products/databases/queries" title="Queries" %} Learn how to filter, sort, and search your documents with various query operators. {% /cards_item %} {% cards_item href="/docs/products/databases/pagination" title="Pagination" %} Handle large datasets by implementing pagination in your document queries. {% /cards_item %} {% cards_item href="/docs/products/databases/bulk-operations" title="Bulk operations" %} Perform create, update, and delete operations on multiple documents simultaneously. {% /cards_item %} {% cards_item href="/docs/products/databases/timestamp-overrides" title="Timestamp overrides" %} Set custom creation and update timestamps when migrating data or backdating records. {% /cards_item %} {% /cards %} [Learn more about bulk operations](/docs/products/databases/bulk-operations). --- ## Order https://appwrite.io/docs/products/databases/legacy/order You can order results returned by Appwrite Databases by using an order query. For best performance, create an [index](/docs/products/databases/legacy/collections#indexes) on the column you plan to order by. ### Ordering one column {% #one-column %} When querying using the [listDocuments](/docs/references/cloud/client-web/databases#listDocuments) endpoint, you can specify the order of the documents returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods. {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); databases.listDocuments( '', '', [ Query.orderAsc('title'), ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final documents = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.orderAsc('title') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); let databases = Databases(client) do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.orderAsc("title") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); val databases = Databases(client) try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.orderAsc("title") ] ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```graphql query { databasesListDocuments( databaseId: "", collectionId: "" queries: ["orderAsc(\"title\")"] ) { total documents { _id data } } } ``` {% /multicode %} ### Multiple columns {% #multiple-columns %} To sort based on multiple attributes, simply provide multiple query methods. For better performance, create an index on the first attribute that you order by. In the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order. {% multicode %} ```js // Web SDK code example for sorting based on multiple attributes // ... // List documents and sort based on multiple attributes databases.listDocuments( '', '', [ Query.orderAsc('title'), // Order first by title in ascending order Query.orderDesc('year'), // Then, order by year in descending order ] ); ``` ```dart // Flutter SDK code example for sorting based on multiple attributes // ... // List documents and sort based on multiple attributes try { final documents = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.orderAsc('title'), // Order by title in ascending order Query.orderDesc('year') // Order by year in descending order ] ); } on AppwriteException catch(e) { print(e); } ``` ```kotlin // Android SDK code example for sorting based on multiple attributes // ... // List documents and sort based on multiple attributes try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.orderAsc("title"), // Order by title in ascending order Query.orderDesc("year") // Order by year in descending order ] ); } catch (e: AppwriteException) { Log.e("Appwrite", e.message); } ``` ```swift // Apple SDK code example for sorting based on multiple attributes // ... // List documents and sort based on multiple attributes do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.orderAsc("title"), // Order by title in ascending order Query.orderDesc("year") // Order by year in descending order ] ); } catch { print(error.localizedDescription); } ``` ```graphql query { databasesListDocuments( databaseId: "", collectionId: "", queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"] ) { total documents { _id data } } } ``` {% /multicode %} ### Ordering by sequence {% #sequence-ordering %} For numeric ordering based on insertion order, you can use the `$sequence` field, which Appwrite automatically adds to all documents. This field increments with each new insert. {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); databases.listDocuments( '', '', [ Query.orderAsc('$sequence'), ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final documents = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.orderAsc('\$sequence') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.orderAsc("$sequence") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = listOf( Query.orderAsc("\$sequence") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```graphql query { databasesListDocuments( databaseId: "", collectionId: "" queries: ["orderAsc(\"$sequence\")"] ) { total documents { _id data } } } ``` {% /multicode %} The `$sequence` field is useful when you need: - Consistent ordering for pagination, especially with high-frequency inserts - Reliable insertion order tracking when timestamps might not be precise enough - Simple numeric ordering without managing custom counter fields --- ## Pagination https://appwrite.io/docs/products/databases/legacy/pagination As your database grows in size, you'll need to paginate results returned. Pagination improves performance by returning a subset of results that match a query at a time, called a page. By default, list operations return 25 items per page, which can be changed using the `Query.limit(25)` operator. There is no hard limit on the number of items you can request. However, beware that **large pages can degrade performance**. ### Offset pagination {% #offset-pagination %} Offset pagination works by dividing documents into `M` pages containing `N` documents. Every page is retrieved by skipping `offset = M * (N - 1)` items and reading the following `M` pages. Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. With `Query.limit()` you can define how many documents can be returned from one request. The `Query.offset()` is number of records you wish to skip before selecting records. {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); // Page 1 const page1 = await databases.listDocuments( '', '', [ Query.limit(25), Query.offset(0) ] ); // Page 2 const page2 = await databases.listDocuments( '', '', [ Query.limit(25), Query.offset(25) ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); final page1 = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.limit(25), Query.offset(0) ] ); final page2 = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.limit(25), Query.offset(25) ] ); } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) let page1 = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.limit(25), Query.offset(0) ] ) let page2 = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.limit(25), Query.offset(25) ] ) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) val page1 = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.limit(25), Query.offset(0) ] ) val page2 = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.limit(25), Query.offset(25) ] ) } ``` {% /multicode %} {% info title="Drawbacks" %} While traditional offset pagination is familiar, it comes with some drawbacks. The request gets slower as the number of records increases because the database has to read up to the offset number `M * (N - 1)` of rows to know where it should start selecting data. If the data changes frequently, offset pagination will also produce **missing and duplicate** results. {% /info %} ### Cursor pagination {% #cursor-pagination %} The cursor is a unique identifier for a document that points to where the next page should start. After reading a page of documents, pass the last document's ID into the `Query.cursorAfter(lastId)` query method to get the next page of documents. Pass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. {% multicode %} ```client-web import { Databases, Query } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); const databases = new Databases(client); // Page 1 const page1 = await databases.listDocuments( '', '', [ Query.limit(25), ] ); const lastId = page1.documents[page1.documents.length - 1].$id; // Page 2 const page2 = await databases.listDocuments( '', '', [ Query.limit(25), Query.cursorAfter(lastId), ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); final page1 = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.limit(25) ] ); final lastId = page1.documents[page1.documents.length - 1].$id; final page2 = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.limit(25), Query.cursorAfter(lastId) ] ); } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) let page1 = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.limit(25) ] ) let lastId = page1.documents[page1.documents.count - 1].$id let page2 = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.limit(25), Query.cursorAfter(lastId) ] ) } ``` ```client-android-kotlin import android.util.Log import io.appwrite.AppwriteException import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) val page1 = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.limit(25) ] ) val lastId = page1.documents[page1.documents.size - 1].$id val page2 = databases.listDocuments( databaseId = "", collectionId = "", queries = [ Query.limit(25), Query.cursorAfter(lastId) ] ) } ``` {% /multicode %} ### When to use what? {% #when-to-use %} Offset pagination should be used for collections that rarely change. Offset paginations allow you to create indicator of the current page number and total page number. For example, a list with up to 20 pages or static data like a list of countries or currencies. Using offset pagination on large collections and frequently updated collections may result in slow performance and **missing and duplicate** results. Cursor pagination should be used for frequently updated collections. It is best suited for lazy-loaded pages with infinite scrolling. For example, a feed, comment section, chat history, or high volume datasets. --- ## Database permissions https://appwrite.io/docs/products/databases/legacy/permissions Permissions define who can access documents in a collection. By default **no permissions** are granted to any users, so no user can access any documents. Permissions exist at two levels, collection level and document level permissions. In Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted. A user with access granted at either collection level or document level will be able to access a document. Users **don't need access at both levels** to access documents. ### Collection level {% #collection-level %} Collection level permissions apply to every document in the collection. If a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection. Configure collection level permissions by navigating to **Your collection** > **Settings** > **Permissions**. [Learn more about permissions and roles](/docs/advanced/platform/permissions) ### Document level {% #document-level %} Document level permissions grant access to individual documents. If a user has read, create, update, or delete permissions at the document level, the user can access the **individual document**. Document level permissions are only applied if Document Security is enabled in the settings of your collection. Enable document level permissions by navigating to **Your collection** > **Settings** > **Document security**. Document level permissions are configured in individual documents. [Learn more about permissions and roles](/docs/advanced/platform/permissions) ### Common use cases {% #common-use-cases %} For examples of how to implement common permission patterns, including creating private documents that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. --- ## Queries https://appwrite.io/docs/products/databases/legacy/queries Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. ### Query class {% #query-class %} Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. ### Building queries {% #building-queries %} Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. For example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`. {% info title="Default pagination behavior" %} By default, results are limited to the **first 25 items**. You can change this through [pagination](/docs/products/databases/legacy/pagination). {% /info %} {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); databases.listDocuments( '', '', [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greaterThan('year', 1999) ] ); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); const databases = new sdk.Databases(client); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey('') ; const promise = databases.listDocuments( '', '', [ sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']), sdk.Query.greaterThan('year', 1999) ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final documents = await databases.listDocuments( '', '', [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greaterThan('year', 1999) ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.equal("title", value: ["Avatar", "Lord of the Rings"]), Query.greaterThan("year", value: 1999) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); val databases = Databases(client) try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = listOf( Query.equal("title", listOf("Avatar", "Lord of the Rings")), Query.greaterThan("year", 1999) ) ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey('') ; $databases = new Databases($client); $result = $databases->listDocuments( '', '', [ Query::equal('title', ['Avatar', 'Lord of the Rings']), Query::greaterThan('year', 1999) ] ); ``` ```python from appwrite.client import Client from appwrite.query import Query from appwrite.services.databases import Databases client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') ) databases = Databases(client) result = databases.list_documents( '', '', [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greater_than('year', 1999) ] ) ``` ```graphql query { databasesListDocuments( databaseId: "", collectionId: "" queries: [ "{\"method\":\"equal\",\"attribute\":\"title\",\"values\":[\"Avatar\",\"Lord of the Rings\"]}", "{\"method\":\"greaterThan\",\"attribute\":\"year\",\"values\":[1999]}" ] ) { total documents { _id data } } } ``` ```http GET /v1/databases//collections//documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: ``` {% /multicode %} ### Query operators {% #query-operators %} #### Select {% #select %} The `select` operator allows you to specify which attributes should be returned from a document. This is essential for optimizing response size, controlling which relationship data loads, and only retrieving the data you need. {% multicode %} ```client-web Query.select(["name", "title"]) ``` ```client-flutter Query.select(["name", "title"]) ``` ```python Query.select(["name", "title"]) ``` ```ruby Query.select(["name", "title"]) ``` ```server-nodejs Query.select(["name", "title"]) ``` ```php Query::select(["name", "title"]) ``` ```swift Query.select(["name", "title"]) ``` ```http {"method":"select","values":["name","title"]} ``` {% /multicode %} ##### Select relationship data {% #relationship-select %} With [opt-in relationship loading](/docs/products/databases/legacy/relationships#performance-loading), you must explicitly select relationship data. This gives you fine-grained control over performance and payload size. ###### Get documents without relationships By default, documents return only their own fields: {% multicode %} ```client-web const doc = await databases.getDocument( '', '', '', [Query.select(['name', 'age'])] ); ``` ```client-flutter final doc = await databases.getDocument( databaseId: '', collectionId: '', documentId: '', queries: [Query.select(["name", "age"])] ); ``` ```python doc = databases.get_document( '', '', '', [Query.select(["name", "age"])] ) ``` ```ruby doc = databases.get_document( '', '', '', [Query.select(["name", "age"])] ) ``` ```server-nodejs const doc = await databases.getDocument( '', '', '', [Query.select(['name', 'age'])] ); ``` ```php $doc = $databases->getDocument( '', '', '', [Query::select(["name", "age"])] ); ``` ```swift let doc = try await databases.getDocument( databaseId: "", collectionId: "", documentId: "", queries: [Query.select(["name", "age"])] ) ``` ```http GET /v1/databases//collections//documents/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22name%22%2C%22age%22%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: ``` {% /multicode %} ###### Load all relationship data Use the `*` wildcard to load all fields from related documents: {% multicode %} ```client-web const doc = await databases.getDocument( '', '', '', [Query.select(['*', 'reviews.*'])] ); ``` ```client-flutter final doc = await databases.getDocument( databaseId: '', collectionId: '', documentId: '', queries: [Query.select(["*", "reviews.*"])] ); ``` ```python doc = databases.get_document( '', '', '', [Query.select(["*", "reviews.*"])] ) ``` ```ruby doc = databases.get_document( '', '', '', [Query.select(["*", "reviews.*"])] ) ``` ```server-nodejs const doc = await databases.getDocument( '', '', '', [Query.select(["*", "reviews.*"])] ); ``` ```php $doc = $databases->getDocument( '', '', '', [Query::select(["*", "reviews.*"])] ); ``` ```swift let doc = try await databases.getDocument( databaseId: "", collectionId: "", documentId: "", queries: [Query.select(["*", "reviews.*"])] ) ``` ```http GET /v1/databases//collections//documents/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22%2A%22%2C%22reviews.%2A%22%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: {"method":"select","values":["*","reviews.*"]} ``` {% /multicode %} ###### Select specific relationship fields For precise control, select only specific fields from related documents: {% multicode %} ```client-web const doc = await databases.getDocument( '', '', '', [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])] ); ``` ```client-flutter final doc = await databases.getDocument( databaseId: '', collectionId: '', documentId: '', queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ); ``` ```python doc = databases.get_document( '', '', '', [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```ruby doc = databases.get_document( '', '', '', [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```server-nodejs const doc = await databases.getDocument( '', '', '', [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ); // Result: { name: "John", age: 30, reviews: [{ author: "...", rating: 5 }] } ``` ```php $doc = $databases->getDocument( '', '', '', [Query::select(["name", "age", "reviews.author", "reviews.rating"])] ); ``` ```swift let doc = try await databases.getDocument( databaseId: "", collectionId: "", documentId: "", queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```http ### Load specific fields from main and related documents {"method":"select","values":["name","age","reviews.author","reviews.rating"]} ``` {% /multicode %} ###### Load nested relationships You can also load relationships of relationships: {% multicode %} ```client-web Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```client-flutter Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```python Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```ruby Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```server-nodejs Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```php Query::select(["*", "reviews.*", "reviews.author.*"]) ``` ```swift Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```http {"method":"select","values":["*","reviews.*","reviews.author.*"]} ``` {% /multicode %} ##### Use selection patterns {% #select-patterns %} | Pattern | Description | Use case | |---------|-------------|----------| | `["field1", "field2"]` | Specific attributes only | Minimize response size | | `["*"]` | All document attributes | Get complete document data | | `["*", "relationName.*"]` | Document + all relationship fields | Load document with complete related data | | `["field1", "relationName.field2"]` | Specific fields from document and relationships | Precise data loading | | `["*", "relationName.field1", "relationName.field2"]` | All document fields + specific relationship fields | Partial relationship loading | | `["relationName.*", "relationName.nestedRelation.*"]` | Nested relationship loading | Load relationships of relationships | ##### Optimize performance {% #select-performance %} **Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse. **Control relationship loading** - Related documents are not loaded by default. Use explicit selection to load only the relationships you need. **Reduce database load** - Selecting fewer fields reduces database processing time, especially for large documents. {% info title="Related documents" %} By default, relationship attributes contain only document IDs. To load the actual related document data, you must explicitly include relationship fields in your select query. Learn more about [relationship performance optimization](/docs/products/databases/legacy/relationships#performance-loading). {% /info %} #### Comparison operators {% #comparison %} ##### Equal {% #equal %} Returns document if attribute is equal to any value in the provided array. {% multicode %} ```client-web Query.equal("title", ["Iron Man"]) ``` ```client-flutter Query.equal("title", ["Iron Man"]) ``` ```python Query.equal("title", ["Iron Man"]) ``` ```ruby Query.equal("title", ["Iron Man"]) ``` ```server-nodejs Query.equal("title", ["Iron Man"]) ``` ```php Query::equal("title", ["Iron Man"]) ``` ```swift Query.equal("title", value: ["Iron Man"]) ``` ```http {"method":"equal","attribute":"title","values":["Iron Man"]} ``` {% /multicode %} ##### Not equal {% #not-equal %} Returns document if attribute is not equal to any value in the provided array. {% multicode %} ```client-web Query.notEqual("title", "Iron Man") ``` ```client-flutter Query.notEqual("title", "Iron Man") ``` ```python Query.not_equal("title", "Iron Man") ``` ```ruby Query.not_equal("title", "Iron Man") ``` ```server-nodejs Query.notEqual("title", "Iron Man") ``` ```php Query::notEqual("title", "Iron Man") ``` ```swift Query.notEqual("title", value: "Iron Man") ``` ```http {"method":"notEqual","attribute":"title","values":"Iron Man"} ``` {% /multicode %} ##### Less than {% #less-than %} Returns document if attribute is less than the provided value. {% multicode %} ```client-web Query.lessThan("score", 10) ``` ```client-flutter Query.lessThan("score", 10) ``` ```python Query.less_than("score", 10) ``` ```ruby Query.less_than("score", 10) ``` ```server-nodejs Query.lessThan("score", 10) ``` ```php Query::lessThan("score", 10) ``` ```swift Query.lessThan("score", value: 10) ``` ```http {"method":"lessThan","attribute":"score","values":[10]} ``` {% /multicode %} ##### Less than or equal {% #less-than-equal %} Returns document if attribute is less than or equal to the provided value. {% multicode %} ```client-web Query.lessThanEqual("score", 10) ``` ```client-flutter Query.lessThanEqual("score", 10) ``` ```python Query.less_than_equal("score", 10) ``` ```ruby Query.less_than_equal("score", 10) ``` ```server-nodejs Query.lessThanEqual("score", 10) ``` ```php Query::lessThanEqual("score", 10) ``` ```swift Query.lessThanEqual("score", value: 10) ``` ```http {"method":"lessThanEqual","attribute":"score","values":[10]} ``` {% /multicode %} ##### Greater than {% #greater-than %} Returns document if attribute is greater than the provided value. {% multicode %} ```client-web Query.greaterThan("score", 10) ``` ```client-flutter Query.greaterThan("score", 10) ``` ```python Query.greater_than("score", 10) ``` ```ruby Query.greater_than("score", 10) ``` ```server-nodejs Query.greaterThan("score", 10) ``` ```php Query::greaterThan("score", 10) ``` ```swift Query.greaterThan("score", value: 10) ``` ```http {"method":"greaterThan","attribute":"score","values":[10]} ``` {% /multicode %} ##### Greater than or equal {% #greater-than-equal %} Returns document if attribute is greater than or equal to the provided value. {% multicode %} ```client-web Query.greaterThanEqual("score", 10) ``` ```client-flutter Query.greaterThanEqual("score", 10) ``` ```python Query.greater_than_equal("score", 10) ``` ```ruby Query.greater_than_equal("score", 10) ``` ```server-nodejs Query.greaterThanEqual("score", 10) ``` ```php Query::greaterThanEqual("score", 10) ``` ```swift Query.greaterThanEqual("score", value: 10) ``` ```http {"method":"greaterThanEqual","attribute":"score","values":[10]} ``` {% /multicode %} ##### Between {% #between %} Returns document if attribute value falls between the two values. The boundary values are inclusive and can be strings or numbers. {% multicode %} ```client-web Query.between("price", 5, 10) ``` ```client-flutter Query.between("price", 5, 10) ``` ```python Query.between("price", 5, 10) ``` ```ruby Query.between("price", 5, 10) ``` ```server-nodejs Query.between("price", 5, 10) ``` ```php Query::between("price", 5, 10) ``` ```swift Query.between("price", start: 5, end: 10) ``` ```http {"method":"between","attribute":"price","values":[5,10]} ``` {% /multicode %} #### Null checks {% #null-checks %} ##### Is null {% #is-null %} Returns documents where attribute value is null. {% multicode %} ```client-web Query.isNull("name") ``` ```client-flutter Query.isNull("name") ``` ```python Query.is_null("name") ``` ```ruby Query.is_null("name") ``` ```server-nodejs Query.isNull("name") ``` ```php Query::isNull("name") ``` ```swift Query.isNull("name") ``` ```http {"method":"isNull","attribute":"name"} ``` {% /multicode %} ##### Is not null {% #is-not-null %} Returns documents where attribute value is **not** null. {% multicode %} ```client-web Query.isNotNull("name") ``` ```client-flutter Query.isNotNull("name") ``` ```python Query.is_not_null("name") ``` ```ruby Query.is_not_null("name") ``` ```server-nodejs Query.isNotNull("name") ``` ```php Query::isNotNull("name") ``` ```swift Query.isNotNull("name") ``` ```http {"method":"isNotNull","attribute":"name"} ``` {% /multicode %} #### String operations {% #string-operations %} ##### Starts with {% #starts-with %} Returns documents if a string attribute starts with a substring. {% multicode %} ```client-web Query.startsWith("name", "Once upon a time") ``` ```client-flutter Query.startsWith("name", "Once upon a time") ``` ```python Query.starts_with("name", "Once upon a time") ``` ```ruby Query.starts_with("name", "Once upon a time") ``` ```server-nodejs Query.startsWith("name", "Once upon a time") ``` ```php Query::startsWith("name", "Once upon a time") ``` ```swift Query.startsWith("name", value: "Once upon a time") ``` ```http {"method":"startsWith","attribute":"name","values":["Once upon a time"]} ``` {% /multicode %} ##### Ends with {% #ends-with %} Returns documents if a string attribute ends with a substring. {% multicode %} ```client-web Query.endsWith("name", "happily ever after.") ``` ```client-flutter Query.endsWith("name", "happily ever after.") ``` ```python Query.ends_with("name", "happily ever after.") ``` ```ruby Query.ends_with("name", "happily ever after.") ``` ```server-nodejs Query.endsWith("name", "happily ever after.") ``` ```php Query::endsWith("name", "happily ever after.") ``` ```swift Query.endsWith("name", value: "happily ever after.") ``` ```http {"method":"endsWith","attribute":"name","values":["happily ever after."]} ``` {% /multicode %} ##### Contains {% #contains %} Returns documents if the array attribute contains the specified elements or if a string attribute contains the specified substring. {% multicode %} ```client-web // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```client-flutter // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```python ### For arrays Query.contains("ingredients", ['apple', 'banana']) ### For strings Query.contains("name", "Tom") ``` ```ruby ### For arrays Query.contains("ingredients", ['apple', 'banana']) ### For strings Query.contains("name", "Tom") ``` ```server-nodejs // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```php // For arrays Query::contains("ingredients", ['apple', 'banana']) // For strings Query::contains("name", "Tom") ``` ```swift // For arrays Query.contains("ingredients", value: ['apple', 'banana']) // For strings Query.contains("name", value: "Tom") ``` ```http ### For arrays {"method":"contains","attribute":"ingredients","values":["apple","banana"]} ### For strings {"method":"contains","attribute":"name","values":["Tom"]} ``` {% /multicode %} ##### Search {% #search %} Searches string attributes for provided keywords. Requires a [full-text index](/docs/products/databases/legacy/collections#indexes) on queried attributes. {% multicode %} ```client-web Query.search("text", "key words") ``` ```client-flutter Query.search("text", "key words") ``` ```python Query.search("text", "key words") ``` ```ruby Query.search("text", "key words") ``` ```server-nodejs Query.search("text", "key words") ``` ```php Query::search("text", "key words") ``` ```swift Query.search("text", value: "key words") ``` ```http {"method":"search","attribute":"text","values":["key words"]} ``` {% /multicode %} #### Logical operators {% #logical-operators %} ##### AND {% #and %} Returns document if it matches all of the nested sub-queries in the array passed in. {% multicode %} ```client-web Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```client-flutter Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```python Query.and_queries([ Query.less_than("size", 10), Query.greater_than("size", 5) ]) ``` ```ruby Query.and([ Query.less_than("size", 10), Query.greater_than("size", 5) ]) ``` ```server-nodejs Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```php Query::and([ Query::lessThan("size", 10), Query::greaterThan("size", 5) ]) ``` ```swift Query.and([ Query.lessThan("size", value: 10), Query.greaterThan("size", value: 5) ]) ``` ```http {"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]} ``` {% /multicode %} ##### OR {% #or %} Returns document if it matches any of the nested sub-queries in the array passed in. {% multicode %} ```client-web Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```client-flutter Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```python Query.or_queries([ Query.less_than("size", 5), Query.greater_than("size", 10) ]) ``` ```ruby Query.or([ Query.less_than("size", 5), Query.greater_than("size", 10) ]) ``` ```server-nodejs Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```php Query::or([ Query::lessThan("size", 5), Query::greaterThan("size", 10) ]) ``` ```swift Query.or([ Query.lessThan("size", value: 5), Query.greaterThan("size", value: 10) ]) ``` ```http {"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]} ``` {% /multicode %} #### Ordering {% #ordering %} ##### Order descending {% #order-desc %} Orders results in descending order by attribute. Attribute must be indexed. {% multicode %} ```client-web Query.orderDesc("attribute") ``` ```client-flutter Query.orderDesc("attribute") ``` ```python Query.order_desc("attribute") ``` ```ruby Query.order_desc("attribute") ``` ```server-nodejs Query.orderDesc("attribute") ``` ```php Query::orderDesc("attribute") ``` ```swift Query.orderDesc("attribute") ``` ```http {"method":"orderDesc","attribute":"attribute"} ``` {% /multicode %} ##### Order ascending {% #order-asc %} Orders results in ascending order by attribute. Attribute must be indexed. {% multicode %} ```client-web Query.orderAsc("attribute") ``` ```client-flutter Query.orderAsc("attribute") ``` ```python Query.order_asc("attribute") ``` ```ruby Query.order_asc("attribute") ``` ```server-nodejs Query.orderAsc("attribute") ``` ```php Query::orderAsc("attribute") ``` ```swift Query.orderAsc("attribute") ``` ```http {"method":"orderAsc","attribute":"attribute"} ``` {% /multicode %} #### Pagination {% #pagination %} ##### Limit {% #limit %} Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/legacy/pagination). {% multicode %} ```client-web Query.limit(25) ``` ```client-flutter Query.limit(25) ``` ```python Query.limit(25) ``` ```ruby Query.limit(25) ``` ```server-nodejs Query.limit(25) ``` ```php Query::limit(25) ``` ```swift Query.limit(25) ``` ```http {"method":"limit","values":[25]} ``` {% /multicode %} ##### Offset {% #offset %} Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/legacy/pagination). {% multicode %} ```client-web Query.offset(0) ``` ```client-flutter Query.offset(0) ``` ```python Query.offset(0) ``` ```ruby Query.offset(0) ``` ```server-nodejs Query.offset(0) ``` ```php Query::offset(0) ``` ```swift Query.offset(0) ``` ```http {"method":"offset","values":[0]} ``` {% /multicode %} ##### Cursor after {% #cursor-after %} Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination). {% multicode %} ```client-web Query.cursorAfter("62a7...f620") ``` ```client-flutter Query.cursorAfter("62a7...f620") ``` ```python Query.cursor_after("62a7...f620") ``` ```ruby Query.cursor_after("62a7...f620") ``` ```server-nodejs Query.cursorAfter("62a7...f620") ``` ```php Query::cursorAfter("62a7...f620") ``` ```swift Query.cursorAfter("62a7...f620") ``` ```http {"method":"cursorAfter","values":["62a7...f620"]} ``` {% /multicode %} ##### Cursor before {% #cursor-before %} Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination). {% multicode %} ```client-web Query.cursorBefore("62a7...a600") ``` ```client-flutter Query.cursorBefore("62a7...a600") ``` ```python Query.cursor_before("62a7...a600") ``` ```ruby Query.cursor_before("62a7...a600") ``` ```server-nodejs Query.cursorBefore("62a7...a600") ``` ```php Query::cursorBefore("62a7...a600") ``` ```swift Query.cursorBefore("62a7...a600") ``` ```http {"method":"cursorBefore","values":["62a7...a600"]} ``` {% /multicode %} ### Complex queries {% #complex-queries %} You can create complex queries by combining AND and OR operations. For example, to find items that are either books under $20 or magazines under $10: {% multicode %} ```client-web const results = await databases.listDocuments( '', '', [ Query.or([ Query.and([ Query.equal('category', ['books']), Query.lessThan('price', 20) ]), Query.and([ Query.equal('category', ['magazines']), Query.lessThan('price', 10) ]) ]) ] ); ``` ```client-flutter final results = await databases.listDocuments( '', '', [ Query.or([ Query.and([ Query.equal('category', ['books']), Query.lessThan('price', 20) ]), Query.and([ Query.equal('category', ['magazines']), Query.lessThan('price', 10) ]) ]) ] ); ``` ```python results = databases.list_documents( database_id='', collection_id='', queries=[ Query.or_queries([ Query.and_queries([ Query.equal('category', ['books']), Query.less_than('price', 20) ]), Query.and_queries([ Query.equal('category', ['magazines']), Query.less_than('price', 10) ]) ]) ] ) ``` ```http {"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"category","values":["books"]},{"method":"lessThan","attribute":"price","values":[20]}]},{"method":"and","values":[{"method":"equal","attribute":"category","values":["magazines"]},{"method":"lessThan","attribute":"price","values":[10]}]}]} ``` {% /multicode %} This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10. Each condition within the OR is composed of two AND conditions - one for the category and one for the price threshold. The database will return documents that match either of these combined conditions. --- ## Start with Databases https://appwrite.io/docs/products/databases/legacy/quick-start {% section #create-database step=1 title="Create database" %} Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and create a database and name it `Oscar`. Optionally, add a custom database ID. {% /section %} {% section #create-collection step=2 title="Create collection" %} Create a collection and name it `My books`. Optionally, add a custom collection ID. Navigate to **Attributes** and create attributes by clicking **Create attribute** and select **String**. Attributes define the structure of your collection's documents. Enter **Attribute key** and **Size**. For example, `title` and `100`. Navigate to **Settings** > **Permissions** and add a new role **Any**. Check the **CREATE** and **READ** permissions, so anyone can create and read documents. {% /section %} {% section #create-documents step=3 title="Create documents" %} To create a document use the `createDocument` method. In the **Settings** menu, find your project ID and replace `` in the example. Navigate to the `Oscar` database, copy the database ID, and replace ``. Then, in the `My books` collection, copy the collection ID, and replace ``. {% multicode %} ```client-web import { Client, Databases, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); const promise = databases.createDocument( '', '', ID.unique(), { "title": "Hamlet" } ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); try { final document = databases.createDocument( databaseId: '', collectionId: '', documentId: ID.unique(), data: { "title": "Hamlet" } ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let document = try await databases.createDocument( databaseId: "", collectionId: "", documentId: ID.unique(), data: ["title" : "hamlet"] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val document = databases.createDocument( databaseId = "", collectionId = "", documentId = ID.unique(), data = mapOf("title" to "hamlet"), ) } catch (e: Exception) { Log.e("Appwrite", "Error: " + e.message) } } ``` {% /multicode %} The response should look similar to this. ```json { "title": "Hamlet", "$id": "65013138dcd8618e80c4", "$permissions": [], "$createdAt": "2023-09-13T03:49:12.905+00:00", "$updatedAt": "2023-09-13T03:49:12.905+00:00", "$databaseId": "650125c64b3c25ce4bc4", "$collectionId": "650125cff227cf9f95ad" } ``` {% /section %} {% section #list-documents step=4 title="List documents" %} To read and query data from your collection, use the `listDocuments` endpoint. Like the previous step, replace ``, ``, and`` with their respective IDs. {% multicode %} ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") const databases = new Databases(client); let promise = databases.listDocuments( "", "", [ Query.equal('title', 'Hamlet') ] ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") final databases = Databases(client); try { final documents = await databases.listDocuments( databaseId: '', collectionId: '', queries: [ Query.equal('title', 'Hamlet') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws{ let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Databases(client) do { let documents = try await databases.listDocuments( databaseId: "", collectionId: "", queries: [ Query.equal("title", value: "Hamlet") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.Databases suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Databases(client) try { val documents = databases.listDocuments( databaseId = "", collectionId = "", queries = listOf( Query.equal("title", "Hamlet") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` {% /multicode %} {% /section %} --- ## Relationships https://appwrite.io/docs/products/databases/legacy/relationships Relationships describe how documents in different collections are associated, so that related documents can be read, updated, or deleted together. Entities in real-life often associate with each other in an organic and logical way, like a person and their dog, an album and its songs, or friends in a social network. These types of association between entities can be modeled in Appwrite using relationships. {% info title="Experimental feature" %} Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions. {% /info %} ### Relationship Attributes {% #relationship-attributes %} Relationships are represented in a collection using **relationship attributes**. The relationship attribute contains the ID of related documents, which it references during read, update, and delete operations. This attribute is **null** if a document has no related documents. ### When to use a relationship {% #when-to-use-relationships %} Relationships help reduce redundant information. For example, a user can create many posts in your app. You can model this without relationships by keeping a copy of the user's information in all the documents representing posts, but this creates a lot of duplicate information in your database about the user. ### Benefits of relationships {% #benefit-of-relationships %} Duplicated records waste storage, but more importantly, makes the database much harder to maintain. If the user changes their user name, you will have to update dozens or hundreds of records, a problem commonly known as an update anomaly in databases. You can avoid duplicate information by storing users and posts in separate collections and relating a user and their posts through a relationship. ### Tradeoff {% #trade-offs %} Consider using relationships when the same information is found in multiple places to avoid duplicates. However, relationships come with the tradeoff of slowing down queries. For applications where the best read and write performance is important, it may be acceptable to tolerate duplicate data. ### Opt-in Loading {% #performance-loading %} By default, Appwrite returns only a document's own fields when you retrieve documents. Related documents are **not automatically loaded** unless you explicitly request them using query selection. This eliminates unintentional payload bloat and gives you precise control over performance. {% arrow_link href="/docs/products/databases/legacy/queries#relationship-select" %} Learn how to load relationships with queries {% /arrow_link %} ### Directionality {% #directionality %} Appwrite relationships can be one-way or two-way. | Type | Description | | -------- | ----------------------------------------------------------------------------------------------------------------- | | One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure. | | Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure. | ### Types {% #types %} Appwrite provides four different relationship types to enforce different associative rules between documents. | Type | Description | | ----------- | ----------------------------------------------------------------------- | | One-to-one | A document can only be related to one and only one document. | | One-to-many | A document can be related to many other documents. | | Many-to-one | Many documents can be related to a single document. | | Many-to-many| A document can be related to many other documents. | ### On-delete {% #on-delete %} Appwrite also allows you to define the behavior of a relationship when a document is deleted. | Type | Description | | ---------- | ---------------------------------------------------------------------- | | Restrict | If a document has at least one related document, it cannot be deleted.| | Cascade | If a document has related documents, when it is deleted, the related documents are also deleted.| | Set null | If a document has related documents, when it is deleted, the related documents are kept with their relationship attribute set to null.| ### Creating relationships {% #create-relationships %} You can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server) {% tabs %} {% tabsitem #console title="Console" %} You can create relationships in the Appwrite Console by adding a relationship attribute to a collection. 1. In your project, navigate to **Databases** > **Select your database** > **Select your collection** > **Attributes** > **Create attribute**. 2. Select **Relationship** as the attribute type. 3. In the **Relationship** modal, select the [relationship type](#types) and pick the related collection and attributes. 4. Pick relationship attribute key(s) to represent the related collection. Relationship attribute keys are used to reference the related collection in queries, so pick something that's intuitive and easy to remember. 5. Select desired [on delete](#on-delete) behavior. 6. Click the **Create** button to create the relationship. {% /tabsitem %} {% tabsitem #sdk title="SDK" %} Here's an example that adds a relationship between the collections **movies** and **reviews**. A relationship attribute with the key `reviews` is added to the movies collection, and another relationship attribute with the key `movie` is added to the reviews collection. {% multicode %} ```js const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); databases.createRelationshipAttribute( 'marvel', // Database ID 'movies', // Collection ID 'reviews', // Related collection ID 'oneToMany', // Relationship type true, // Is two-way 'reviews', // Attribute key 'movie', // Two-way attribute key 'cascade' // On delete action ); ``` ```php use \Appwrite\Client; use \Appwrite\Services\Databases; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject(''); // Your project ID $databases = new Databases($client); $databases->createRelationshipAttribute( databaseId: 'marvel', // Database ID collectionId: 'movies', // Collection ID relatedCollectionId: 'reviews', // Related collection ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Attribute key twoWayKey: 'movie', // Two-way attribute key onDelete: 'cascade' // On delete action ); ``` ```python from appwrite.client import Client from appwrite.services.databases import Databases client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('')) # Your project ID databases = Databases(client) databases.create_relationship_attribute( database_id='marvel', # Database ID collection_id='movies', # Collection ID related_collection_id='reviews', # Related collection ID type='oneToMany', # Relationship type two_way=True, # Is two-way key='reviews', # Attribute key two_way_key='movie', # Two-way attribute key on_delete='cascade' # On delete action ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1')# Your API Endpoint .set_project('') # Your project ID databases = Databases.new(client) databases.create_relationship_attribute( database_id: 'marvel', # Database ID collection_id: 'movies', # Collection ID related_collection_id: 'reviews', # Related collection ID type: 'oneToMany', # Relationship type two_way: true, # Is two-way key: 'reviews', # Attribute key two_way_key: 'movie', # Two-way attribute key on_delete: 'cascade' # On delete action ) ``` ```deno import { Client, Databases } from "npm:node-appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject(""); // Your project ID const databases = new Databases(client); databases.createRelationshipAttribute( "marvel", // Database ID "movies", // Collection ID "reviews", // Related collection ID "oneToMany", // Relationship type true, // Is two-way "reviews", // Attribute key "movie", // Two-way attribute key "cascade" // On delete action ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final databases = Databases(client); await databases.createRelationshipAttribute( databaseId: 'marvel', // Database ID collectionId: 'movies', // Collection ID relatedCollectionId: 'reviews', // Related collection ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Attribute key twoWayKey: 'movie', // Two-way attribute key onDelete: 'cascade', // On delete action ); ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Databases val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val databases = Databases(client) databases.createRelationshipAttribute( databaseId = "marvel", // Database ID collectionId = "movies", // Collection ID relatedCollectionId = "reviews", // Related collection ID type = "oneToMany", // Relationship type twoWay = true, // Is two-way key = "reviews", // Attribute key twoWayKey = "movie", // Two-way attribute key onDelete = "cascade" // On delete action ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let databases = Databases(client) databases.createRelationshipAttribute( databaseId: "marvel", // Database ID collectionId: "movies", // Collection ID relatedCollectionId: "reviews", // Related collection ID type: "oneToMany", // Relationship type twoWay: true, // Is two-way key: "reviews", // Attribute key twoWayKey: "movie", // Two-way attribute key onDelete : "cascade" // On delete action ) ``` ```csharp using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject(""); var databases = new Databases(client); await databases.CreateRelationshipAttribute( databaseId: "marvel", collectionId: "movies", relatedCollectionId: "reviews", type: "oneToMany", twoWay: true, key: "reviews", twoWayKey: "movie", onDelete: "cascade"); ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Creating documents {% #create-documents %} If a collection has relationship attributes, you can create documents in two ways. You create both parent and child at the same time using a **nested** syntax or link parent and child documents through **references***. {% tabs %} {% tabsitem #nested title="Nested" %} You can create both the **parent** and **child** at once in a relationship by nesting data. {% multicode %} ```js const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!' }, { author: 'Alice', text: 'Loved it!' } ] } ) ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text': 'Great movie!' }, { 'author': 'Alice', 'text': 'Loved it!' } ] }, ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!" ], [ "author": "Alice", "text": "Loved it!" ] ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Database import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val databases = Database(client) databases.createDocument( databaseId = "marvel", collectionId = "movies", documentId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( mapOf("author" to "Bob", "text" to "Great movie!"), mapOf("author" to "Alice", "text" to "Loved it!") ) ) ) ``` {% /multicode %} #### Edge case behaviors {% #edge-case-behaviors %} - If a nested child document is included and **no child document ID** is provided, the child document will be given a unique ID. - If a nested child document is included and **no conflicting child document ID** exists, the child document will be **created**. - If a nested child document is included and the **child document ID already exists**, the child document will be **updated**. {% /tabsitem %} {% tabsitem #reference title="Reference" %} If the child documents are already present in the related collection, you can create the parent and **reference the child documents** using their IDs. Here's an example connecting reviews to a movie. {% multicode %} ```js const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ '', '' ] } ) ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ '', '' ] }, ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ "", "" ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Database import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val databases = Database(client) databases.createDocument( databaseId = "marvel", collectionId = "movies", documentId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "", "" ) ) ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Queries {% #queries %} Queries are currently not available in the experimental version of Appwrite Relationships but will be added in a later version. ### Update Relationships {% #update %} Relationships can be updated by updating the relationship attribute. {% multicode %} ```js const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); await databases.updateDocument( 'marvel', 'movies', 'spiderman', { title: 'Spiderman', year: 2002, reviews: [ 'review4', 'review5' ] } ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); await databases.updateDocument( databaseId: 'marvel', collectionId: 'movies', documentId: 'spiderman', data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ 'review4', 'review5' ] }, ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Database(client: client) databases.updateDocument( databaseId: "marvel", collectionId: "movies", documentId: "spiderman", data: [ "title": "Spiderman", "year": 2002, "reviews": [ "review4", "review5" ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Database val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Database(client) databases.updateDocument( databaseId = "marvel", collectionId = "movies", documentId = "spiderman", data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "review4", "review5" ) ) ) ``` {% /multicode %} ### Delete relationships {% #delete %} #### Unlink relationships, retain documents {% #unlink %} If you need to unlink documents in a relationship but retain the documents, you can do this by **updating the relationship attribute** and removing the ID of the related document. If a document can be related to **only one document**, you can delete the relationship by setting the relationship attribute to `null`. If a document can be related to **more than one document**, you can delete the relationship by setting the relationship attribute to an empty list. #### Delete relationships and documents {% #delete-both %} If you need to delete the documents as well as unlink the relationship, the approach depends on the [on-delete behavior](#on-delete) of a relationship. If the on-delete behavior is **restrict**, the link between the documents needs to be deleted first before the documents can be deleted **individually**. If the on-delete behavior is **set null**, deleting a document will leave related documents in place with their relationship attribute **set to null**. If you wish to also delete related documents, they must be deleted **individually**. If the on-delete behavior is **cascade**, deleting the parent documents also deletes **related child documents**, except for many-to-one relationships. In many-to-one relationships, there are multiple parent documents related to a single child document, and when the child document is deleted, the parents are deleted in cascade. {% multicode %} ```js const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); await databases.deleteDocument( 'marvel', 'movies', 'spiderman' ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); await databases.deleteDocument( databaseId: 'marvel', collectionId: 'movies', documentId: 'spiderman' ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Database(client: client) databases.deleteDocument( databaseId: "marvel", collectionId: "movies", documentId: "spiderman" ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Database val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val databases = Database(client) databases.deleteDocument( databaseId = "marvel", collectionId = "movies", documentId = "spiderman" ) ``` {% /multicode %} ### Permissions {% #permissions %} To access documents in a relationship, you must have permission to access both the parent and child documents. When creating both the parent and child documents, the child document will **inherit permissions** from its parent. You can also provide explicit permissions to the child document if they should be **different from their parent**. {% multicode %} ```js const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!', $permissions: [ Permission.read(Role.any()) ] }, ] } ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text': 'Great movie!', '\$permissions': [ Permission.read(Role.any()) ] }, ] }, ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] ); ``` ```kotlin import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] ); ``` {% /multicode %} When creating, updating, or deleting in a relationship, you must have permission to access all documents referenced. If the user does not have read permission to any document, an exception will be thrown. ### Limitations {% #limitations %} Relationships can be nested between collections, but are restricted to a **max depth of three levels**. Relationship attribute key, type, and directionality can't be updated. On-delete behavior is the only option that can be updated for relationship attributes. --- ## Type generation https://appwrite.io/docs/products/databases/legacy/type-generation The Appwrite CLI provides a simple way to generate types based on your Appwrite database schema. This feature is particularly useful for developers who want to ensure type safety in their applications by generating type definitions that match their database collections and attributes. To generate types, the CLI reads the database schema from your project's `appwrite.json` file and generates type definitions for each collection. #### Generating types First, ensure you have the [Appwrite CLI](/docs/tooling/command-line/installation#getting-started) installed and your project is [initialised](/docs/tooling/command-line/installation#initialization). Then, run the following command in your terminal to pull collections from your Appwrite project: ```bash appwrite pull collections ``` To generate types, you can use the Appwrite CLI command: ```bash appwrite types [options] ``` The following options are currently available: | Option | Description | |--------|-------------| | `--language` or `-l` | The programming language for which types can be generated. Choices include `ts`, `js`, `php`, `kotlin`, `swift`, `java`, `dart`, `auto`. The CLI will use `auto` as the default option if this option is skipped. | | `--help` or `-h` | Displays help information for the command. | #### Example usage Suppose you want to generate types for a collection with data on books with the following schema from your `appwrite.json` file: ```json { "projectId": "682ca9a50004cf4b330f", "projectName": "Appwrite project", "databases": [ { "$id": "684c678b00211ddac082", "name": "Library", "enabled": true } ], "collections": [ { "$id": "684c6790002d457ee89d", "$permissions": [], "databaseId": "684c678b00211ddac082", "name": "Books", "enabled": true, "documentSecurity": false, "attributes": [ { "key": "name", "type": "string", "required": true, "array": false, "size": 255, "default": null }, { "key": "author", "type": "string", "required": true, "array": false, "size": 255, "default": null }, { "key": "release_year", "type": "datetime", "required": false, "array": false, "format": "", "default": null }, { "key": "category", "type": "string", "required": false, "array": false, "elements": [ "fiction", "nonfiction" ], "format": "enum", "default": null }, { "key": "genre", "type": "string", "required": false, "array": true, "size": 100, "default": null }, { "key": "is_checked_out", "type": "boolean", "required": true, "array": false, "default": null } ], "indexes": [] } ] } ``` Here's how you can generate types for this collection across all supported languages: {% tabs %} {% tabsitem #ts title="TypeScript" %} Run the following command in your terminal: ```bash appwrite types --language ts ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```ts import { Models } from 'appwrite'; export enum Category { FICTION = "fiction", NONFICTION = "nonfiction", } export type Books = Models.Document & { name: string; author: string; releaseYear: string | null; category: Category | null; genre: string[] | null; isCheckedOut: boolean; } ``` {% /tabsitem %} {% tabsitem #js title="JavaScript" %} Run the following command in your terminal: ```bash appwrite types --language js ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```js /** * @typedef {import('appwrite').Models.Document} Document */ /** * @typedef {Object} Books * @property {string} name * @property {string} author * @property {string|null|undefined} releaseYear * @property {"fiction"|"nonfiction"|null|undefined} category * @property {string[]|null|undefined} genre * @property {boolean} isCheckedOut */ ``` {% /tabsitem %} {% tabsitem #java title="Java" %} Run the following command in your terminal: ```bash appwrite types --language java ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```java package io.appwrite.models; import java.util.*; public class Books { public enum Category { fiction, nonfiction; } private String name; private String author; private String releaseYear; private Category category; private List genre; private boolean isCheckedOut; public Books() { } public Books( String name, String author, String releaseYear, Category category, List genre, boolean isCheckedOut ) { this.name = name; this.author = author; this.releaseYear = releaseYear; this.category = category; this.genre = genre; this.isCheckedOut = isCheckedOut; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getReleaseYear() { return releaseYear; } public void setReleaseYear(String releaseYear) { this.releaseYear = releaseYear; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public List getGenre() { return genre; } public void setGenre(List genre) { this.genre = genre; } public boolean getIsCheckedOut() { return isCheckedOut; } public void setIsCheckedOut(boolean isCheckedOut) { this.isCheckedOut = isCheckedOut; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Books that = (Books) obj; return Objects.equals(name, that.name) && Objects.equals(author, that.author) && Objects.equals(releaseYear, that.releaseYear) && Objects.equals(category, that.category) && Objects.equals(genre, that.genre) && Objects.equals(isCheckedOut, that.isCheckedOut); } @Override public int hashCode() { return Objects.hash(name, author, releaseYear, category, genre, isCheckedOut); } @Override public String toString() { return "Books{" + "name=" + name + "author=" + author + "releaseYear=" + releaseYear + "category=" + category + "genre=" + genre + "isCheckedOut=" + isCheckedOut + '}'; } } ``` {% /tabsitem %} {% tabsitem #php title="PHP" %} Run the following command in your terminal: ```bash appwrite types --language php ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```php name = $name; $this->author = $author; $this->releaseYear = $releaseYear; $this->category = $category; $this->genre = $genre; $this->isCheckedOut = $isCheckedOut; } public function getName(): string { return $this->name; } public function setName(string $name): void { $this->name = $name; } public function getAuthor(): string { return $this->author; } public function setAuthor(string $author): void { $this->author = $author; } public function getReleaseYear(): string|null { return $this->releaseYear; } public function setReleaseYear(string|null $releaseYear): void { $this->releaseYear = $releaseYear; } public function getCategory(): Category|null { return $this->category; } public function setCategory(Category|null $category): void { $this->category = $category; } public function getGenre(): array { return $this->genre; } public function setGenre(array $genre): void { $this->genre = $genre; } public function getIsCheckedOut(): bool { return $this->isCheckedOut; } public function setIsCheckedOut(bool $isCheckedOut): void { $this->isCheckedOut = $isCheckedOut; } } ``` {% /tabsitem %} {% tabsitem #dart title="Dart" %} Run the following command in your terminal: ```bash appwrite types --language dart ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```dart enum Category { fiction, nonfiction, } class Books { String name; String author; String? releaseYear; Category? category; List? genre; bool isCheckedOut; Books({ required this.name, required this.author, this.releaseYear, this.category, this.genre, required this.isCheckedOut, }); factory Books.fromMap(Map map) { return Books( name: map['name'].toString(), author: map['author'].toString(), releaseYear: map['release_year']?.toString() ?? null, category: map['category'] != null ? Category.values.where((e) => e.name == map['category']).firstOrNull : null, genre: List.from(map['genre'] ?? []) ?? [], isCheckedOut: map['is_checked_out'], ); } Map toMap() { return { "name": name, "author": author, "release_year": releaseYear, "category": category?.name ?? null, "genre": genre, "is_checked_out": isCheckedOut, }; } } ``` {% /tabsitem %} {% tabsitem #kotlin title="Kotlin" %} Run the following command in your terminal: ```bash appwrite types --language kotlin ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```kotlin package io.appwrite.models enum class Category { fiction, nonfiction } data class Books( val name: String, val author: String, val releaseYear: String?, val category: Category?, val genre: List?, val isCheckedOut: Boolean, ) ``` {% /tabsitem %} {% tabsitem #swift title="Swift" %} Run the following command in your terminal: ```bash appwrite types --language swift ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```swift import Foundation public enum Category: String, Codable, CaseIterable { case fiction = "fiction" case nonfiction = "nonfiction" } public class Books: Codable { public let name: String public let author: String public let releaseYear: String? public let category: Category? public let genre: [String]? public let isCheckedOut: Bool enum CodingKeys: String, CodingKey { case name = "name" case author = "author" case releaseYear = "release_year" case category = "category" case genre = "genre" case isCheckedOut = "is_checked_out" } init( name: String, author: String, releaseYear: String?, category: Category?, genre: [String]?, isCheckedOut: Bool ) { self.name = name self.author = author self.releaseYear = releaseYear self.category = category self.genre = genre self.isCheckedOut = isCheckedOut } public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) self.author = try container.decode(String.self, forKey: .author) self.releaseYear = try container.decodeIfPresent(String.self, forKey: .releaseYear) self.category = try container.decodeIfPresent(Category.self, forKey: .category) self.genre = try container.decodeIfPresent([String].self, forKey: .genre) self.isCheckedOut = try container.decode(Bool.self, forKey: .isCheckedOut) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(author, forKey: .author) try container.encodeIfPresent(releaseYear, forKey: .releaseYear) try container.encodeIfPresent(category, forKey: .category) try container.encodeIfPresent(genre, forKey: .genre) try container.encode(isCheckedOut, forKey: .isCheckedOut) } public func toMap() -> [String: Any] { return [ "name": name as Any, "author": author as Any, "release_year": releaseYear as Any, "category": category as Any, "genre": genre as Any, "is_checked_out": isCheckedOut as Any ] } public static func from(map: [String: Any]) -> Books { return Books( name: map["name"] as! String, author: map["author"] as! String, releaseYear: map["release_year"] as? String, category: map["category"] as? String, genre: map["genre"] as? [String], isCheckedOut: map["is_checked_out"] as! Bool ) } } ``` {% /tabsitem %} {% /tabs %} --- ## Offline sync https://appwrite.io/docs/products/databases/offline Offline synchronization (or offline sync) is a mechanism that allows apps to store and update data locally when a user is offline (i.e., loses internet connectivity), and then synchronize that data with an Appwrite database once the user is back online. This capability is crucial for building resilient and responsive applications, especially in environments with unreliable or intermittent internet connectivity. Suppose you are driving from one city to another and lose internet connectivitity while passing through a rural area, locally-downloaded maps in your GPS app would ensure that you do not get lost. Another example could be that you are waiting in queue at a supermarket and there is a network outage; an offline-synchronized databases with inventory data would prevent the point-of-sale (POS) systems from failing, ensuring you and your fellow customers can buy groceries. Some real-world scenarios where offline sync is useful are: - Journaling and note-taking apps - Warehouse inventory management systems - Medical data entry tools - Airline check-in management apps - GPS navigation software ### Integrate offline sync in your apps {% only_light %} {% cards %} {% cards_item href="/integrations/replication-rxdb" title="RxDB" image="/images/docs/databases/offline/logos/rxdb.svg" %} {% /cards_item %} {% /cards %} {% /only_light %} {% only_dark %} {% cards %} {% cards_item href="/integrations/replication-rxdb" title="RxDB" image="/images/docs/databases/offline/logos/dark/rxdb.svg" %} {% /cards_item %} {% /cards %} {% /only_dark %} ### How does offline sync work? The process of implementing offline sync in Appwrite-powered apps (and in general) is as follows: 1. **Local data storage:** When a user opens your app, the app downloads relevant data from the server and saves it locally on their device via local-first data stores like IndexedDB, LocalStorage, SQLite, or RxDB. 2. **Working offline**: While offline, users can either read previously synced data or make changes (create, update, or delete data) in the local data store. 3. **Detecting connectivity**: The app monitors network status. As soon as connectivity is restored, a sync operation is triggered between the local data store and the Appwrite database. 5. **Two-way synchronization**: Local changes are *"pushed"* to the Appwrite database and new changes from the database are *"pulled"* into the local store. This process is called **push-pull replication**. 6. **Conflict resolution**: If the same data was changed both locally and on the server, the system must prioritise one of the two operations. Various strategies can be implemented to mitigate this issue, such as *last write wins* or *manual user conflict resolution*. --- ## Operators https://appwrite.io/docs/products/databases/operators Database operators let you update fields directly on the server without fetching the full row. Instead of sending new values, you describe the action you want: increment, append, replace, or adjust. This eliminates race conditions and reduces bandwidth usage when updating any values that need to be modified atomically. The operation is applied atomically at the storage layer for safe, concurrent updates. - Atomic by field: Each operation is applied safely at the storage layer to prevent lost updates under concurrency. - Multi-field updates: Apply multiple operations across different fields in a single request or transaction. - Type-safe: Operators are exposed through typed SDK methods for clarity and safety. - Transaction-ready: Operators can be staged and committed alongside other database actions for consistent writes. ### How operators work {% #how-database-operators-work %} Instead of the traditional **read-modify-write** pattern, operators use dedicated methods to modify values directly on the server. The server applies the change atomically under concurrency control and returns the new value. Let's take an example of appending a value to an array field. **Traditional approach:** 1. Fetch row → `{ letters: ['a', 'b' ] }` 2. Update client-side → `letters: ['a', 'b', 'c']` 3. Write back → `{ letters: ['a', 'b', 'c'] }` **Operator approach:** 1. Update/upsert the row with the appropriate value to append 2. Server applies atomically → `letters: ['a', 'b', 'c']` Here's how you can do so programmatically: {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); await tablesDB.updateRow({ databaseId: "", tableId: "", rowId: "", data: { letters: Operator.arrayAppend(['c']) } }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { letters: sdk.Operator.arrayAppend(['c']) } }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { await tablesDB.updateRow( '', '', '', { 'letters': Operator.arrayAppend(['c']) }, ); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { _ = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "letters": Operator.arrayAppend(["c"]) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "letters" to Operator.arrayAppend(listOf("c")) ) ) } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) _, err := tablesDB.UpdateRow( "", "", "", tablesDB.WithUpdateRowData(map[string]any{ "letters": operator.ArrayAppend([]string{"c"}), }), ) if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); $result = $tablesDB->updateRow( '', '', '', [ 'letters' => Operator::arrayAppend(['c']) ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) result = tablesDB.update_row( '', '', '', { 'letters': Operator.arrayAppend(['c']) } ) ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); await tablesDB.UpdateRow( databaseId: "", tableId: "", rowId: "", data: new Dictionary { { "letters", Operator.ArrayAppend(new[] { "c" }) } } ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) result = tablesDB.update_row( '', '', '', { 'letters' => Operator.arrayAppend(['c']) } ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); tablesDB.updateRow( "", "", "", Map.of("letters", Operator.arrayAppend(List.of("c"))), new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` {% /multicode %} ### When to use operators {% #when-to-use %} Use operators when you need to: - Update fields frequently under concurrency (likes, scores, credits, inventory) - Edit lists/tags without rewriting whole arrays - Make small text changes in-place - Adjust dates for lifecycle events or scheduling This keeps payloads small, avoids race conditions, and reduces round-trips. ### Available operators {% #operators %} The following operators are available, grouped by field type. Each operator updates the given column atomically on the server. #### Numeric {% #numeric %} Perform arithmetic on numeric fields without reading the row first. ##### increment {% #increment %} Increase a numeric field by a specified value. Optionally cap the result at a maximum value. {% multicode %} ```client-web Operator.increment(1) ``` ```server-python Operator.increment(1) ``` ```server-php Operator::increment(1) ``` ```client-apple Operator.increment(1) ``` ```client-android-kotlin Operator.increment(1) ``` ```server-go operator.Increment(1) ``` ```client-flutter Operator.increment(1) ``` ```server-dotnet Operator.Increment(1) ``` ```server-ruby Operator.increment(1) ``` ```server-java Operator.increment(1) ``` {% /multicode %} ##### decrement {% #decrement %} Decrease a numeric field by a specified value. Optionally cap the result at a minimum value. {% multicode %} ```client-web Operator.decrement(1) ``` ```server-python Operator.decrement(1) ``` ```server-php Operator::decrement(1) ``` ```client-apple Operator.decrement(1) ``` ```client-android-kotlin Operator.decrement(1) ``` ```server-go operator.Decrement(1) ``` ```client-flutter Operator.decrement(1) ``` ```server-dotnet Operator.Decrement(1) ``` ```server-ruby Operator.decrement(1) ``` ```server-java Operator.decrement(1) ``` {% /multicode %} ##### multiply {% #multiply %} Multiply a numeric field by a specified factor. Optionally cap the result at a maximum value. {% multicode %} ```client-web Operator.multiply(2) ``` ```server-python Operator.multiply(2) ``` ```server-php Operator::multiply(2) ``` ```client-apple Operator.multiply(2) ``` ```client-android-kotlin Operator.multiply(2) ``` ```server-go operator.Multiply(2) ``` ```client-flutter Operator.multiply(2) ``` ```server-dotnet Operator.Multiply(2) ``` ```server-ruby Operator.multiply(2) ``` ```server-java Operator.multiply(2) ``` {% /multicode %} ##### divide {% #divide %} Divide a numeric field by a specified divisor. Optionally cap the result at a minimum value. Divisor cannot be zero. {% multicode %} ```client-web Operator.divide(5) ``` ```server-python Operator.divide(5) ``` ```server-php Operator::divide(5) ``` ```client-apple Operator.divide(5) ``` ```client-android-kotlin Operator.divide(5) ``` ```server-go operator.Divide(5) ``` ```client-flutter Operator.divide(5) ``` ```server-dotnet Operator.Divide(5) ``` ```server-ruby Operator.divide(5) ``` ```server-java Operator.divide(5) ``` {% /multicode %} ##### modulo {% #modulo %} Set a numeric field to the remainder of itself divided by a specified value. {% multicode %} ```client-web Operator.modulo(3) ``` ```server-python Operator.modulo(3) ``` ```server-php Operator::modulo(3) ``` ```client-apple Operator.modulo(3) ``` ```client-android-kotlin Operator.modulo(3) ``` ```server-go operator.Modulo(3) ``` ```client-flutter Operator.modulo(3) ``` ```server-dotnet Operator.Modulo(3) ``` ```server-ruby Operator.modulo(3) ``` ```server-java Operator.modulo(3) ``` {% /multicode %} ##### power {% #power %} Raise a numeric field to a specified exponent. Optionally cap the result at a maximum value. {% multicode %} ```client-web Operator.power(2) ``` ```server-python Operator.power(2) ``` ```server-php Operator::power(2) ``` ```client-apple Operator.power(2) ``` ```client-android-kotlin Operator.power(2) ``` ```server-go operator.Power(2) ``` ```client-flutter Operator.power(2) ``` ```server-dotnet Operator.Power(2) ``` ```server-ruby Operator.power(2) ``` ```server-java Operator.power(2) ``` {% /multicode %} #### Array {% #array %} Edit lists in place: append, remove, or modify array items atomically. ##### arrayAppend {% #array-append %} Add one or more elements to the end of an array. {% multicode %} ```client-web Operator.arrayAppend(['c']) ``` ```server-python Operator.arrayAppend(['c']) ``` ```server-php Operator::arrayAppend(['c']) ``` ```client-apple Operator.arrayAppend(["c"]) ``` ```client-android-kotlin Operator.arrayAppend(listOf("c")) ``` ```server-go operator.ArrayAppend([]string{"c"}) ``` ```client-flutter Operator.arrayAppend(['c']) ``` ```server-dotnet Operator.ArrayAppend(new[] { "c" }) ``` ```server-ruby Operator.arrayAppend(['c']) ``` ```server-java Operator.arrayAppend(List.of("c")) ``` {% /multicode %} ##### arrayPrepend {% #array-prepend %} Add one or more elements to the beginning of an array. {% multicode %} ```client-web Operator.arrayPrepend(['z']) ``` ```server-python Operator.arrayPrepend(['z']) ``` ```server-php Operator::arrayPrepend(['z']) ``` ```client-apple Operator.arrayPrepend(["z"]) ``` ```client-android-kotlin Operator.arrayPrepend(listOf("z")) ``` ```server-go operator.ArrayPrepend([]string{"z"}) ``` ```client-flutter Operator.arrayPrepend(['z']) ``` ```server-dotnet Operator.ArrayPrepend(new[] { "z" }) ``` ```server-ruby Operator.arrayPrepend(['z']) ``` ```server-java Operator.arrayPrepend(List.of("z")) ``` {% /multicode %} ##### arrayInsert {% #array-insert %} Insert an element at a specific index in an array. {% multicode %} ```client-web Operator.arrayInsert(1, 'x') ``` ```server-python Operator.arrayInsert(1, 'x') ``` ```server-php Operator::arrayInsert(1, 'x') ``` ```client-apple Operator.arrayInsert(1, "x") ``` ```client-android-kotlin Operator.arrayInsert(1, "x") ``` ```server-go operator.ArrayInsert(1, "x") ``` ```client-flutter Operator.arrayInsert(1, 'x') ``` ```server-dotnet Operator.ArrayInsert(1, "x") ``` ```server-ruby Operator.arrayInsert(1, 'x') ``` ```server-java Operator.arrayInsert(1, "x") ``` {% /multicode %} ##### arrayRemove {% #array-remove %} Remove a specified element from an array. {% multicode %} ```client-web Operator.arrayRemove('b') ``` ```server-python Operator.arrayRemove('b') ``` ```server-php Operator::arrayRemove('b') ``` ```client-apple Operator.arrayRemove("b") ``` ```client-android-kotlin Operator.arrayRemove("b") ``` ```server-go operator.ArrayRemove("b") ``` ```client-flutter Operator.arrayRemove('b') ``` ```server-dotnet Operator.ArrayRemove("b") ``` ```server-ruby Operator.arrayRemove('b') ``` ```server-java Operator.arrayRemove("b") ``` {% /multicode %} ##### arrayUnique {% #array-unique %} Remove duplicate elements from an array. {% multicode %} ```client-web Operator.arrayUnique() ``` ```server-python Operator.arrayUnique() ``` ```server-php Operator::arrayUnique() ``` ```client-apple Operator.arrayUnique() ``` ```client-android-kotlin Operator.arrayUnique() ``` ```server-go operator.ArrayUnique() ``` ```client-flutter Operator.arrayUnique() ``` ```server-dotnet Operator.ArrayUnique() ``` ```server-ruby Operator.arrayUnique() ``` ```server-java Operator.arrayUnique() ``` {% /multicode %} ##### arrayIntersect {% #array-intersect %} Keep only elements that exist in both arrays. {% multicode %} ```client-web Operator.arrayIntersect(['news', 'tech']) ``` ```server-python Operator.arrayIntersect(['news', 'tech']) ``` ```server-php Operator::arrayIntersect(['news', 'tech']) ``` ```client-apple Operator.arrayIntersect(["news", "tech"]) ``` ```client-android-kotlin Operator.arrayIntersect(listOf("news", "tech")) ``` ```server-go operator.ArrayIntersect([]string{"news", "tech"}) ``` ```client-flutter Operator.arrayIntersect(['news', 'tech']) ``` ```server-dotnet Operator.ArrayIntersect(new[] { "news", "tech" }) ``` ```server-ruby Operator.arrayIntersect(['news', 'tech']) ``` ```server-java Operator.arrayIntersect(new String[] {"news", "tech"}) ``` {% /multicode %} ##### arrayDiff {% #array-diff %} Return elements that exist in the current array but not in the provided array. {% multicode %} ```client-web Operator.arrayDiff(['old']) ``` ```server-python Operator.arrayDiff(['old']) ``` ```server-php Operator::arrayDiff(['old']) ``` ```client-apple Operator.arrayDiff(["old"]) ``` ```client-android-kotlin Operator.arrayDiff(listOf("old")) ``` ```server-go operator.ArrayDiff([]string{"old"}) ``` ```client-flutter Operator.arrayDiff(['old']) ``` ```server-dotnet Operator.ArrayDiff(new[] { "old" }) ``` ```server-ruby Operator.arrayDiff(['old']) ``` ```server-java Operator.arrayDiff(new String[] {"old"}) ``` {% /multicode %} ##### arrayFilter {% #array-filter %} Filter array elements based on a condition. {% multicode %} ```client-web Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```server-python Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```server-php Operator::arrayFilter(Condition::GreaterThan, 10) ``` ```client-apple Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```client-android-kotlin Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```server-go operator.ArrayFilter(ConditionGreaterThan, 10) ``` ```client-flutter Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```server-dotnet Operator.ArrayFilter(Condition.GreaterThan, 10) ``` ```server-ruby Operator.arrayFilter(Condition.GreaterThan, 10) ``` ```server-java Operator.arrayFilter(Condition.GreaterThan, 10) ``` {% /multicode %} #### String {% #string %} Make lightweight text changes without rewriting the whole row. ##### stringConcat {% #string-concat %} Concatenate a value to a string or array field. {% multicode %} ```client-web Operator.stringConcat('!') ``` ```server-python Operator.stringConcat('!') ``` ```server-php Operator::stringConcat('!') ``` ```client-apple Operator.stringConcat("!") ``` ```client-android-kotlin Operator.stringConcat("!") ``` ```server-go operator.StringConcat("!") ``` ```client-flutter Operator.stringConcat('!') ``` ```server-dotnet Operator.StringConcat("!") ``` ```server-ruby Operator.stringConcat('!') ``` ```server-java Operator.stringConcat("!") ``` {% /multicode %} ##### stringReplace {% #string-replace %} Replace occurrences of a substring with a new string. {% multicode %} ```client-web Operator.stringReplace('old', 'new') ``` ```server-python Operator.stringReplace('old', 'new') ``` ```server-php Operator::stringReplace('old', 'new') ``` ```client-apple Operator.stringReplace("old", "new") ``` ```client-android-kotlin Operator.stringReplace("old", "new") ``` ```server-go operator.StringReplace("old", "new") ``` ```client-flutter Operator.stringReplace('old', 'new') ``` ```server-dotnet Operator.StringReplace("old", "new") ``` ```server-ruby Operator.stringReplace('old', 'new') ``` ```server-java Operator.stringReplace("old", "new") ``` {% /multicode %} #### Date {% #date %} Adjust time-based fields for lifecycle and scheduling logic. ##### dateAddDays {% #date-add-days %} Add a specified number of days to a date field. {% multicode %} ```client-web Operator.dateAddDays(7) ``` ```server-python Operator.dateAddDays(7) ``` ```server-php Operator::dateAddDays(7) ``` ```client-apple Operator.dateAddDays(7) ``` ```client-android-kotlin Operator.dateAddDays(7) ``` ```server-go operator.DateAddDays(7) ``` ```client-flutter Operator.dateAddDays(7) ``` ```server-dotnet Operator.DateAddDays(7) ``` ```server-ruby Operator.dateAddDays(7) ``` ```server-java Operator.dateAddDays(7) ``` {% /multicode %} ##### dateSubDays {% #date-sub-days %} Subtract a specified number of days from a date field. {% multicode %} ```client-web Operator.dateSubDays(3) ``` ```server-python Operator.dateSubDays(3) ``` ```server-php Operator::dateSubDays(3) ``` ```client-apple Operator.dateSubDays(3) ``` ```client-android-kotlin Operator.dateSubDays(3) ``` ```server-go operator.DateSubDays(3) ``` ```client-flutter Operator.dateSubDays(3) ``` ```server-dotnet Operator.DateSubDays(3) ``` ```server-ruby Operator.dateSubDays(3) ``` ```server-java Operator.dateSubDays(3) ``` {% /multicode %} ##### dateSetNow {% #date-set-now %} Set a date field to the current time on the server. {% multicode %} ```client-web Operator.dateSetNow() ``` ```server-python Operator.dateSetNow() ``` ```server-php Operator::dateSetNow() ``` ```client-apple Operator.dateSetNow() ``` ```client-android-kotlin Operator.dateSetNow() ``` ```server-go operator.DateSetNow() ``` ```client-flutter Operator.dateSetNow() ``` ```server-dotnet Operator.DateSetNow() ``` ```server-ruby Operator.dateSetNow() ``` ```server-java Operator.dateSetNow() ``` {% /multicode %} #### Boolean {% #boolean %} Toggle boolean values in place. ##### toggle {% #toggle %} Toggle a boolean field between true and false. {% multicode %} ```client-web Operator.toggle() ``` ```server-python Operator.toggle() ``` ```server-php Operator::toggle() ``` ```client-apple Operator.toggle() ``` ```client-android-kotlin Operator.toggle() ``` ```server-go operator.Toggle() ``` ```client-flutter Operator.toggle() ``` ```server-dotnet Operator.Toggle() ``` ```server-ruby Operator.toggle() ``` ```server-java Operator.toggle() ``` {% /multicode %} ### Examples {% #examples %} The following examples will demonstrate how you can use operators in different situations #### Update the count of upvotes on a post This example demonstrates using the `increment` operator to atomically increase the upvote count on a post. {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); await tablesDB.updateRow({ databaseId: "", tableId: "", rowId: "", data: { upvotes: Operator.increment(1) } }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { upvotes: sdk.Operator.increment(1) } }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { await tablesDB.updateRow( '', '', '', { 'upvotes': Operator.increment(1) }, ); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { _ = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "upvotes": Operator.increment(1) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "upvotes" to Operator.increment(1) ) ) } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) _, err := tablesDB.UpdateRow( "", "", "", tablesDB.WithUpdateRowData(map[string]any{ "upvotes": operator.Increment(1), }), ) if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); $result = $tablesDB->updateRow( '', '', '', [ 'upvotes' => Operator::increment(1) ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) result = tablesDB.update_row( '', '', '', { 'upvotes': Operator.increment(1) } ) ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); await tablesDB.UpdateRow( databaseId: "", tableId: "", rowId: "", data: new Dictionary { { "upvotes", Operator.Increment(1) } } ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) result = tablesDB.update_row( '', '', '', { 'upvotes' => Operator.increment(1) } ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); tablesDB.updateRow( "", "", "", Map.of("upvotes", Operator.increment(1)), new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` {% /multicode %} #### Add a book to a list This example demonstrates using the `arrayAppend` operator to add a new book to an existing array of books. {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); await tablesDB.updateRow({ databaseId: "", tableId: "", rowId: "", data: { books: Operator.arrayAppend(['The Great Gatsby']) } }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { books: sdk.Operator.arrayAppend(['The Great Gatsby']) } }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { await tablesDB.updateRow( '', '', '', { 'books': Operator.arrayAppend(['The Great Gatsby']) }, ); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { _ = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "books": Operator.arrayAppend(["The Great Gatsby"]) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "books" to Operator.arrayAppend(listOf("The Great Gatsby")) ) ) } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) _, err := tablesDB.UpdateRow( "", "", "", tablesDB.WithUpdateRowData(map[string]any{ "books": operator.ArrayAppend([]string{"The Great Gatsby"}), }), ) if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); $result = $tablesDB->updateRow( '', '', '', [ 'books' => Operator::arrayAppend(['The Great Gatsby']) ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) result = tablesDB.update_row( '', '', '', { 'books': Operator.arrayAppend(['The Great Gatsby']) } ) ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); await tablesDB.UpdateRow( databaseId: "", tableId: "", rowId: "", data: new Dictionary { { "books", Operator.ArrayAppend(new[] { "The Great Gatsby" }) } } ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) result = tablesDB.update_row( '', '', '', { 'books' => Operator.arrayAppend(['The Great Gatsby']) } ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); tablesDB.updateRow( "", "", "", Map.of("books", Operator.arrayAppend(List.of("The Great Gatsby"))), new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` {% /multicode %} #### Update the date field in a deletion table This example demonstrates using the `dateAddDays` operator to set a scheduled deletion date 30 days from now. {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); await tablesDB.updateRow({ databaseId: "", tableId: "", rowId: "", data: { scheduledDeletion: Operator.dateAddDays(30) } }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const result = await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { scheduledDeletion: sdk.Operator.dateAddDays(30) } }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { await tablesDB.updateRow( '', '', '', { 'scheduledDeletion': Operator.dateAddDays(30) }, ); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { _ = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "scheduledDeletion": Operator.dateAddDays(30) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "scheduledDeletion" to Operator.dateAddDays(30) ) ) } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) _, err := tablesDB.UpdateRow( "", "", "", tablesDB.WithUpdateRowData(map[string]any{ "scheduledDeletion": operator.DateAddDays(30), }), ) if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); $result = $tablesDB->updateRow( '', '', '', [ 'scheduledDeletion' => Operator::dateAddDays(30) ] ); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) result = tablesDB.update_row( '', '', '', { 'scheduledDeletion': Operator.dateAddDays(30) } ) ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); await tablesDB.UpdateRow( databaseId: "", tableId: "", rowId: "", data: new Dictionary { { "scheduledDeletion", Operator.DateAddDays(30) } } ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) result = tablesDB.update_row( '', '', '', { 'scheduledDeletion' => Operator.dateAddDays(30) } ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); tablesDB.updateRow( "", "", "", Map.of("scheduledDeletion", Operator.dateAddDays(30)), new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` {% /multicode %} #### Update a single row in a transaction This example demonstrates combining multiple operators (`increment` and `dateSetNow`) in a single transaction to ensure atomic updates. {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Create a transaction const tx = await tablesDB.createTransaction(); // Update row with operators inside the transaction await tablesDB.updateRow({ databaseId: "", tableId: "", rowId: "", data: { upvotes: Operator.increment(1), lastModified: Operator.dateSetNow() }, transactionId: tx.$id }); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); // Create a transaction const tx = await tablesDB.createTransaction(); // Update row with operators inside the transaction await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { upvotes: sdk.Operator.increment(1), lastModified: sdk.Operator.dateSetNow() }, transactionId: tx.$id }); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { // Create a transaction final tx = await tablesDB.createTransaction(); // Update row with operators inside the transaction await tablesDB.updateRow( '', '', '', { 'upvotes': Operator.increment(1), 'lastModified': Operator.dateSetNow() }, transactionId: tx.$id ); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { // Create a transaction let tx = try await tablesDB.createTransaction() // Update row with operators inside the transaction _ = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: [ "upvotes": Operator.increment(1), "lastModified": Operator.dateSetNow() ], transactionId: tx.$id ) // Commit the transaction _ = try await tablesDB.updateTransaction( transactionId: tx.$id, status: "commit" ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) // Create a transaction val tx = tablesDB.createTransaction() // Update row with operators inside the transaction tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf( "upvotes" to Operator.increment(1), "lastModified" to Operator.dateSetNow() ), transactionId = tx.$id ) // Commit the transaction tablesDB.updateTransaction(tx.$id, "commit") } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) // Create a transaction tx, err := tablesDB.CreateTransaction() if err != nil { log.Fatal(err) } // Update row with operators inside the transaction _, err = tablesDB.UpdateRow( "", "", "", tablesDB.WithUpdateRowData(map[string]any{ "upvotes": operator.Increment(1), "lastModified": operator.DateSetNow(), }), tablesDB.WithUpdateRowTransactionId(tx.Id), ) if err != nil { log.Fatal(err) } // Commit the transaction _, err = tablesDB.UpdateTransaction(tx.Id, "commit") if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); // Create a transaction $tx = $tablesDB->createTransaction(); // Update row with operators inside the transaction $result = $tablesDB->updateRow( '', '', '', [ 'upvotes' => Operator::increment(1), 'lastModified' => Operator::dateSetNow() ], transactionId: $tx['$id'] ); // Commit the transaction $tablesDB->updateTransaction($tx['$id'], 'commit'); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) ### Create a transaction tx = tablesDB.create_transaction() ### Update row with operators inside the transaction result = tablesDB.update_row( '', '', '', { 'upvotes': Operator.increment(1), 'lastModified': Operator.dateSetNow() }, transaction_id=tx['$id'] ) ### Commit the transaction tablesDB.update_transaction(tx['$id'], 'commit') ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); // Create a transaction var tx = await tablesDB.CreateTransaction(); // Update row with operators inside the transaction await tablesDB.UpdateRow( databaseId: "", tableId: "", rowId: "", data: new Dictionary { { "upvotes", Operator.Increment(1) }, { "lastModified", Operator.DateSetNow() } }, transactionId: tx.Id ); // Commit the transaction await tablesDB.UpdateTransaction(tx.Id, "commit"); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) ### Create a transaction tx = tablesDB.create_transaction ### Update row with operators inside the transaction result = tablesDB.update_row( '', '', '', { 'upvotes' => Operator.increment(1), 'lastModified' => Operator.dateSetNow() }, transaction_id: tx['$id'] ) ### Commit the transaction tablesDB.update_transaction(tx['$id'], 'commit') ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); // Create a transaction tablesDB.createTransaction(new CoroutineCallback<>((tx, txError) -> { if (txError != null) { txError.printStackTrace(); return; } // Update row with operators inside the transaction tablesDB.updateRow( "", "", "", Map.of( "upvotes", Operator.increment(1), "lastModified", Operator.dateSetNow() ), tx.getId(), new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } // Commit the transaction tablesDB.updateTransaction( tx.getId(), "commit", new CoroutineCallback<>((commitResult, commitError) -> { if (commitError != null) { commitError.printStackTrace(); return; } System.out.println("Transaction committed"); }) ); }) ); })); ``` {% /multicode %} #### Update multiple rows in a transaction This example demonstrates using `createOperations` to update multiple rows across different tables atomically, combining date and array operators in a single transaction. {% multicode %} ```client-web import { Client, TablesDB, Operator } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Create a transaction const tx = await tablesDB.createTransaction(); // Stage multiple operations at once using createOperations await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'update', databaseId: '', tableId: '', rowId: '', data: { lastActivity: Operator.dateSetNow() } }, { action: 'update', databaseId: '', tableId: '', rowId: '', data: { amount: Operator.increment(10), events: Operator.arrayAppend(['credit_added']) } } ] }); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); // Create a transaction const tx = await tablesDB.createTransaction(); // Stage multiple operations at once using createOperations await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'update', databaseId: '', tableId: '', rowId: '', data: { lastActivity: sdk.Operator.dateSetNow() } }, { action: 'update', databaseId: '', tableId: '', rowId: '', data: { amount: sdk.Operator.increment(10), events: sdk.Operator.arrayAppend(['credit_added']) } } ] }); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { // Create a transaction final tx = await tablesDB.createTransaction(); // Stage multiple operations at once using createOperations await tablesDB.createOperations( transactionId: tx.$id, operations: [ { 'action': 'update', 'databaseId': '', 'tableId': '', 'rowId': '', 'data': { 'lastActivity': Operator.dateSetNow() } }, { 'action': 'update', 'databaseId': '', 'tableId': '', 'rowId': '', 'data': { 'amount': Operator.increment(10), 'events': Operator.arrayAppend(['credit_added']) } } ], ); // Commit the transaction await tablesDB.updateTransaction(tx.$id, 'commit'); } on AppwriteException catch (e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { // Create a transaction let tx = try await tablesDB.createTransaction() // Stage multiple operations at once using createOperations _ = try await tablesDB.createOperations( transactionId: tx.$id, operations: [ [ "action": "update", "databaseId": "", "tableId": "", "rowId": "", "data": [ "lastActivity": Operator.dateSetNow() ] ], [ "action": "update", "databaseId": "", "tableId": "", "rowId": "", "data": [ "amount": Operator.increment(10), "events": Operator.arrayAppend(["credit_added"]) ] ] ] ) // Commit the transaction _ = try await tablesDB.updateTransaction( transactionId: tx.$id, status: "commit" ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.Operator suspend fun main() { val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) // Create a transaction val tx = tablesDB.createTransaction() // Stage multiple operations at once using createOperations tablesDB.createOperations( transactionId = tx.$id, operations = listOf( mapOf( "action" to "update", "databaseId" to "", "tableId" to "", "rowId" to "", "data" to mapOf( "lastActivity" to Operator.dateSetNow() ) ), mapOf( "action" to "update", "databaseId" to "", "tableId" to "", "rowId" to "", "data" to mapOf( "amount" to Operator.increment(10), "events" to Operator.arrayAppend(listOf("credit_added")) ) ) ) ) // Commit the transaction tablesDB.updateTransaction(tx.$id, "commit") } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" operator "github.com/appwrite/sdk-for-go/operator" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) // Create a transaction tx, err := tablesDB.CreateTransaction() if err != nil { log.Fatal(err) } // Stage multiple operations at once using createOperations _, err = tablesDB.CreateOperations( tx.Id, []map[string]any{ { "action": "update", "databaseId": "", "tableId": "", "rowId": "", "data": map[string]any{ "lastActivity": operator.DateSetNow(), }, }, { "action": "update", "databaseId": "", "tableId": "", "rowId": "", "data": map[string]any{ "amount": operator.Increment(10), "events": operator.ArrayAppend([]string{"credit_added"}), }, }, }, ) if err != nil { log.Fatal(err) } // Commit the transaction _, err = tablesDB.UpdateTransaction(tx.Id, "commit") if err != nil { log.Fatal(err) } } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); // Create a transaction $tx = $tablesDB->createTransaction(); // Stage multiple operations at once using createOperations $tablesDB->createOperations( transactionId: $tx['$id'], operations: [ [ 'action' => 'update', 'databaseId' => '', 'tableId' => '', 'rowId' => '', 'data' => [ 'lastActivity' => Operator::dateSetNow() ] ], [ 'action' => 'update', 'databaseId' => '', 'tableId' => '', 'rowId' => '', 'data' => [ 'amount' => Operator::increment(10), 'events' => Operator::arrayAppend(['credit_added']) ] ] ] ); // Commit the transaction $tablesDB->updateTransaction($tx['$id'], 'commit'); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.operator import Operator client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('')) tablesDB = TablesDB(client) ### Create a transaction tx = tablesDB.create_transaction() ### Stage multiple operations at once using createOperations tablesDB.create_operations( transaction_id=tx['$id'], operations=[ { 'action': 'update', 'databaseId': '', 'tableId': '', 'rowId': '', 'data': { 'lastActivity': Operator.dateSetNow() } }, { 'action': 'update', 'databaseId': '', 'tableId': '', 'rowId': '', 'data': { 'amount': Operator.increment(10), 'events': Operator.arrayAppend(['credit_added']) } } ] ) ### Commit the transaction tablesDB.update_transaction(tx['$id'], 'commit') ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); var tablesDB = new TablesDB(client); // Create a transaction var tx = await tablesDB.CreateTransaction(); // Stage multiple operations at once using createOperations await tablesDB.CreateOperations( transactionId: tx.Id, operations: new List> { new Dictionary { { "action", "update" }, { "databaseId", "" }, { "tableId", "" }, { "rowId", "" }, { "data", new Dictionary { { "lastActivity", Operator.DateSetNow() } } } }, new Dictionary { { "action", "update" }, { "databaseId", "" }, { "tableId", "" }, { "rowId", "" }, { "data", new Dictionary { { "amount", Operator.Increment(10) }, { "events", Operator.ArrayAppend(new[] { "credit_added" }) } } } } } ); // Commit the transaction await tablesDB.UpdateTransaction(tx.Id, "commit"); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tablesDB = TablesDB.new(client) ### Create a transaction tx = tablesDB.create_transaction ### Stage multiple operations at once using createOperations tablesDB.create_operations( transaction_id: tx['$id'], operations: [ { 'action' => 'update', 'databaseId' => '', 'tableId' => '', 'rowId' => '', 'data' => { 'lastActivity' => Operator.dateSetNow() } }, { 'action' => 'update', 'databaseId' => '', 'tableId' => '', 'rowId' => '', 'data' => { 'amount' => Operator.increment(10), 'events' => Operator.arrayAppend(['credit_added']) } } ] ) ### Commit the transaction tablesDB.update_transaction(tx['$id'], 'commit') ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import io.appwrite.Operator; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); // Create a transaction tablesDB.createTransaction(new CoroutineCallback<>((tx, txError) -> { if (txError != null) { txError.printStackTrace(); return; } // Stage multiple operations at once using createOperations List> operations = Arrays.asList( Map.of( "action", "update", "databaseId", "", "tableId", "", "rowId", "", "data", Map.of( "lastActivity", Operator.dateSetNow() ) ), Map.of( "action", "update", "databaseId", "", "tableId", "", "rowId", "", "data", Map.of( "amount", Operator.increment(10), "events", Operator.arrayAppend(List.of("credit_added")) ) ) ); tablesDB.createOperations( tx.getId(), operations, new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } // Commit the transaction tablesDB.updateTransaction( tx.getId(), "commit", new CoroutineCallback<>((commitResult, commitError) -> { if (commitError != null) { commitError.printStackTrace(); return; } System.out.println("Transaction committed"); }) ); }) ); })); ``` {% /multicode %} --- ## Order https://appwrite.io/docs/products/databases/order You can order results returned by Appwrite Databases by using an order query. For best performance, create an [index](/docs/products/databases/tables#indexes) on the column you plan to order by. ### Ordering one column {% #one-column %} When querying using the [listRows](/docs/references/cloud/client-web/tables#listRows) endpoint, you can specify the order of the rows returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.orderAsc('title'), ] }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final rows = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.orderAsc('title') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); let tablesDB = TablesDB(client) do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.orderAsc("title") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); val tablesDB = TablesDB(client) try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.orderAsc("title") ] ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```graphql query { tablesListRows( databaseId: "", tableId: "" queries: ["orderAsc(\"title\")"] ) { total rows { _id data } } } ``` {% /multicode %} ### Multiple columns {% #multiple-columns %} To sort based on multiple columns, simply provide multiple query methods. For better performance, create an index on the first column that you order by. In the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order. {% multicode %} ```js // Web SDK code example for sorting based on multiple columns // ... // List rows and sort based on multiple columns tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.orderAsc('title'), // Order first by title in ascending order Query.orderDesc('year'), // Then, order by year in descending order ] }); ``` ```dart // Flutter SDK code example for sorting based on multiple columns // ... // List rows and sort based on multiple columns try { final rows = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.orderAsc('title'), // Order by title in ascending order Query.orderDesc('year') // Order by year in descending order ] ); } on AppwriteException catch(e) { print(e); } ``` ```kotlin // Android SDK code example for sorting based on multiple columns // ... // List rows and sort based on multiple columns try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.orderAsc("title"), // Order by title in ascending order Query.orderDesc("year") // Order by year in descending order ] ); } catch (e: AppwriteException) { Log.e("Appwrite", e.message); } ``` ```swift // Apple SDK code example for sorting based on multiple columns // ... // List rows and sort based on multiple columns do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.orderAsc("title"), // Order by title in ascending order Query.orderDesc("year") // Order by year in descending order ] ); } catch { print(error.localizedDescription); } ``` ```graphql query { tablesListRows( databaseId: "", tableId: "", queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"] ) { total rows { _id data } } } ``` {% /multicode %} ### Ordering by sequence {% #sequence-ordering %} For numeric ordering based on insertion order, you can use the `$sequence` field, which Appwrite automatically adds to all rows. This field increments with each new insert. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.orderAsc('$sequence'), ] }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final rows = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.orderAsc('\$sequence') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.orderAsc("$sequence") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.orderAsc("\$sequence") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```graphql query { tablesListRows( databaseId: "", tableId: "" queries: ["orderAsc(\"$sequence\")"] ) { total rows { _id data } } } ``` {% /multicode %} The `$sequence` field is useful when you need: - Consistent ordering for pagination, especially with high-frequency inserts - Reliable insertion order tracking when timestamps might not be precise enough - Simple numeric ordering without managing custom counter fields --- ## Pagination https://appwrite.io/docs/products/databases/pagination As your database grows in size, you'll need to paginate results returned. Pagination improves performance by returning a subset of results that match a query at a time, called a page. By default, list operations return 25 items per page, which can be changed using the `Query.limit(25)` operator. There is no hard limit on the number of items you can request. However, beware that **large pages can degrade performance**. ### Offset pagination {% #offset-pagination %} Offset pagination works by dividing rows into `M` pages containing `N` rows. Every page is retrieved by skipping `offset = M * (N - 1)` items and reading the following `M` pages. Using `Query.limit()` and `Query.offset()` you can achieve offset pagination. With `Query.limit()` you can define how many rows can be returned from one request. The `Query.offset()` is number of records you wish to skip before selecting records. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Page 1 const page1 = await tablesDB.listRows( '', '', [ Query.limit(25), Query.offset(0) ] ); // Page 2 const page2 = await tablesDB.listRows( '', '', [ Query.limit(25), Query.offset(25) ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); final page1 = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.limit(25), Query.offset(0) ] ); final page2 = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.limit(25), Query.offset(25) ] ); } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) let page1 = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25), Query.offset(0) ] ) let page2 = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25), Query.offset(25) ] ) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) val page1 = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.limit(25), Query.offset(0) ] ) val page2 = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.limit(25), Query.offset(25) ] ) } ``` {% /multicode %} {% info title="Drawbacks" %} While traditional offset pagination is familiar, it comes with some drawbacks. The request gets slower as the number of records increases because the database has to read up to the offset number `M * (N - 1)` of rows to know where it should start selecting data. If the data changes frequently, offset pagination will also produce **missing and duplicate** results. {% /info %} ### Cursor pagination {% #cursor-pagination %} The cursor is a unique identifier for a row that points to where the next page should start. After reading a page of rows, pass the last row's ID into the `Query.cursorAfter(lastId)` query method to get the next page of rows. Pass the first row's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); const tablesDB = new TablesDB(client); // Page 1 const page1 = await tablesDB.listRows( '', '', [ Query.limit(25), ] ); const lastId = page1.rows[page1.rows.length - 1].$id; // Page 2 const page2 = await tablesDB.listRows( '', '', [ Query.limit(25), Query.cursorAfter(lastId), ] ); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); final page1 = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.limit(25) ] ); final lastId = page1.rows[page1.rows.length - 1].$id; final page2 = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.limit(25), Query.cursorAfter(lastId) ] ); } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) let page1 = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25) ] ) let lastId = page1.rows[page1.rows.count - 1].$id let page2 = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25), Query.cursorAfter(lastId) ] ) } ``` ```client-android-kotlin import android.util.Log import io.appwrite.AppwriteException import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) val page1 = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.limit(25) ] ) val lastId = page1.rows[page1.rows.size - 1].$id val page2 = tablesDB.listRows( databaseId = "", tableId = "", queries = [ Query.limit(25), Query.cursorAfter(lastId) ] ) } ``` {% /multicode %} ### When to use what? {% #when-to-use %} Offset pagination should be used for tables that rarely change. Offset pagination allow you to create indicator of the current page number and total page number. For example, a list with up to 20 pages or static data like a list of countries or currencies. Using offset pagination on large tables and frequently updated tables may result in slow performance and **missing and duplicate** results. Cursor pagination should be used for frequently updated tablesDB. It is best suited for lazy-loaded pages with infinite scrolling. For example, a feed, comment section, chat history, or high volume datasets. ### Skip totals for faster lists {% #skip-totals %} By default, list responses include an accurate `total` count. On large tables and filtered queries, calculating totals requires an extra database COUNT which can add latency. If your UI does not rely on exact totals (for example, infinite scroll or “load more”), you can skip counting totals by passing `total=false` to any list endpoint. The response keeps the same shape and sets `total` to `0` for compatibility. Recommendations: - Use with cursor pagination for the best performance and UX. - Keep the default behavior when you need “N results” or “Page X of Y”. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); const page = await tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.limit(25) ], total: false // Skip computing total count }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new sdk.TablesDB(client); const page = await tablesDB.listRows({ databaseId: '', tableId: '', queries: [ sdk.Query.limit(25) ], total: false // Skip computing total count }); ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.query import Query client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') tables_db = TablesDB(client) page = tables_db.list_rows( database_id='', table_id='', queries=[ Query.limit(25) ], total=False # Skip computing total count ) ``` ```server-ruby require 'appwrite' client = Appwrite::Client.new .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') tables_db = Appwrite::TablesDB.new(client) page = tables_db.list_rows( database_id: '', table_id: '', queries: [ Appwrite::Query.limit(25) ], total: false # Skip computing total count ) ``` ```server-deno import { Client, Query, TablesDB } from "https://deno.land/x/appwrite/mod.ts"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const tablesDB = new TablesDB(client); const page = await tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.limit(25) ], total: false // Skip computing total count }); ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $tablesDB = new TablesDB($client); $page = $tablesDB->listRows( databaseId: '', tableId: '', queries: [ Query::limit(25) ], total: false // Skip computing total count ); ``` ```server-go package main import ( "fmt" "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/query" ) func main() { client := appwrite.NewClient() client.SetEndpoint("https://.cloud.appwrite.io/v1") client.SetProject("") client.SetKey("") tablesDB := appwrite.NewTablesDB(client) page, err := tablesDB.ListRows( "", "", appwrite.WithListRowsQueries([]string{ query.Limit(25) }), appwrite.WithListRowsTotal(false), // Skip computing total count ) if err != nil { fmt.Println(err) } } ``` ```server-swift import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") let tablesDB = TablesDB(client) let page = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25) ], total: false // Skip computing total count ) } ``` ```server-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") val tablesDB = TablesDB(client) val page = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.limit(25) ), total = false // Skip computing total count ) } ``` ```server-java import io.appwrite.Client; import io.appwrite.Query; import io.appwrite.services.TablesDB; public class Main { public static void main(String[] args) throws Exception { Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); TablesDB tablesDB = new TablesDB(client); RowList page = tablesDB.listRows( "", "", Arrays.asList( Query.limit(25) ), false // Skip computing total count ); } } ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); final page = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.limit(25) ], total: false, // Skip computing total count ); } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) let page = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.limit(25) ], total: false // Skip computing total count ) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) val page = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.limit(25) ), total = false // Skip computing total count ) } ``` ```graphql query { tablesListRows( databaseId: "", tableId: "", queries: ["limit(25)"], total: false ) { total rows { _id data } } } ``` ```http GET /v1/tablesdb//tables//rows?total=false HTTP/1.1 Content-Type: application/json X-Appwrite-Project: ``` ```json { "total": 0, "rows": [ { "_id": "...", "data": { /* ... */ } } ] } ``` {% /multicode %} --- ## Database permissions https://appwrite.io/docs/products/databases/permissions Permissions define who can access rows in a table. By default **no permissions** are granted to any users, so no user can access any rows. Permissions exist at two levels, table level and row level permissions. In Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted. A user with access granted at either table level or row level will be able to access a row. Users **don't need access at both levels** to access rows. ### Table level {% #table-level %} Table level permissions apply to every row in the table. If a user has read, create, update, or delete permissions at the table level, the user can access **all rows** inside the table. Configure table level permissions by navigating to **Your table** > **Settings** > **Permissions**. [Learn more about permissions and roles](/docs/advanced/platform/permissions) ### Row level {% #row-level %} Row level permissions grant access to individual rows. If a user has read, create, update, or delete permissions at the row level, the user can access the **individual row**. Row level permissions are only applied if Row Security is enabled in the settings of your table. Enable row level permissions by navigating to **Your table** > **Settings** > **Row security**. Row level permissions are configured in individual rows. [Learn more about permissions and roles](/docs/advanced/platform/permissions) ### Common use cases {% #common-use-cases %} For examples of how to implement common permission patterns, including creating private rows that are only accessible to their creators, see the [permissions examples](/docs/advanced/platform/permissions#examples) in our platform documentation. --- ## Queries https://appwrite.io/docs/products/databases/queries Many list endpoints in Appwrite allow you to filter, sort, and paginate results using queries. Appwrite provides a common set of syntax to build queries. ### Query class {% #query-class %} Appwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation. ### Building queries {% #building-queries %} Queries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class. Each query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas. For example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`. {% info title="Default pagination behavior" %} By default, results are limited to the **first 25 items**. You can change this through [pagination](/docs/products/databases/pagination). {% /info %} {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greaterThan('year', 1999) ] }); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client(); const tablesDB = new sdk.TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey('') ; const promise = tablesDB.listRows({ databaseId: '', tableId: '', queries: [ sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']), sdk.Query.greaterThan('year', 1999) ] }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final rows = await tablesDB.listRows( '', '', [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greaterThan('year', 1999) ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.equal("title", value: ["Avatar", "Lord of the Rings"]), Query.greaterThan("year", value: 1999) ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); val tablesDB = TablesDB(client) try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.equal("title", listOf("Avatar", "Lord of the Rings")), Query.greaterThan("year", 1999) ) ) } catch (e: AppwriteException) { Log.e("Appwrite", e.message) } } ``` ```server-go package main import ( "fmt" "log" "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/query" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), appwrite.WithProject(""), appwrite.WithKey(""), ) tablesDB := appwrite.NewTablesDB(client) rows, err := tablesDB.ListRows( "", "", tablesDB.WithListRowsQueries([]string{ query.Equal("title", []string{"Avatar", "Lord of the Rings"}), query.GreaterThan("year", 1999), }), ) if err != nil { log.Fatal(err) } fmt.Printf("Rows: %+v\n", rows) } ``` ```server-php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey('') ; $tablesDB = new TablesDB($client); $result = $tables->listRows( '', '', [ Query::equal('title', ['Avatar', 'Lord of the Rings']), Query::greaterThan('year', 1999) ] ); ``` ```server-python from appwrite.client import Client from appwrite.query import Query from appwrite.services.tables_db import TablesDB client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') ) tablesDB = TablesDB(client) result = tablesDB.list_rows( '', '', [ Query.equal('title', ['Avatar', 'Lord of the Rings']), Query.greater_than('year', 1999) ] ) ``` ```graphql query { tablesListRows( databaseId: "", tableId: "" queries: [ "{\"method\":\"equal\",\"column\":\"title\",\"values\":[\"Avatar\",\"Lord of the Rings\"]}", "{\"method\":\"greaterThan\",\"column\":\"year\",\"values\":[1999]}" ] ) { total rows { _id data } } } ``` ```http GET /v1/tablesdb//tables//rows?queries[]=%7B%22method%22%3A%22equal%22%2C%22column%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22column%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: ``` {% /multicode %} ### Query operators {% #query-operators %} #### Select {% #select %} The `select` operator allows you to specify which columns should be returned from a row. This is essential for optimizing response size, controlling which relationship data loads, and only retrieving the data you need. {% multicode %} ```client-web Query.select(["name", "title"]) ``` ```client-flutter Query.select(["name", "title"]) ``` ```server-python Query.select(["name", "title"]) ``` ```server-ruby Query.select(["name", "title"]) ``` ```server-deno Query.select(["name", "title"]) ``` ```server-php Query::select(["name", "title"]) ``` ```client-apple Query.select(["name", "title"]) ``` ```server-go query.Select([]string{"name", "title"}) ``` ```http {"method":"select","values":["name","title"]} ``` {% /multicode %} ##### Select relationship data {% #relationship-select %} With [opt-in relationship loading](/docs/products/databases/relationships#performance-loading), you must explicitly select relationship data. This gives you fine-grained control over performance and payload size. ###### Get rows without relationships By default, rows return only their own fields: {% multicode %} ```client-web const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(['name', 'age'])] }); ``` ```client-flutter final doc = await tablesDB.getRow( databaseId: '', tableId: '', rowId: '', queries: [Query.select(["name", "age"])] ); ``` ```server-python doc = tablesDB.get_row( '', '', '', [Query.select(["name", "age"])] ) ``` ```server-ruby doc = tablesDB.get_row( '', '', '', [Query.select(["name", "age"])] ) ``` ```server-nodejs const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(['name', 'age'])] }); ``` ```server-php $doc = $tablesDB->getRow( '', '', '', [Query::select(["name", "age"])] ); ``` ```client-apple let doc = try await tablesDB.getRow( databaseId: "", tableId: "", rowId: "", queries: [Query.select(["name", "age"])] ) ``` ```http GET /v1/tablesdb//tables//rows/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22name%22%2C%22age%22%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: ``` {% /multicode %} ###### Load all relationship data Use the `*` wildcard to load all fields from related rows: {% multicode %} ```client-web const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(['*', 'reviews.*'])] }); ``` ```client-flutter final doc = await tablesDB.getRow( databaseId: '', tableId: '', rowId: '', queries: [Query.select(["*", "reviews.*"])] ); ``` ```server-python doc = tablesDB.get_row( '', '', '', [Query.select(["*", "reviews.*"])] ) ``` ```server-ruby doc = tablesDB.get_row( '', '', '', [Query.select(["*", "reviews.*"])] ) ``` ```server-nodejs const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(["*", "reviews.*"])] }); ``` ```server-php $doc = $tablesDB->getRow( '', '', '', [Query::select(["*", "reviews.*"])] ); ``` ```client-apple let doc = try await tablesDB.getRow( databaseId: "", tableId: "", rowId: "", queries: [Query.select(["*", "reviews.*"])] ) ``` ```http GET /v1/tablesdb//tables//rows/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22%2A%22%2C%22reviews.%2A%22%5D%7D HTTP/1.1 Content-Type: application/json X-Appwrite-Project: {"method":"select","values":["*","reviews.*"]} ``` {% /multicode %} ###### Select specific relationship fields For precise control, select only specific fields from related rows: {% multicode %} ```client-web const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])] }); ``` ```client-flutter final doc = await tablesDB.getRow( databaseId: '', tableId: '', rowId: '', queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ); ``` ```server-python doc = tablesDB.get_row( '', '', '', [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```server-ruby doc = tablesDB.get_row( '', '', '', [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```server-nodejs const doc = await tablesDB.getRow({ databaseId: '', tableId: '', rowId: '', queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])] }); // Result: { name: "John", age: 30, reviews: [{ author: "...", rating: 5 }] } ``` ```server-php $doc = $tablesDB->getRow( '', '', '', [Query::select(["name", "age", "reviews.author", "reviews.rating"])] ); ``` ```client-apple let doc = try await tablesDB.getRow( databaseId: "", tableId: "", rowId: "", queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])] ) ``` ```http ### Load specific fields from main and related rows {"method":"select","values":["name","age","reviews.author","reviews.rating"]} ``` {% /multicode %} ###### Load nested relationships You can also load relationships of relationships: {% multicode %} ```client-web Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```client-flutter Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```server-python Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```server-ruby Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```server-nodejs Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```server-php Query::select(["*", "reviews.*", "reviews.author.*"]) ``` ```client-apple Query.select(["*", "reviews.*", "reviews.author.*"]) ``` ```http {"method":"select","values":["*","reviews.*","reviews.author.*"]} ``` {% /multicode %} ##### Use selection patterns {% #select-patterns %} | Pattern | Description | Use case | |---------|-------------|----------| | `["field1", "field2"]` | Specific columns only | Minimize response size | | `["*"]` | All row columns | Get complete row data | | `["*", "relationName.*"]` | Row + all relationship fields | Load row with complete related data | | `["field1", "relationName.field2"]` | Specific fields from row and relationships | Precise data loading | | `["*", "relationName.field1", "relationName.field2"]` | All row fields + specific relationship fields | Partial relationship loading | | `["relationName.*", "relationName.nestedRelation.*"]` | Nested relationship loading | Load relationships of relationships | ##### Optimize performance {% #select-performance %} **Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse. **Control relationship loading** - Related rows are not loaded by default. Use explicit selection to load only the relationships you need. **Reduce database load** - Selecting fewer fields reduces database processing time, especially for large rows. {% info title="Related rows" %} By default, relationship columns contain only row IDs. To load the actual related row data, you must explicitly include relationship fields in your select query. Learn more about [relationship performance optimization](/docs/products/databases/relationships#performance-loading). {% /info %} #### Comparison operators {% #comparison %} ##### Equal {% #equal %} Returns row if column is equal to any value in the provided array. Also supported for spatial types. {% multicode %} ```client-web Query.equal("title", ["Iron Man"]) ``` ```client-flutter Query.equal("title", ["Iron Man"]) ``` ```server-python Query.equal("title", ["Iron Man"]) ``` ```server-ruby Query.equal("title", ["Iron Man"]) ``` ```server-deno Query.equal("title", ["Iron Man"]) ``` ```server-php Query::equal("title", ["Iron Man"]) ``` ```client-apple Query.equal("title", value: ["Iron Man"]) ``` ```server-go query.Equal("title", []string{"Iron Man"}) ``` ```http {"method":"equal","column":"title","values":["Iron Man"]} ``` {% /multicode %} ##### Not equal {% #not-equal %} Returns row if column is not equal to any value in the provided array. Also supported for spatial types. {% multicode %} ```client-web Query.notEqual("title", "Iron Man") ``` ```client-flutter Query.notEqual("title", "Iron Man") ``` ```server-python Query.not_equal("title", "Iron Man") ``` ```server-ruby Query.not_equal("title", "Iron Man") ``` ```server-deno Query.notEqual("title", "Iron Man") ``` ```server-php Query::notEqual("title", "Iron Man") ``` ```client-apple Query.notEqual("title", value: "Iron Man") ``` ```server-go query.NotEqual("title", "Iron Man") ``` ```http {"method":"notEqual","column":"title","values":"Iron Man"} ``` {% /multicode %} ##### Less than {% #less-than %} Returns row if column is less than the provided value. {% multicode %} ```client-web Query.lessThan("score", 10) ``` ```client-flutter Query.lessThan("score", 10) ``` ```server-python Query.less_than("score", 10) ``` ```server-ruby Query.less_than("score", 10) ``` ```server-deno Query.lessThan("score", 10) ``` ```server-php Query::lessThan("score", 10) ``` ```client-apple Query.lessThan("score", value: 10) ``` ```server-go query.LessThan("score", 10) ``` ```http {"method":"lessThan","column":"score","values":[10]} ``` {% /multicode %} ##### Less than or equal {% #less-than-equal %} Returns row if column is less than or equal to the provided value. {% multicode %} ```client-web Query.lessThanEqual("score", 10) ``` ```client-flutter Query.lessThanEqual("score", 10) ``` ```server-python Query.less_than_equal("score", 10) ``` ```server-ruby Query.less_than_equal("score", 10) ``` ```server-deno Query.lessThanEqual("score", 10) ``` ```server-php Query::lessThanEqual("score", 10) ``` ```client-apple Query.lessThanEqual("score", value: 10) ``` ```server-go query.LessThanEqual("score", 10) ``` ```http {"method":"lessThanEqual","column":"score","values":[10]} ``` {% /multicode %} ##### Greater than {% #greater-than %} Returns row if column is greater than the provided value. {% multicode %} ```client-web Query.greaterThan("score", 10) ``` ```client-flutter Query.greaterThan("score", 10) ``` ```server-python Query.greater_than("score", 10) ``` ```server-ruby Query.greater_than("score", 10) ``` ```server-deno Query.greaterThan("score", 10) ``` ```server-php Query::greaterThan("score", 10) ``` ```client-apple Query.greaterThan("score", value: 10) ``` ```server-go query.GreaterThan("score", 10) ``` ```http {"method":"greaterThan","column":"score","values":[10]} ``` {% /multicode %} ##### Greater than or equal {% #greater-than-equal %} Returns row if column is greater than or equal to the provided value. {% multicode %} ```client-web Query.greaterThanEqual("score", 10) ``` ```client-flutter Query.greaterThanEqual("score", 10) ``` ```server-python Query.greater_than_equal("score", 10) ``` ```server-ruby Query.greater_than_equal("score", 10) ``` ```server-deno Query.greaterThanEqual("score", 10) ``` ```server-php Query::greaterThanEqual("score", 10) ``` ```client-apple Query.greaterThanEqual("score", value: 10) ``` ```server-go query.GreaterThanEqual("score", 10) ``` ```http {"method":"greaterThanEqual","column":"score","values":[10]} ``` {% /multicode %} ##### Between {% #between %} Returns row if column value falls between the two values. The boundary values are inclusive and can be strings or numbers. {% multicode %} ```client-web Query.between("price", 5, 10) ``` ```client-flutter Query.between("price", 5, 10) ``` ```server-python Query.between("price", 5, 10) ``` ```server-ruby Query.between("price", 5, 10) ``` ```server-deno Query.between("price", 5, 10) ``` ```server-php Query::between("price", 5, 10) ``` ```client-apple Query.between("price", start: 5, end: 10) ``` ```server-go query.Between("price", 5, 10) ``` ```http {"method":"between","column":"price","values":[5,10]} ``` {% /multicode %} ##### Not between {% #not-between %} Returns rows if the column value is outside the range defined by the two values (strictly less than start OR strictly greater than end). Works with strings or numbers. Boundary values are excluded. {% multicode %} ```client-web Query.notBetween("price", 5, 10) ``` ```client-flutter Query.notBetween("price", 5, 10) ``` ```client-apple Query.notBetween("price", start: 5, end: 10) ``` ```client-android-kotlin Query.notBetween("price", 5, 10) ``` ```client-android-java Query.notBetween("price", 5, 10) ``` ```server-python Query.not_between("price", 5, 10) ``` ```server-ruby Query.not_between("price", 5, 10) ``` ```server-deno Query.notBetween("price", 5, 10) ``` ```server-nodejs Query.notBetween("price", 5, 10) ``` ```server-php Query::notBetween("price", 5, 10) ``` ```server-swift Query.notBetween("price", start: 5, end: 10) ``` ```http {"method":"notBetween","column":"price","values":[5,10]} ``` {% /multicode %} #### Null checks {% #null-checks %} ##### Is null {% #is-null %} Returns rows where column value is null. {% multicode %} ```client-web Query.isNull("name") ``` ```client-flutter Query.isNull("name") ``` ```server-python Query.is_null("name") ``` ```server-ruby Query.is_null("name") ``` ```server-deno Query.isNull("name") ``` ```server-php Query::isNull("name") ``` ```client-apple Query.isNull("name") ``` ```server-go query.IsNull("name") ``` ```http {"method":"isNull","column":"name"} ``` {% /multicode %} ##### Is not null {% #is-not-null %} Returns rows where column value is **not** null. {% multicode %} ```client-web Query.isNotNull("name") ``` ```client-flutter Query.isNotNull("name") ``` ```server-python Query.is_not_null("name") ``` ```server-ruby Query.is_not_null("name") ``` ```server-deno Query.isNotNull("name") ``` ```server-php Query::isNotNull("name") ``` ```client-apple Query.isNotNull("name") ``` ```server-go query.IsNotNull("name") ``` ```http {"method":"isNotNull","column":"name"} ``` {% /multicode %} #### String operations {% #string-operations %} ##### Starts with {% #starts-with %} Returns rows if a string column starts with a substring. {% multicode %} ```client-web Query.startsWith("name", "Once upon a time") ``` ```client-flutter Query.startsWith("name", "Once upon a time") ``` ```server-python Query.starts_with("name", "Once upon a time") ``` ```server-ruby Query.starts_with("name", "Once upon a time") ``` ```server-deno Query.startsWith("name", "Once upon a time") ``` ```server-php Query::startsWith("name", "Once upon a time") ``` ```client-apple Query.startsWith("name", value: "Once upon a time") ``` ```server-go query.StartsWith("name", "Once upon a time") ``` ```http {"method":"startsWith","column":"name","values":["Once upon a time"]} ``` {% /multicode %} ##### Not starts with {% #not-starts-with %} Returns rows if a string column does not start with a substring. {% multicode %} ```client-web Query.notStartsWith("name", "Once upon a time") ``` ```client-flutter Query.notStartsWith("name", "Once upon a time") ``` ```client-apple Query.notStartsWith("name", value: "Once upon a time") ``` ```client-android-kotlin Query.notStartsWith("name", "Once upon a time") ``` ```client-android-java Query.notStartsWith("name", "Once upon a time") ``` ```server-python Query.not_starts_with("name", "Once upon a time") ``` ```server-ruby Query.not_starts_with("name", "Once upon a time") ``` ```server-deno Query.notStartsWith("name", "Once upon a time") ``` ```server-nodejs Query.notStartsWith("name", "Once upon a time") ``` ```server-php Query::notStartsWith("name", "Once upon a time") ``` ```server-swift Query.notStartsWith("name", value: "Once upon a time") ``` ```http {"method":"notStartsWith","column":"name","values":["Once upon a time"]} ``` {% /multicode %} ##### Ends with {% #ends-with %} Returns rows if a string column ends with a substring. {% multicode %} ```client-web Query.endsWith("name", "happily ever after.") ``` ```client-flutter Query.endsWith("name", "happily ever after.") ``` ```server-python Query.ends_with("name", "happily ever after.") ``` ```server-ruby Query.ends_with("name", "happily ever after.") ``` ```server-deno Query.endsWith("name", "happily ever after.") ``` ```server-php Query::endsWith("name", "happily ever after.") ``` ```client-apple Query.endsWith("name", value: "happily ever after.") ``` ```server-go query.EndsWith("name", "happily ever after.") ``` ```http {"method":"endsWith","column":"name","values":["happily ever after."]} ``` {% /multicode %} ##### Not ends with {% #not-ends-with %} Returns rows if a string column does not end with a substring. {% multicode %} ```client-web Query.notEndsWith("name", "happily ever after.") ``` ```client-flutter Query.notEndsWith("name", "happily ever after.") ``` ```client-apple Query.notEndsWith("name", value: "happily ever after.") ``` ```client-android-kotlin Query.notEndsWith("name", "happily ever after.") ``` ```client-android-java Query.notEndsWith("name", "happily ever after.") ``` ```server-python Query.not_ends_with("name", "happily ever after.") ``` ```server-ruby Query.not_ends_with("name", "happily ever after.") ``` ```server-deno Query.notEndsWith("name", "happily ever after.") ``` ```server-nodejs Query.notEndsWith("name", "happily ever after.") ``` ```server-php Query::notEndsWith("name", "happily ever after.") ``` ```server-swift Query.notEndsWith("name", value: "happily ever after.") ``` ```http {"method":"notEndsWith","column":"name","values":["happily ever after."]} ``` {% /multicode %} ##### Contains {% #contains %} Returns rows if the array column contains the specified elements or if a string column contains the specified substring. Also supported for spatial types. {% multicode %} ```client-web // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```client-flutter // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```server-python ### For arrays Query.contains("ingredients", ['apple', 'banana']) ### For strings Query.contains("name", "Tom") ``` ```server-ruby ### For arrays Query.contains("ingredients", ['apple', 'banana']) ### For strings Query.contains("name", "Tom") ``` ```server-deno // For arrays Query.contains("ingredients", ['apple', 'banana']) // For strings Query.contains("name", "Tom") ``` ```server-php // For arrays Query::contains("ingredients", ['apple', 'banana']) // For strings Query::contains("name", "Tom") ``` ```client-apple // For arrays Query.contains("ingredients", value: ["apple", "banana"]) // For strings Query.contains("name", value: "Tom") ```server-go // For arrays query.Contains("ingredients", []string{"apple", "banana"}) // For strings query.Contains("name", "Tom") ``` ```http ### For arrays {"method":"contains","column":"ingredients","values":["apple","banana"]} ### For strings {"method":"contains","column":"name","values":["Tom"]} ``` {% /multicode %} ##### Not contains {% #not-contains %} Returns rows if the array column does not contain the specified elements, or if a string column does not contain the specified substring. Also supported for spatial types. {% multicode %} ```client-web // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```client-flutter // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```client-react-native // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```client-apple // For arrays Query.notContains("ingredients", value: ['apple', 'banana']) // For strings Query.notContains("name", value: "Tom") ``` ```client-android-kotlin // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```client-android-java // For arrays Query.notContains("ingredients", Arrays.asList("apple", "banana")) // For strings Query.notContains("name", "Tom") ``` ```server-python ### For arrays Query.not_contains("ingredients", ['apple', 'banana']) ### For strings Query.not_contains("name", "Tom") ``` ```server-ruby ### For arrays Query.not_contains("ingredients", ['apple', 'banana']) ### For strings Query.not_contains("name", "Tom") ``` ```server-deno // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```server-nodejs // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```server-php // For arrays Query::notContains("ingredients", ['apple', 'banana']) // For strings Query::notContains("name", "Tom") ``` ```server-dotnet // For arrays Query.NotContains("ingredients", new List { "apple", "banana" }) // For strings Query.NotContains("name", "Tom") ``` ```server-go // For arrays query.NotContains("ingredients", []string{"apple", "banana"}) // For strings query.NotContains("name", "Tom") ```server-dart // For arrays Query.notContains("ingredients", ['apple', 'banana']) // For strings Query.notContains("name", "Tom") ``` ```server-swift // For arrays Query.notContains("ingredients", value: ['apple', 'banana']) // For strings Query.notContains("name", value: "Tom") ``` ```server-kotlin // For arrays Query.notContains("ingredients", listOf("apple", "banana")) // For strings Query.notContains("name", "Tom") ``` ```http ### For arrays {"method":"notContains","column":"ingredients","values":["apple","banana"]} ### For strings {"method":"notContains","column":"name","values":["Tom"]} ``` {% /multicode %} ##### Search {% #search %} Searches string columns for provided keywords. Requires a [full-text index](/docs/products/databases/tables#indexes) on queried columns. {% multicode %} ```client-web Query.search("text", "key words") ``` ```client-flutter Query.search("text", "key words") ``` ```server-python Query.search("text", "key words") ``` ```server-ruby Query.search("text", "key words") ``` ```server-deno Query.search("text", "key words") ``` ```server-php Query::search("text", "key words") ``` ```client-apple Query.search("text", value: "key words") ``` ```server-go query.Search("text", "key words") ``` ```http {"method":"search","column":"text","values":["key words"]} ``` {% /multicode %} ##### Not search {% #not-search %} Returns rows if a string column does not match the full-text search query. Requires a [full-text index](/docs/products/databases/tables#indexes) on queried columns. {% multicode %} ```client-web Query.notSearch("text", "key words") ``` ```client-flutter Query.notSearch("text", "key words") ``` ```client-apple Query.notSearch("text", value: "key words") ``` ```client-android-kotlin Query.notSearch("text", "key words") ``` ```client-android-java Query.notSearch("text", "key words") ``` ```server-python Query.not_search("text", "key words") ``` ```server-ruby Query.not_search("text", "key words") ``` ```server-deno Query.notSearch("text", "key words") ``` ```server-nodejs Query.notSearch("text", "key words") ``` ```server-php Query::notSearch("text", "key words") ``` ```server-swift Query.notSearch("text", value: "key words") ``` ```http {"method":"notSearch","column":"text","values":["key words"]} ``` {% /multicode %} #### Logical operators {% #logical-operators %} ##### AND {% #and %} Returns row if it matches all of the nested sub-queries in the array passed in. {% multicode %} ```client-web Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```client-flutter Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```server-python Query.and_queries([ Query.less_than("size", 10), Query.greater_than("size", 5) ]) ``` ```server-ruby Query.and([ Query.less_than("size", 10), Query.greater_than("size", 5) ]) ``` ```server-deno Query.and([ Query.lessThan("size", 10), Query.greaterThan("size", 5) ]) ``` ```server-php Query::and([ Query::lessThan("size", 10), Query::greaterThan("size", 5) ]) ``` ```client-apple Query.and([ Query.lessThan("size", value: 10), Query.greaterThan("size", value: 5) ]) ``` ```server-go query.And([]string{ query.LessThan("size", 10), query.GreaterThan("size", 5), }) ``` ```http {"method":"and","values":[{"method":"lessThan","column":"size","values":[10]},{"method":"greaterThan","column":"size","values":[5]}]} ``` {% /multicode %} ##### OR {% #or %} Returns row if it matches any of the nested sub-queries in the array passed in. {% multicode %} ```client-web Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```client-flutter Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```server-python Query.or_queries([ Query.less_than("size", 5), Query.greater_than("size", 10) ]) ``` ```server-ruby Query.or([ Query.less_than("size", 5), Query.greater_than("size", 10) ]) ``` ```server-deno Query.or([ Query.lessThan("size", 5), Query.greaterThan("size", 10) ]) ``` ```server-php Query::or([ Query::lessThan("size", 5), Query::greaterThan("size", 10) ]) ``` ```client-apple Query.or([ Query.lessThan("size", value: 5), Query.greaterThan("size", value: 10) ]) ``` ```server-go query.Or([]string{ query.LessThan("size", 5), query.GreaterThan("size", 10), }) ``` ```http {"method":"or","values":[{"method":"lessThan","column":"size","values":[5]},{"method":"greaterThan","column":"size","values":[10]}]} ``` {% /multicode %} #### Ordering {% #ordering %} ##### Order descending {% #order-desc %} Orders results in descending order by column. Column must be indexed. {% multicode %} ```client-web Query.orderDesc("column") ``` ```client-flutter Query.orderDesc("column") ``` ```server-python Query.order_desc("column") ``` ```server-ruby Query.order_desc("column") ``` ```server-nodejs Query.orderDesc("column") ``` ```server-php Query::orderDesc("column") ``` ```client-apple Query.orderDesc("column") ``` ```server-go query.OrderDesc("attribute") ``` ```http {"method":"orderDesc","column":"column"} ``` {% /multicode %} ##### Order ascending {% #order-asc %} Orders results in ascending order by column. Column must be indexed. {% multicode %} ```client-web Query.orderAsc("column") ``` ```client-flutter Query.orderAsc("column") ``` ```server-python Query.order_asc("column") ``` ```server-ruby Query.order_asc("column") ``` ```server-nodejs Query.orderAsc("column") ``` ```server-php Query::orderAsc("column") ``` ```client-apple Query.orderAsc("column") ``` ```server-go query.OrderAsc("attribute") ``` ```http {"method":"orderAsc","column":"column"} ``` {% /multicode %} ##### Order random {% #order-random %} Orders results in random order. {% multicode %} ```client-web Query.orderRandom() ``` ```client-flutter Query.orderRandom() ``` ```server-python Query.order_random() ``` ```server-ruby Query.order_random() ``` ```server-nodejs Query.orderRandom() ``` ```server-php Query::orderRandom() ``` ```client-apple Query.orderRandom() ``` ```server-go query.OrderRandom() ``` ```http {"method":"orderRandom"} ``` {% /multicode %} #### Pagination {% #pagination %} ##### Limit {% #limit %} Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/pagination). {% multicode %} ```client-web Query.limit(25) ``` ```client-flutter Query.limit(25) ``` ```server-python Query.limit(25) ``` ```server-ruby Query.limit(25) ``` ```server-deno Query.limit(25) ``` ```server-php Query::limit(25) ``` ```client-apple Query.limit(25) ``` ```server-go query.Limit(25) ``` ```http {"method":"limit","values":[25]} ``` {% /multicode %} ##### Offset {% #offset %} Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/pagination). {% multicode %} ```client-web Query.offset(0) ``` ```client-flutter Query.offset(0) ``` ```server-python Query.offset(0) ``` ```server-ruby Query.offset(0) ``` ```server-deno Query.offset(0) ``` ```server-php Query::offset(0) ``` ```client-apple Query.offset(0) ``` ```server-go query.Offset(0) ``` ```http {"method":"offset","values":[0]} ``` {% /multicode %} ##### Cursor after {% #cursor-after %} Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/pagination). {% multicode %} ```client-web Query.cursorAfter("62a7...f620") ``` ```client-flutter Query.cursorAfter("62a7...f620") ``` ```server-python Query.cursor_after("62a7...f620") ``` ```server-ruby Query.cursor_after("62a7...f620") ``` ```server-deno Query.cursorAfter("62a7...f620") ``` ```server-php Query::cursorAfter("62a7...f620") ``` ```client-apple Query.cursorAfter("62a7...f620") ``` ```server-go query.CursorAfter("62a7...f620") ``` ```http {"method":"cursorAfter","values":["62a7...f620"]} ``` {% /multicode %} ##### Cursor before {% #cursor-before %} Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/pagination). {% multicode %} ```client-web Query.cursorBefore("62a7...a600") ``` ```client-flutter Query.cursorBefore("62a7...a600") ``` ```server-python Query.cursor_before("62a7...a600") ``` ```server-ruby Query.cursor_before("62a7...a600") ``` ```server-deno Query.cursorBefore("62a7...a600") ``` ```server-php Query::cursorBefore("62a7...a600") ``` ```client-apple Query.cursorBefore("62a7...a600") ``` ```server-go query.CursorBefore("62a7...a600") ``` ```http {"method":"cursorBefore","values":["62a7...a600"]} ``` {% /multicode %} ### Time helpers {% #time-helpers %} Built-in helpers for filtering by creation and update timestamps using ISO 8601 date-time strings (for example, "2025-01-01T00:00:00Z"). ##### Created before {% #created-before %} Returns rows created before the given date. {% multicode %} ```client-web Query.createdBefore("2025-01-01T00:00:00Z") ``` ```client-flutter Query.createdBefore("2025-01-01T00:00:00Z") ``` ```client-apple Query.createdBefore("2025-01-01T00:00:00Z") ``` ```client-android-kotlin Query.createdBefore("2025-01-01T00:00:00Z") ``` ```client-android-java Query.createdBefore("2025-01-01T00:00:00Z") ``` ```server-python Query.created_before("2025-01-01T00:00:00Z") ``` ```server-ruby Query.created_before("2025-01-01T00:00:00Z") ``` ```server-deno Query.createdBefore("2025-01-01T00:00:00Z") ``` ```server-nodejs Query.createdBefore("2025-01-01T00:00:00Z") ``` ```server-php Query::createdBefore("2025-01-01T00:00:00Z") ``` ```server-swift Query.createdBefore("2025-01-01T00:00:00Z") ``` ```http {"method":"createdBefore","values":["2025-01-01T00:00:00Z"]} ``` {% /multicode %} ##### Created after {% #created-after %} Returns rows created after the given date. {% multicode %} ```client-web Query.createdAfter("2025-01-01T00:00:00Z") ``` ```client-flutter Query.createdAfter("2025-01-01T00:00:00Z") ``` ```client-apple Query.createdAfter("2025-01-01T00:00:00Z") ``` ```client-android-kotlin Query.createdAfter("2025-01-01T00:00:00Z") ``` ```client-android-java Query.createdAfter("2025-01-01T00:00:00Z") ``` ```server-python Query.created_after("2025-01-01T00:00:00Z") ``` ```server-ruby Query.created_after("2025-01-01T00:00:00Z") ``` ```server-deno Query.createdAfter("2025-01-01T00:00:00Z") ``` ```server-nodejs Query.createdAfter("2025-01-01T00:00:00Z") ``` ```server-php Query::createdAfter("2025-01-01T00:00:00Z") ``` ```server-swift Query.createdAfter("2025-01-01T00:00:00Z") ``` ```http {"method":"createdAfter","values":["2025-01-01T00:00:00Z"]} ``` {% /multicode %} ##### Updated before {% #updated-before %} Returns rows updated before the given date. {% multicode %} ```client-web Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```client-flutter Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```client-apple Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```client-android-kotlin Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```client-android-java Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```server-python Query.updated_before("2025-01-01T00:00:00Z") ``` ```server-ruby Query.updated_before("2025-01-01T00:00:00Z") ``` ```server-deno Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```server-nodejs Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```server-php Query::updatedBefore("2025-01-01T00:00:00Z") ``` ```server-swift Query.updatedBefore("2025-01-01T00:00:00Z") ``` ```http {"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]} ``` {% /multicode %} ##### Updated after {% #updated-after %} Returns rows updated after the given date. {% multicode %} ```client-web Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```client-flutter Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```client-apple Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```client-android-kotlin Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```client-android-java Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```server-python Query.updated_after("2025-01-01T00:00:00Z") ``` ```server-ruby Query.updated_after("2025-01-01T00:00:00Z") ``` ```server-deno Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```server-nodejs Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```server-php Query::updatedAfter("2025-01-01T00:00:00Z") ``` ```server-swift Query.updatedAfter("2025-01-01T00:00:00Z") ``` ```http {"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]} ``` {% /multicode %} ### Geo queries and spatial operations {% #geo-queries %} Geo queries enable geographic operations on [spatial columns](/docs/products/databases/spatial). Coordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees. For conceptual information about spatial data types, spatial columns and indexing, see [Geo queries](/docs/products/databases/geo-queries). {% info title="Additional supported queries" %} In addition to the spatial-specific operations below, the query helpers `equal`, `notEqual`, `contains`, and `notContains` are also supported on spatial columns. This lets you match or exclude exact spatial values, check whether a geometry collection contains a geometry or not. {% /info %} #### Distance equal {% #distance-equal %} Returns rows where the spatial column is exactly the specified distance from a point. {% multicode %} ```client-web // Coordinates: [longitude, latitude] Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```client-flutter Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```client-react-native Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```client-apple // Query.distanceEqual(column, coordinates, distance) Query.distanceEqual("location", values: [-73.9851, 40.7589], distance: 200) ``` ```client-android-kotlin Query.distanceEqual("location", listOf(-73.9851, 40.7589), 200) ``` ```client-android-java Query.distanceEqual("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```server-python Query.distance_equal("location", [-73.9851, 40.7589], 200) ``` ```server-ruby Query.distance_equal("location", [-73.9851, 40.7589], 200) ``` ```server-deno Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```server-php Query::distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```server-dotnet Query.DistanceEqual("location", new List { -73.9851, 40.7589 }, 200) ``` ```server-go // query.DistanceEqual(column, coordinates, distance) query.DistanceEqual("location", []float64{-73.9851, 40.7589}, 200) ``` ```server-dart Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```server-swift Query.distanceEqual("location", [-73.9851, 40.7589], 200) ``` ```server-kotlin Query.distanceEqual("location", listOf(-73.9851, 40.7589), 200) ``` ```server-java Query.distanceEqual("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```http {"method":"distanceEqual","column":"location","values":[[-73.9851, 40.7589], 200]} ``` {% /multicode %} #### Distance not equal {% #distance-not-equal %} Returns rows where the spatial column is not exactly the specified distance from a point. {% multicode %} ```client-web Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```client-flutter Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```client-react-native Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```client-apple // Query.distanceNotEqual(column, coordinates, distance) Query.distanceNotEqual("location", values: [-73.9851, 40.7589], distance: 200) ``` ```client-android-kotlin Query.distanceNotEqual("location", listOf(-73.9851, 40.7589), 200) ``` ```client-android-java Query.distanceNotEqual("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```server-python Query.distance_not_equal("location", [-73.9851, 40.7589], 200) ``` ```server-ruby Query.distance_not_equal("location", [-73.9851, 40.7589], 200) ``` ```server-deno Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```server-php Query::distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```server-dotnet Query.DistanceNotEqual("location", new List { -73.9851, 40.7589 }, 200) ``` ```server-go // query.DistanceNotEqual(column, coordinates, distance) query.DistanceNotEqual("location", []float64{-73.9851, 40.7589}, 200) ``` ```server-dart Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```server-swift Query.distanceNotEqual("location", [-73.9851, 40.7589], 200) ``` ```server-kotlin Query.distanceNotEqual("location", listOf(-73.9851, 40.7589), 200) ``` ```server-java Query.distanceNotEqual("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```http {"method":"distanceNotEqual","column":"location","values":[[-73.9851, 40.7589], 200]} ``` {% /multicode %} #### Distance greater than {% #distance-greater-than %} Returns rows where the spatial column is more than the specified distance from a point. {% multicode %} ```client-web Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```client-flutter Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```client-react-native Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```client-apple // Query.distanceGreaterThan(column, coordinates, distance) Query.distanceGreaterThan("location", values: [-73.9851, 40.7589], distance: 200) ``` ```client-android-kotlin Query.distanceGreaterThan("location", listOf(-73.9851, 40.7589), 200) ``` ```client-android-java Query.distanceGreaterThan("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```server-python Query.distance_greater_than("location", [-73.9851, 40.7589], 200) ``` ```server-ruby Query.distance_greater_than("location", [-73.9851, 40.7589], 200) ``` ```server-deno Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```server-php Query::distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```server-dotnet Query.DistanceGreaterThan("location", new List { -73.9851, 40.7589 }, 200) ``` ```server-go // query.DistanceGreaterThan(column, coordinates, distance) query.DistanceGreaterThan("location", []float64{-73.9851, 40.7589}, 200) ``` ```server-dart Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```server-swift Query.distanceGreaterThan("location", [-73.9851, 40.7589], 200) ``` ```server-kotlin Query.distanceGreaterThan("location", listOf(-73.9851, 40.7589), 200) ``` ```http {"method":"distanceGreaterThan","column":"location","values":[[-73.9851, 40.7589], 200]} ``` {% /multicode %} #### Distance less than {% #distance-less-than %} Returns rows where the spatial column is less than the specified distance from a point. {% multicode %} ```client-web Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```client-flutter Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```client-react-native Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```client-apple // Query.distanceLessThan(column, coordinates, distance) Query.distanceLessThan("location", values: [-73.9851, 40.7589], distance: 200) ``` ```client-android-kotlin Query.distanceLessThan("location", listOf(-73.9851, 40.7589), 200) ``` ```client-android-java Query.distanceLessThan("location", Arrays.asList(-73.9851, 40.7589), 200) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```server-python Query.distance_less_than("location", [-73.9851, 40.7589], 200) ``` ```server-ruby Query.distance_less_than("location", [-73.9851, 40.7589], 200) ``` ```server-deno Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```server-php Query::distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```server-dotnet Query.DistanceLessThan("location", new List { -73.9851, 40.7589 }, 200) ``` ```server-go // query.DistanceLessThan(column, coordinates, distance) query.DistanceLessThan("location", []float64{-73.9851, 40.7589}, 200) ``` ```server-dart Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```server-swift Query.distanceLessThan("location", [-73.9851, 40.7589], 200) ``` ```server-kotlin Query.distanceLessThan("location", listOf(-73.9851, 40.7589), 200) ``` ```http {"method":"distanceLessThan","column":"location","values":[[-73.9851, 40.7589], 200]} ``` {% /multicode %} #### Intersects {% #intersects %} Returns rows where the spatial column intersects with the provided geometry. {% multicode %} ```client-web Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.intersects(column, geometry) Query.intersects("area", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.intersects("area", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.intersects("area", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.Intersects("area", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.Intersects(column, geometry) query.Intersects("area", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.intersects("area", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"intersects","column":"area","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Not intersects {% #not-intersects %} Returns rows where the spatial column does not intersect with the provided geometry. {% multicode %} ```client-web Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.notIntersects(column, geometry) Query.notIntersects("area", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.notIntersects("area", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.notIntersects("area", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.not_intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.not_intersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.NotIntersects("area", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.NotIntersects(column, geometry) query.NotIntersects("area", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.notIntersects("area", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.notIntersects("area", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"notIntersects","column":"area","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Overlaps {% #overlaps %} Returns rows where the spatial column overlaps with the provided geometry. {% multicode %} ```client-web Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.overlaps(column, geometry) Query.overlaps("zone", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.overlaps("zone", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.overlaps("zone", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.Overlaps("zone", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.Overlaps(column, geometry) query.Overlaps("zone", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.overlaps("zone", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"overlaps","column":"zone","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Not overlaps {% #not-overlaps %} Returns rows where the spatial column does not overlap with the provided geometry. {% multicode %} ```client-web Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.notOverlaps(column, geometry) Query.notOverlaps("zone", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.notOverlaps("zone", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.notOverlaps("zone", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.not_overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.not_overlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.NotOverlaps("zone", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.NotOverlaps(column, geometry) query.NotOverlaps("zone", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.notOverlaps("zone", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.notOverlaps("zone", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"notOverlaps","column":"zone","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Touches {% #touches %} Returns rows where the spatial column touches the provided geometry. {% multicode %} ```client-web Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.touches(column, geometry) Query.touches("boundary", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.touches("boundary", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.touches("boundary", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.Touches("boundary", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.Touches(column, geometry) query.Touches("boundary", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.touches("boundary", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"touches","column":"boundary","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Not touches {% #not-touches %} Returns rows where the spatial column does not touch the provided geometry. {% multicode %} ```client-web Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-flutter Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-react-native Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-apple // Query.notTouches(column, geometry) Query.notTouches("boundary", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```client-android-kotlin Query.notTouches("boundary", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```client-android-java Query.notTouches("boundary", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614), Arrays.asList(-73.9733, 40.7505), Arrays.asList(-73.9851, 40.7589) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-python Query.not_touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-ruby Query.not_touches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-deno Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-php Query::notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-dotnet Query.NotTouches("boundary", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 }, new List { -73.9733, 40.7505 }, new List { -73.9851, 40.7589 } }) ``` ```server-go // query.NotTouches(column, geometry) query.NotTouches("boundary", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}}) ``` ```server-dart Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-swift Query.notTouches("boundary", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]) ``` ```server-kotlin Query.notTouches("boundary", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589))) ``` ```http {"method":"notTouches","column":"boundary","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]} ``` {% /multicode %} #### Crosses {% #crosses %} Returns rows where the spatial column crosses the provided geometry. {% multicode %} ```client-web Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-flutter Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-react-native Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-apple // Query.crosses(column, geometry) Query.crosses("route", value: [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-android-kotlin Query.crosses("route", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614))) ``` ```client-android-java Query.crosses("route", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-python Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-ruby Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-deno Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-php Query::crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-dotnet Query.Crosses("route", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 } }) ``` ```server-go // query.Crosses(column, geometry) query.Crosses("route", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}}) ``` ```server-dart Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-swift Query.crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-kotlin Query.crosses("route", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614))) ``` ```http {"method":"crosses","column":"route","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]} ``` {% /multicode %} #### Not crosses {% #not-crosses %} Returns rows where the spatial column does not cross the provided geometry. {% multicode %} ```client-web Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-flutter Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-react-native Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-apple // Query.notCrosses(column, geometry) Query.notCrosses("route", value: [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```client-android-kotlin Query.notCrosses("route", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614))) ``` ```client-android-java Query.notCrosses("route", Arrays.asList( Arrays.asList(-73.9851, 40.7589), Arrays.asList(-73.9776, 40.7614) )) ``` ```server-nodejs const sdk = require('node-appwrite'); sdk.Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-python Query.not_crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-ruby Query.not_crosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-deno Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-php Query::notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-dotnet Query.NotCrosses("route", new List> { new List { -73.9851, 40.7589 }, new List { -73.9776, 40.7614 } }) ``` ```server-go // query.NotCrosses(column, geometry) query.NotCrosses("route", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}}) ``` ```server-dart Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-swift Query.notCrosses("route", [[-73.9851, 40.7589], [-73.9776, 40.7614]]) ``` ```server-kotlin Query.notCrosses("route", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614))) ``` ```http {"method":"notCrosses","column":"route","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]} ``` {% /multicode %} ### Complex queries {% #complex-queries %} You can create complex queries by combining AND and OR operations. For example, to find items that are either books under $20 or magazines under $10: {% multicode %} ```client-web const results = await tablesDB.listRows({ databaseId: '', tableId: '', queries: [ Query.or([ Query.and([ Query.equal('category', ['books']), Query.lessThan('price', 20) ]), Query.and([ Query.equal('category', ['magazines']), Query.lessThan('price', 10) ]) ]) ] }); ``` ```client-flutter final results = await tablesDB.listRows( '', '', [ Query.or([ Query.and([ Query.equal('category', ['books']), Query.lessThan('price', 20) ]), Query.and([ Query.equal('category', ['magazines']), Query.lessThan('price', 10) ]) ]) ] ); ``` ```server-python results = tablesDB.list_rows( database_id='', table_id='', queries=[ Query.or_queries([ Query.and_queries([ Query.equal('category', ['books']), Query.less_than('price', 20) ]), Query.and_queries([ Query.equal('category', ['magazines']), Query.less_than('price', 10) ]) ]) ] ) ``` ```server-go rows, err := tablesDB.ListRows( "", "", tablesDB.WithListRowsQueries([]string{ query.Or([]string{ query.And([]string{ query.Equal("category", []string{"books"}), query.LessThan("price", 20), }), query.And([]string{ query.Equal("category", []string{"magazines"}), query.LessThan("price", 10), }), }), }), ) if err != nil { log.Fatal(err) } ``` ```http {"method":"or","values":[{"method":"and","values":[{"method":"equal","column":"category","values":["books"]},{"method":"lessThan","column":"price","values":[20]}]},{"method":"and","values":[{"method":"equal","column":"category","values":["magazines"]},{"method":"lessThan","column":"price","values":[10]}]}]} ``` {% /multicode %} This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: books under $20 OR magazines under $10. Each condition within the OR is composed of two AND conditions - one for the category and one for the price threshold. The database will return rows that match either of these combined conditions. --- ## Start with Databases https://appwrite.io/docs/products/databases/quick-start {% section #create-database step=1 title="Create database" %} Head to your [Appwrite Console](https://cloud.appwrite.io/console/) and create a database and name it `Oscar`. Optionally, add a custom database ID. {% /section %} {% section #create-table step=2 title="Create table" %} Create a table and name it `My books`. Optionally, add a custom table ID. Navigate to **Columns** and create columns by clicking **Create column** and select **String**. Columns define the structure of your table's rows. Enter **Column key** and **Size**. For example, `title` and `100`. Navigate to **Settings** > **Permissions** and add a new role **Any**. Check the **CREATE** and **READ** permissions, so anyone can create and read rows. {% /section %} {% section #create-rows step=3 title="Create rows" %} To create a row use the `createRow` method. In the **Settings** menu, find your project ID and replace `` in the example. Navigate to the `Oscar` database, copy the database ID, and replace ``. Then, in the `My books` table, copy the table ID, and replace ``. {% multicode %} ```client-web import { Client, ID, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); const promise = tablesDB.createRow({ databaseId: '', tableId: '', rowId: ID.unique(), data: { title: "Hamlet" } }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final row = tablesDB.createRow( databaseId: '', tableId: '', rowId: ID.unique(), data: { "title": "Hamlet" } ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: ID.unique(), data: ["title" : "hamlet"] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val row = tablesDB.createRow( databaseId = "", tableId = "", rowId = ID.unique(), data = mapOf("title" to "hamlet"), ) } catch (e: Exception) { Log.e("Appwrite", "Error: " + e.message) } } ``` {% /multicode %} The response should look similar to this. ```json { "title": "Hamlet", "$id": "65013138dcd8618e80c4", "$permissions": [], "$createdAt": "2023-09-13T03:49:12.905+00:00", "$updatedAt": "2023-09-13T03:49:12.905+00:00", "$databaseId": "650125c64b3c25ce4bc4", "$tableId": "650125cff227cf9f95ad" } ``` {% /section %} {% section #list-rows step=4 title="List rows" %} To read and query data from your table, use the `listRows` endpoint. Like the previous step, replace ``, ``, and `` with their respective IDs. {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") const tablesDB = new TablesDB(client); const promise = tablesDB.listRows({ databaseId: "", tableId: "", queries: [ Query.equal('title', 'Hamlet') ] }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") final tablesDB = TablesDB(client); try { final rows = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.equal('title', 'Hamlet') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws{ let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.equal("title", value: "Hamlet") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.equal("title", "Hamlet") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` {% /multicode %} {% /section %} {% section #type-safety step=5 title="Type safety with models" %} For added type safety and better development experience, mobile and native SDKs support custom model types with the `nestedType` parameter. Define a data class or model that matches your table structure: {% multicode %} ```client-android-kotlin data class Book( val title: String, val author: String? = null, val pages: Int? = null, val isAvailable: Boolean = true ) val tablesDB = TablesDB(client) try { // Use nestedType for type-safe responses val books = tablesDB.listRows( databaseId = "", tableId = "", nestedType = Book::class.java ) for (book in books.rows) { Log.d("Appwrite", "Book: ${book.title} by ${book.author}") } } catch (e: AppwriteException) { Log.e("Appwrite", "Error: ${e.message}") } ``` ```client-apple struct Book: Codable { let title: String let author: String? let pages: Int? let isAvailable: Bool } let tablesDB = TablesDB(client) do { // Use nestedType for type-safe responses let books = try await tablesDB.listRows( databaseId: "", tableId: "", nestedType: Book.self ) for book in books.rows { print("Book: \(book.title) by \(book.author ?? "Unknown")") } } catch { print(error.localizedDescription) } ``` ```client-web // Web SDK supports generics for type safety interface Book { title: string; author?: string; pages?: number; isAvailable: boolean; } const tablesDB = new TablesDB(client); try { const books = await tablesDB.listRows({ databaseId: '', tableId: '' }); books.rows.forEach(book => { console.log(`Book: ${book.title} by ${book.author}`); }); } catch (error) { console.log(error); } ``` {% /multicode %} {% info title="Automatic type generation" %} You can automatically generate type definitions for your tables using the [Appwrite CLI type generation](/docs/products/databases/type-generation) feature. Run `appwrite types collection` to generate models for your collections. {% /info %} ##### Model methods Models returned by native SDKs include helpful methods for data manipulation: {% tabs %} {% tabsitem #kotlin title="Kotlin/Java" %} ```kotlin val book = books.rows.first() // Convert to Map for debugging or manual manipulation val bookMap = book.toMap() Log.d("Appwrite", bookMap.toString()) // Create model from Map val bookData = mapOf( "title" to "The Great Gatsby", "author" to "F. Scott Fitzgerald" ) val newBook = Book.from(bookData, Book::class.java) ``` {% /tabsitem %} {% tabsitem #swift title="Swift" %} ```swift let book = books.rows.first! // Convert to dictionary for debugging let bookMap = book.toMap() print(bookMap) // Create model from dictionary let bookData: [String: Any] = [ "title": "The Great Gatsby", "author": "F. Scott Fitzgerald" ] let newBook = Book.from(map: bookData) // Encode to JSON let jsonData = try JSONEncoder().encode(book) let jsonString = String(data: jsonData, encoding: .utf8) ``` {% /tabsitem %} {% /tabs %} {% /section %} --- ## Relationships https://appwrite.io/docs/products/databases/relationships Relationships describe how rows in different tables are associated, so that related rows can be read, updated, or deleted together. Entities in real-life often associate with each other in an organic and logical way, like a person and their dog, an album and its songs, or friends in a social network. These types of association between entities can be modeled in Appwrite using relationships. {% info title="Experimental feature" %} Appwrite Relationships is an experimental feature. The API and behavior are subject to change in future versions. {% /info %} ### Relationship columns {% #relationship-columns %} Relationships are represented in a table using **relationship columns**. The relationship column contains the ID of related rows, which it references during read, update, and delete operations. This column is **null** if a row has no related rows. ### When to use a relationship {% #when-to-use-relationships %} Relationships help reduce redundant information. For example, a user can create many posts in your app. You can model this without relationships by keeping a copy of the user's information in all the rows representing posts, but this creates a lot of duplicate information in your database about the user. ### Benefits of relationships {% #benefit-of-relationships %} Duplicated records waste storage, but more importantly, makes the database much harder to maintain. If the user changes their user name, you will have to update dozens or hundreds of records, a problem commonly known as an update anomaly in tablesDB. You can avoid duplicate information by storing users and posts in separate tables and relating a user and their posts through a relationship. ### Tradeoffs {% #trade-offs %} Consider using relationships when the same information is found in multiple places to avoid duplicates. However, relationships come with the tradeoff of slowing down queries. For applications where the best read and write performance is important, it may be acceptable to tolerate duplicate data. ### Opt-in loading {% #performance-loading %} By default, Appwrite returns only a row's own fields when you retrieve rows. Related rows are **not automatically loaded** unless you explicitly request them using query selection. This eliminates unintentional payload bloat and gives you precise control over performance. {% arrow_link href="/docs/products/databases/queries#relationship-select" %} Learn how to load relationships with queries {% /arrow_link %} ### Directionality {% #directionality %} Appwrite relationships can be one-way or two-way. | Type | Description | | -------- | ----------------------------------------------------------------------------------------------------------------- | | One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure. | | Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure. | ### Types {% #types %} Appwrite provides four different relationship types to enforce different associative rules between rows. | Type | Description | | ----------- | ----------------------------------------------------------------------- | | One-to-one | A row can only be related to one and only one row. | | One-to-many | A row can be related to many other rows. | | Many-to-one | Many rows can be related to a single row. | | Many-to-many| A row can be related to many other rows. | ### On-delete {% #on-delete %} Appwrite also allows you to define the behavior of a relationship when a row is deleted. | Type | Description | | ---------- | ---------------------------------------------------------------------- | | Restrict | If a row has at least one related row, it cannot be deleted.| | Cascade | If a row has related rows, when it is deleted, the related rows are also deleted.| | Set null | If a row has related rows, when it is deleted, the related rows are kept with their relationship column set to null.| ### Creating relationships {% #create-relationships %} You can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server) {% tabs %} {% tabsitem #console title="Console" %} You can create relationships in the Appwrite Console by adding a relationship column to a table. 1. In your project, navigate to **Databases** > **Select your database** > **Select your table** > **Columns** > **Create column**. 2. Select **Relationship** as the column type. 3. In the **Relationship** modal, select the [relationship type](#types) and pick the related table and columns. 4. Pick relationship column key(s) to represent the related table. Relationship column keys are used to reference the related table in queries, so pick something that's intuitive and easy to remember. 5. Select desired [on delete](#on-delete) behavior. 6. Click the **Create** button to create the relationship. {% /tabsitem %} {% tabsitem #sdk title="SDK" %} Here's an example that adds a relationship between the tables **movies** and **reviews**. A relationship column with the key `reviews` is added to the movies table, and another relationship column with the key `movie` is added to the reviews table. {% multicode %} ```js const { Client, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); tablesDB.createRelationshipColumn( 'marvel', // Database ID 'movies', // Table ID 'reviews', // Related table ID 'oneToMany', // Relationship type true, // Is two-way 'reviews', // Column key 'movie', // Two-way column key 'cascade' // On delete action ); ``` ```php use \Appwrite\Client; use \Appwrite\Services\TablesDB; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject(''); // Your project ID $tablesDB = new TablesDB($client); $tables->createRelationshipColumn( databaseId: 'marvel', // Database ID tableId: 'movies', // Table ID relatedTableId: 'reviews', // Related table ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Column key twoWayKey: 'movie', // Two-way column key onDelete: 'cascade' // On delete action ); ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = (Client() .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('')) # Your project ID tablesDB = TablesDB(client) tablesDB.create_relationship_column( database_id='marvel', # Database ID table_id='movies', # Table ID related_table_id='reviews', # Related table ID type='oneToMany', # Relationship type two_way=True, # Is two-way key='reviews', # Column key two_way_key='movie', # Two-way column key on_delete='cascade' # On delete action ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1')# Your API Endpoint .set_project('') # Your project ID tablesDB = TablesDB.new(client) tablesDB.create_relationship_column( database_id: 'marvel', # Database ID table_id: 'movies', # Table ID related_table_id: 'reviews', # Related table ID type: 'oneToMany', # Relationship type two_way: true, # Is two-way key: 'reviews', # Column key two_way_key: 'movie', # Two-way column key on_delete: 'cascade' # On delete action ) ``` ```deno import { Client, TablesDB } from "npm:node-appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject(""); // Your project ID const tablesDB = new TablesDB(client); tablesDB.createRelationshipColumn( "marvel", // Database ID "movies", // Table ID "reviews", // Related table ID "oneToMany", // Relationship type true, // Is two-way "reviews", // Column key "movie", // Two-way column key "cascade" // On delete action ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final tablesDB = TablesDB(client); await tablesDB.createRelationshipColumn( databaseId: 'marvel', // Database ID tableId: 'movies', // Table ID relatedTableId: 'reviews', // Related table ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Column key twoWayKey: 'movie', // Two-way column key onDelete: 'cascade', // On delete action ); ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val tablesDB = TablesDB(client) tablesDB.createRelationshipColumn( databaseId = "marvel", // Database ID tableId = "movies", // Table ID relatedTableId = "reviews", // Related table ID type = "oneToMany", // Relationship type twoWay = true, // Is two-way key = "reviews", // Column key twoWayKey = "movie", // Two-way column key onDelete = "cascade" // On delete action ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let tablesDB = TablesDB(client) tablesDB.createRelationshipColumn( databaseId: "marvel", // Database ID tableId: "movies", // Table ID relatedTableId: "reviews", // Related table ID type: "oneToMany", // Relationship type twoWay: true, // Is two-way key: "reviews", // Column key twoWayKey: "movie", // Two-way column key onDelete: "cascade" // On delete action ) ``` ```csharp using Appwrite; using Appwrite.Services; var client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject(""); var tablesDB = new TablesDB(client); await tablesDB.CreateRelationshipColumn( databaseId: "marvel", tableId: "movies", relatedTableId: "reviews", type: "oneToMany", twoWay: true, key: "reviews", twoWayKey: "movie", onDelete: "cascade"); ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Creating rows {% #create-rows %} If a table has relationship columns, you can create rows in two ways. You create both parent and child at the same time using a **nested** syntax or link parent and child rows through **references***. {% tabs %} {% tabsitem #nested title="Nested" %} You can create both the **parent** and **child** at once in a relationship by nesting data. {% multicode %} ```js const { Client, ID, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); await tablesDB.createRow({ databaseId: 'marvel', tableId: 'movies', rowId: ID.unique(), data: { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!' }, { author: 'Alice', text: 'Loved it!' } ] } }); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final tablesDB = TablesDB(client); await tablesDB.createRow( databaseId: 'marvel', tableId: 'movies', rowId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text': 'Great movie!' }, { 'author': 'Alice', 'text': 'Loved it!' } ] }, ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let tablesDB = TablesDB(client: client) tablesDB.createRow( databaseId: "marvel", tableId: "movies", rowId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!" ], [ "author": "Alice", "text": "Loved it!" ] ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val tablesDB = TablesDB(client) tablesDB.createRow( databaseId = "marvel", tableId = "movies", rowId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( mapOf("author" to "Bob", "text" to "Great movie!"), mapOf("author" to "Alice", "text" to "Loved it!") ) ) ) ``` {% /multicode %} #### Edge case behaviors {% #edge-case-behaviors %} - If a nested child row is included and **no child row ID** is provided, the child row will be given a unique ID. - If a nested child row is included and **no conflicting child row ID** exists, the child row will be **created**. - If a nested child row is included and the **child row ID already exists**, the child row will be **updated**. {% /tabsitem %} {% tabsitem #reference title="Reference" %} If the child rows are already present in the related table, you can create the parent and **reference the child rows** using their IDs. Here's an example connecting reviews to a movie. {% multicode %} ```js const { Client, ID, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID await tablesDB.createRow({ databaseId: 'marvel', tableId: 'movies', rowId: ID.unique(), data: { title: 'Spiderman', year: 2002, reviews: [ '', '' }); } ) ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID final tablesDB = TablesDB(client); await tablesDB.createRow( databaseId: 'marvel', tableId: 'movies', rowId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ '', '' ] }, ) ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID let tablesDB = TablesDB(client: client) tablesDB.createRow( databaseId: "marvel", tableId: "movies", rowId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ "", "" ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.ID val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID val tablesDB = TablesDB(client) tablesDB.createRow( databaseId = "marvel", tableId = "movies", rowId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "", "" ) ) ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Queries {% #queries %} Queries are currently not available in the experimental version of Appwrite Relationships but will be added in a later version. ### Update relationships {% #update %} Relationships can be updated by updating the relationship column. {% multicode %} ```js const { Client, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); await tablesDB.updateRow( 'marvel', 'movies', 'spiderman', { title: 'Spiderman', year: 2002, reviews: [ 'review4', 'review5' ] } ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); await tablesDB.updateRow( databaseId: 'marvel', tableId: 'movies', rowId: 'spiderman', data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ 'review4', 'review5' ] }, ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client: client) tablesDB.updateRow( databaseId: "marvel", tableId: "movies", rowId: "spiderman", data: [ "title": "Spiderman", "year": 2002, "reviews": [ "review4", "review5" ] ] ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.updateRow( databaseId = "marvel", tableId = "movies", rowId = "spiderman", data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "review4", "review5" ) ) ) ``` {% /multicode %} ### Delete relationships {% #delete %} #### Unlink relationships, retain rows {% #unlink %} If you need to unlink rows in a relationship but retain the rows, you can do this by **updating the relationship column** and removing the ID of the related row. If a row can be related to **only one row**, you can delete the relationship by setting the relationship column to `null`. If a row can be related to **more than one row**, you can delete the relationship by setting the relationship column to an empty list. #### Delete relationships and rows {% #delete-both %} If you need to delete the rows as well as unlink the relationship, the approach depends on the [on-delete behavior](#on-delete) of a relationship. If the on-delete behavior is **restrict**, the link between the rows needs to be deleted first before the rows can be deleted **individually**. If the on-delete behavior is **set null**, deleting a row will leave related rows in place with their relationship column **set to null**. If you wish to also delete related rows, they must be deleted **individually**. If the on-delete behavior is **cascade**, deleting the parent rows also deletes **related child rows**, except for many-to-one relationships. In many-to-one relationships, there are multiple parent rows related to a single child row, and when the child row is deleted, the parents are deleted in cascade. {% multicode %} ```js const { Client, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); await tablesDB.deleteRow( 'marvel', 'movies', 'spiderman' ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); await tablesDB.deleteRow( databaseId: 'marvel', tableId: 'movies', rowId: 'spiderman' ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client: client) tablesDB.deleteRow( databaseId: "marvel", tableId: "movies", rowId: "spiderman" ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) tablesDB.deleteRow( databaseId = "marvel", tableId = "movies", rowId = "spiderman" ) ``` {% /multicode %} ### Permissions {% #permissions %} To access rows in a relationship, you must have permission to access both the parent and child rows. When creating both the parent and child rows, the child row will **inherit permissions** from its parent. You can also provide explicit permissions to the child row if they should be **different from their parent**. {% multicode %} ```js const { Client, ID, TablesDB } = require('node-appwrite'); const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const tablesDB = new TablesDB(client); await tablesDB.createRow( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!', $permissions: [ Permission.read(Role.any()) ] }, ] } ); ``` ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); await tablesDB.createRow( databaseId: 'marvel', tableId: 'movies', rowId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text': 'Great movie!', '\$permissions': [ Permission.read(Role.any()) ] }, ] }, ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client: client) tablesDB.createRow( databaseId: "marvel", tableId: "movies", rowId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] ); ``` ```kotlin import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client: client) tablesDB.createRow( databaseId: "marvel", tableId: "movies", rowId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] ); ``` {% /multicode %} When creating, updating, or deleting in a relationship, you must have permission to access all rows referenced. If the user does not have read permission to any row, an exception will be thrown. ### Limitations {% #limitations %} Relationships can be nested between tables, but are restricted to a **max depth of three levels**. Relationship column key, type, and directionality can't be updated. On-delete behavior is the only option that can be updated for relationship columns. --- ## Rows https://appwrite.io/docs/products/databases/rows Each piece of data or information in Appwrite Databases is a row. Rows have a structure defined by the parent table. ### Create rows {% #create-rows %} {% info title="Permissions required" %} You must grant _create_ permissions to users at the _table level_ before users can create rows. [Learn more about permissions](#permissions) {% /info %} In most use cases, you will create rows programmatically. {% multicode %} ```client-web import { Client, ID, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); const promise = tablesDB.createRow({ databaseId: '', tableId: '', rowId: ID.unique(), data: {} }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final row = tablesDB.createRow( databaseId: '', tableId: '', rowId: ID.unique(), data: {} ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let row = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: ID.unique(), data: [:] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val row = tablesDB.createRow( databaseId = "", tableId = "", rowId = ID.unique(), data = mapOf("a" to "b"), ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql mutation { tablesCreateRow( databaseId: "", tableId: "", rowId: "", data: "{}" ) { _id _tableId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} During testing, you might prefer to create rows in the Appwrite Console. To do so, navigate to the **Rows** tab of your table and click the **Add row** button. ### List rows {% #list-rows %} {% info title="Permissions required" %} You must grant _read_ permissions to users at the _table level_ before users can read rows. [Learn more about permissions](#permissions) {% /info %} Rows can be retrieved using the [List rows](/docs/references/cloud/client-web/tables#listRows) endpoint. Results can be filtered, sorted, and paginated using Appwrite's shared set of query methods. You can find a full guide on querying in the [Queries Guide](/docs/products/databases/queries). By default, results are limited to the _first 25 items_. You can change this through [pagination](/docs/products/databases/pagination). {% info title="Speed up lists by skipping totals" %} If your UI doesn't need an exact total, set the `total` flag to `false` on list calls. The response keeps the same shape and sets `total` to `0`. This reduces latency for large tables and filtered queries. Learn more in [Pagination: Skip totals](/docs/products/databases/pagination#skip-totals). {% /info %} {% multicode %} ```client-web import { Client, Query, TablesDB } from "appwrite"; const client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") const tablesDB = new TablesDB(client); let promise = tablesDB.listRows({ databaseId: "", tableId: "", queries: [ Query.equal('title', 'Avatar') ] }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") final tablesDB = TablesDB(client); try { final rows = await tablesDB.listRows( databaseId: '', tableId: '', queries: [ Query.equal('title', 'Avatar') ] ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let rows = try await tablesDB.listRows( databaseId: "", tableId: "", queries: [ Query.equal("title", value: "Avatar") ] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.Query import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val rows = tablesDB.listRows( databaseId = "", tableId = "", queries = listOf( Query.equal("title", "Avatar") ) ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql query { tablesListRows( databaseId: "", tableId: "", queries: ["equal(\"title\", [\"Avatar\"])"] ) { total rows { _id data } } } ``` {% /multicode %} ### Update row {% #update-row %} {% info title="Permissions required" %} You must grant _update_ permissions to users at the _table level_ or _row level_ before users can update rows. [Learn more about permissions](#permissions) {% /info %} In most use cases, you will update rows programmatically. {% multicode %} ```client-web import { Client, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); const promise = tablesDB.updateRow( '', '', '', { title: 'Updated Title' } ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final row = await tablesDB.updateRow( databaseId: '', tableId: '', rowId: '', data: { 'title': 'Updated Title' } ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let row = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: ["title": "Updated Title"] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val row = tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf("title" to "Updated Title"), ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ```graphql mutation { tablesDBUpdateRow( databaseId: "", tableId: "", rowId: "", data: "{\"title\": \"Updated Title\"}" ) { _id _tableId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} ### Upsert rows {% #upsert-rows %} Upsert is a combination of "update" and "insert" operations. It creates a new row if one doesn't exist with the given ID, or updates an existing row if it does exist. In most use cases, you will upsert rows programmatically. {% info title="Permissions required" %} You must grant _create_ permissions to users at the _table level_, and _update_ permissions to users at the _table_ or _row_ level before users can upsert rows. [Learn more about permissions](#permissions) {% /info %} {% multicode %} ```client-web import { Client, ID, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); const promise = tablesDB.upsertRow( '', '', ID.unique(), {} ); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() async { final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final tablesDB = TablesDB(client); try { final row = tablesDB.upsertRow( databaseId: '', tableId: '', rowId: ID.unique(), data: {} ); } on AppwriteException catch(e) { print(e); } } ``` ```client-apple import Appwrite import AppwriteModels func main() async throws { let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") let tablesDB = TablesDB(client) do { let row = try await tablesDB.upsertRow( databaseId: "", tableId: "", rowId: ID.unique(), data: [:] ) } catch { print(error.localizedDescription) } } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") val tablesDB = TablesDB(client) try { val row = tablesDB.upsertRow( databaseId = "", tableId = "", rowId = ID.unique(), data = mapOf("a" to "b"), ) } catch (e: AppwriteException) { Log.e("Appwrite", "Error: " + e.message) } } ``` ```graphql mutation { tablesUpsertRow( databaseId: "", tableId: "", rowId: "", data: "{}" ) { _id _tableId _databaseId _createdAt _updatedAt _permissions data } } ``` {% /multicode %} ### Type safety with models {% #type-safety %} Mobile and native SDKs provide type safety when working with rows through the `nestedType` parameter. This allows you to specify custom model types for complete auto-completion and type safety. #### Define your model Create a data class or struct that matches your table structure: {% tabs %} {% tabsitem #kotlin title="Kotlin/Java" %} ```kotlin data class Book( val title: String, val author: String, val publishedYear: Int? = null, val genre: List? = null, val isAvailable: Boolean = true ) ``` {% /tabsitem %} {% tabsitem #swift title="Swift" %} ```swift struct Book: Codable { let title: String let author: String let publishedYear: Int? let genre: [String]? let isAvailable: Bool } ``` {% /tabsitem %} {% tabsitem #web title="Web/Node" %} ```typescript interface Book { title: string; author: string; publishedYear?: number; genre?: string[]; isAvailable: boolean; } ``` {% /tabsitem %} {% /tabs %} #### Using type-safe operations Use the `nestedType` parameter for full type safety in native SDKs, or generics in web SDKs: {% multicode %} ```client-android-kotlin val tablesDB = TablesDB(client) try { // Create with type safety val newBook = tablesDB.createRow( databaseId = "", tableId = "", rowId = ID.unique(), data = mapOf( "title" to "The Great Gatsby", "author" to "F. Scott Fitzgerald", "isAvailable" to true ), nestedType = Book::class.java ) // List with type safety val books = tablesDB.listRows( databaseId = "", tableId = "", nestedType = Book::class.java ) // Now you have full type safety for (book in books.rows) { Log.d("Appwrite", "Book: ${book.title} by ${book.author}") if (book.isAvailable) { Log.d("Appwrite", "Available for checkout") } } } catch (e: AppwriteException) { Log.e("Appwrite", "Error: ${e.message}") } ``` ```client-apple let tablesDB = TablesDB(client) do { // Create with type safety let newBook = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: ID.unique(), data: [ "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "isAvailable": true ], nestedType: Book.self ) // List with type safety let books = try await tablesDB.listRows( databaseId: "", tableId: "", nestedType: Book.self ) // Now you have full type safety for book in books.rows { print("Book: \(book.title) by \(book.author)") if book.isAvailable { print("Available for checkout") } } } catch { print(error.localizedDescription) } ``` ```client-web const tablesDB = new TablesDB(client); try { // Create with generics const newBook = await tablesDB.createRow({ databaseId: '', tableId: '', rowId: ID.unique(), data: { title: "The Great Gatsby", author: "F. Scott Fitzgerald", isAvailable: true } }); // List with generics const books = await tablesDB.listRows({ databaseId: '', tableId: '' }); // TypeScript provides full type safety books.rows.forEach(book => { console.log(`Book: ${book.title} by ${book.author}`); if (book.isAvailable) { console.log("Available for checkout"); } }); } catch (error) { console.log(error); } ``` {% /multicode %} #### Model methods Models returned by native SDKs include helpful utility methods: {% tabs %} {% tabsitem #kotlin-methods title="Kotlin/Java" %} ```kotlin val book = books.rows.first() // Convert model to Map for debugging or manual manipulation val bookMap = book.toMap() Log.d("Appwrite", "Book data: ${bookMap}") // Create model instance from Map data val bookData = mapOf( "title" to "1984", "author" to "George Orwell", "isAvailable" to false ) val newBook = Book.from(bookData, Book::class.java) // JSON serialization using Gson (used internally by SDK) import com.google.gson.Gson val gson = Gson() val jsonString = gson.toJson(book) val bookFromJson = gson.fromJson(jsonString, Book::class.java) ``` {% /tabsitem %} {% tabsitem #swift-methods title="Swift" %} ```swift let book = books.rows.first! // Convert model to dictionary for debugging let bookMap = book.toMap() print("Book data: \(bookMap)") // Create model instance from dictionary let bookData: [String: Any] = [ "title": "1984", "author": "George Orwell", "isAvailable": false ] let newBook = Book.from(map: bookData) // JSON encoding using Swift's Codable let jsonData = try JSONEncoder().encode(book) let jsonString = String(data: jsonData, encoding: .utf8) // JSON decoding if let jsonString = jsonString, let data = jsonString.data(using: .utf8) { let bookFromJson = try JSONDecoder().decode(Book.self, from: data) } ``` {% /tabsitem %} {% /tabs %} {% info title="Generate types automatically" %} You can automatically generate model definitions for your tables using the [Appwrite CLI](/docs/products/databases/type-generation). Run `appwrite types collection` to generate types based on your database schema. {% /info %} ### Permissions {% #permissions %} In Appwrite, permissions can be granted at the table level and the row level. Before a user can create a row, you need to grant create permissions to the user. Read, update, and delete permissions can be granted at both the table and row level. Users only need to be granted access at either the table or row level to access rows. [Learn about configuring permissions](/docs/products/databases/permissions). ### Use transactions {% #use-transactions %} All row operations support `transactionId`. When provided, operations are staged to an internal log and not applied until the transaction is committed. Learn more in the [Transactions guide](/docs/products/databases/transactions). {% multicode %} ```client-web // Create row inside a transaction await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', data: { title: 'Draft' }, transactionId: '' }); // Update row inside a transaction await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { title: 'Published' }, transactionId: '' }); ``` ```client-flutter // Create row inside a transaction await tablesDB.createRow( databaseId: '', tableId: '', rowId: '', data: { 'title': 'Draft' }, transactionId: '' ); // Update row inside a transaction await tablesDB.updateRow( databaseId: '', tableId: '', rowId: '', data: { 'title': 'Published' }, transactionId: '' ); ``` ```client-apple // Create row inside a transaction let _ = try await tablesDB.createRow( databaseId: "", tableId: "", rowId: "", data: ["title": "Draft"], transactionId: "" ) // Update row inside a transaction let _2 = try await tablesDB.updateRow( databaseId: "", tableId: "", rowId: "", data: ["title": "Published"], transactionId: "" ) ``` ```client-android-kotlin // Create row inside a transaction val _ = tablesDB.createRow( databaseId = "", tableId = "", rowId = "", data = mapOf("title" to "Draft"), transactionId = "" ) // Update row inside a transaction val _2 = tablesDB.updateRow( databaseId = "", tableId = "", rowId = "", data = mapOf("title" to "Published"), transactionId = "" ) ``` ```client-android-java // Create row inside a transaction (asynchronous) Map data = new HashMap<>(); data.put("title", "Draft"); tablesDB.createRow( "", "", "", data, "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); // Update row inside a transaction (asynchronous) Map update = new HashMap<>(); update.put("title", "Published"); tablesDB.updateRow( "", "", "", update, "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` ```client-react-native // Create row inside a transaction await tablesDB.createRow({ databaseId: '', tableId: '', rowId: '', data: { title: 'Draft' }, transactionId: '' }); // Update row inside a transaction await tablesDB.updateRow({ databaseId: '', tableId: '', rowId: '', data: { title: 'Published' }, transactionId: '' }); ``` ```server-nodejs // Delete row inside a transaction await tablesDB.deleteRow({ databaseId: '', tableId: '', rowId: '', transactionId: '' }); ``` ```server-deno // Delete row inside a transaction await tablesDB.deleteRow({ databaseId: '', tableId: '', rowId: '', transactionId: '' }); ``` ```server-python ### Delete row inside a transaction tablesDB.delete_row( database_id = '', table_id = '', row_id = '', transaction_id = '' ) ``` ```server-php // Delete row inside a transaction $tablesDB->deleteRow( databaseId: '', tableId: '', rowId: '', transactionId: '' ); ``` ```server-ruby ### Delete row inside a transaction tablesDB.delete_row( database_id: '', table_id: '', row_id: '', transaction_id: '' ) ``` ```server-dotnet // Delete row inside a transaction await tablesDB.DeleteRow( databaseId: "", tableId: "", rowId: "", transactionId: "" ); ``` ```server-dart // Delete row inside a transaction await tablesDB.deleteRow( databaseId: '', tableId: '', rowId: '', transactionId: '' ); ``` ```server-swift // Delete row inside a transaction let _ = try await tablesDB.deleteRow( databaseId: "", tableId: "", rowId: "", transactionId: "" ) ``` ```server-kotlin // Delete row inside a transaction val _ = tablesDB.deleteRow( databaseId = "", tableId = "", rowId = "", transactionId = "" ) ``` ```server-java // Delete row inside a transaction (asynchronous) tablesDB.deleteRow( "", "", "", "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` {% /multicode %} ### Next steps {% #next-steps %} Continue learning with these related guides: {% cards %} {% cards_item href="/docs/products/databases/queries" title="Queries" %} Learn how to filter, sort, and search your rows with various query operators. {% /cards_item %} {% cards_item href="/docs/products/databases/pagination" title="Pagination" %} Handle large datasets by implementing pagination in your row queries. {% /cards_item %} {% cards_item href="/docs/products/databases/bulk-operations" title="Bulk operations" %} Perform create, update, and delete operations on multiple rows simultaneously. {% /cards_item %} {% cards_item href="/docs/products/databases/timestamp-overrides" title="Timestamp overrides" %} Set custom creation and update timestamps when migrating data or backdating records. {% /cards_item %} {% /cards %} --- ## Tables https://appwrite.io/docs/products/databases/tables Appwrite uses tables as containers of rows. Each tables contains many rows identical in structure. The terms tables and rows are used because the Appwrite JSON REST API resembles the API of a traditional NoSQL database, making it intuitive and user-friendly, even though Appwrite uses SQL under the hood. That said, Appwrite is designed to support both SQL and NoSQL database adapters like MariaDB, MySQL, or MongoDB in future versions. ### Create table {% #create-table %} You can create tables using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation). {% tabs %} {% tabsitem #console title="Console" %} You can create a table by heading to the **Databases** page, navigate to a [database](/docs/products/databases/databases), and click **Create table**. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can also create tables programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const tablesDB = new sdk.TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = tablesDB.createTable({ databaseId: '', tableId: '', name: '', columns: [ { key: 'email', type: 'email', required: true }, { key: 'name', type: 'string', size: 255, required: true }, { key: 'age', type: 'integer', required: false }, { key: 'score', type: 'float', required: false }, { key: 'is_active', type: 'boolean', required: true }, { key: 'created_at', type: 'datetime', required: false }, { key: 'status', type: 'enum', elements: ['draft', 'published', 'archived'], required: true }, { key: 'ip_address', type: 'ip', required: false }, { key: 'website', type: 'url', required: false }, { key: 'location', type: 'point', required: false }, { key: 'path', type: 'line', required: false }, { key: 'area', type: 'polygon', required: false }, { key: 'related_items', type: 'relationship', relatedTableId: '', relationType: 'manyToMany', twoWay: true, twoWayKey: 'items', onDelete: 'cascade', required: false } ], indexes: [ { key: 'idx_email', type: 'unique', attributes: ['email'] }, { key: 'idx_name', type: 'key', attributes: ['name'] }, { key: 'idx_name_fulltext', type: 'fulltext', attributes: ['name'] } ] }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let tablesDB = new sdk.TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; let promise = tablesDB.createTable({ databaseId: '', tableId: '', name: '', columns: [ { key: 'email', type: 'email', required: true }, { key: 'name', type: 'string', size: 255, required: true }, { key: 'age', type: 'integer', required: false }, { key: 'score', type: 'float', required: false }, { key: 'is_active', type: 'boolean', required: true }, { key: 'created_at', type: 'datetime', required: false }, { key: 'status', type: 'enum', elements: ['draft', 'published', 'archived'], required: true }, { key: 'ip_address', type: 'ip', required: false }, { key: 'website', type: 'url', required: false }, { key: 'location', type: 'point', required: false }, { key: 'path', type: 'line', required: false }, { key: 'area', type: 'polygon', required: false }, { key: 'related_items', type: 'relationship', relatedTableId: '', relationType: 'manyToMany', twoWay: true, twoWayKey: 'items', onDelete: 'cascade', required: false } ], indexes: [ { key: 'idx_email', type: 'unique', attributes: ['email'] }, { key: 'idx_name', type: 'key', attributes: ['name'] }, { key: 'idx_name_fulltext', type: 'fulltext', attributes: ['name'] } ] }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $tablesDB = new TablesDB($client); $result = $tablesDB->createTable( databaseId: '', tableId: '', name: '', columns: [ [ 'key' => 'email', 'type' => 'email', 'required' => true ], [ 'key' => 'name', 'type' => 'string', 'size' => 255, 'required' => true ], [ 'key' => 'age', 'type' => 'integer', 'required' => false ], [ 'key' => 'score', 'type' => 'float', 'required' => false ], [ 'key' => 'is_active', 'type' => 'boolean', 'required' => true ], [ 'key' => 'created_at', 'type' => 'datetime', 'required' => false ], [ 'key' => 'status', 'type' => 'enum', 'elements' => ['draft', 'published', 'archived'], 'required' => true ], [ 'key' => 'ip_address', 'type' => 'ip', 'required' => false ], [ 'key' => 'website', 'type' => 'url', 'required' => false ], [ 'key' => 'location', 'type' => 'point', 'required' => false ], [ 'key' => 'path', 'type' => 'line', 'required' => false ], [ 'key' => 'area', 'type' => 'polygon', 'required' => false ], [ 'key' => 'related_items', 'type' => 'relationship', 'relatedTableId' => '', 'relationType' => 'manyToMany', 'twoWay' => true, 'twoWayKey' => 'items', 'onDelete' => 'cascade', 'required' => false ] ], indexes: [ [ 'key' => 'idx_email', 'type' => 'unique', 'attributes' => ['email'] ], [ 'key' => 'idx_name', 'type' => 'key', 'attributes' => ['name'] ], [ 'key' => 'idx_name_fulltext', 'type' => 'fulltext', 'attributes' => ['name'] ] ] ); ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) tablesDB = TablesDB(client) result = tablesDB.create_table( database_id='', table_id='', name='', columns=[ { 'key': 'email', 'type': 'email', 'required': True }, { 'key': 'name', 'type': 'string', 'size': 255, 'required': True }, { 'key': 'age', 'type': 'integer', 'required': False }, { 'key': 'score', 'type': 'float', 'required': False }, { 'key': 'is_active', 'type': 'boolean', 'required': True }, { 'key': 'created_at', 'type': 'datetime', 'required': False }, { 'key': 'status', 'type': 'enum', 'elements': ['draft', 'published', 'archived'], 'required': True }, { 'key': 'ip_address', 'type': 'ip', 'required': False }, { 'key': 'website', 'type': 'url', 'required': False }, { 'key': 'location', 'type': 'point', 'required': False }, { 'key': 'path', 'type': 'line', 'required': False }, { 'key': 'area', 'type': 'polygon', 'required': False }, { 'key': 'related_items', 'type': 'relationship', 'relatedTableId': '', 'relationType': 'manyToMany', 'twoWay': True, 'twoWayKey': 'items', 'onDelete': 'cascade', 'required': False } ], indexes=[ { 'key': 'idx_email', 'type': 'unique', 'attributes': ['email'] }, { 'key': 'idx_name', 'type': 'key', 'attributes': ['name'] }, { 'key': 'idx_name_fulltext', 'type': 'fulltext', 'attributes': ['name'] } ] ) ``` ```ruby require 'Appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key tablesDB = TablesDB.new(client) response = tablesDB.create_table( database_id: '', table_id: '', name: '', columns: [ { key: 'email', type: 'email', required: true }, { key: 'name', type: 'string', size: 255, required: true }, { key: 'age', type: 'integer', required: false }, { key: 'score', type: 'float', required: false }, { key: 'is_active', type: 'boolean', required: true }, { key: 'created_at', type: 'datetime', required: false }, { key: 'status', type: 'enum', elements: ['draft', 'published', 'archived'], required: true }, { key: 'ip_address', type: 'ip', required: false }, { key: 'website', type: 'url', required: false }, { key: 'location', type: 'point', required: false }, { key: 'path', type: 'line', required: false }, { key: 'area', type: 'polygon', required: false }, { key: 'related_items', type: 'relationship', relatedTableId: '', relationType: 'manyToMany', twoWay: true, twoWayKey: 'items', onDelete: 'cascade', required: false } ], indexes: [ { key: 'idx_email', type: 'unique', attributes: ['email'] }, { key: 'idx_name', type: 'key', attributes: ['name'] }, { key: 'idx_name_fulltext', type: 'fulltext', attributes: ['name'] } ] ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var tablesDB = new TablesDB(client); Table result = await tablesDB.CreateTable( databaseId: "", tableId: "", name: "", columns: new List> { new Dictionary { { "key", "email" }, { "type", "email" }, { "required", true } }, new Dictionary { { "key", "name" }, { "type", "string" }, { "size", 255 }, { "required", true } }, new Dictionary { { "key", "age" }, { "type", "integer" }, { "required", false } }, new Dictionary { { "key", "score" }, { "type", "float" }, { "required", false } }, new Dictionary { { "key", "is_active" }, { "type", "boolean" }, { "required", true } }, new Dictionary { { "key", "created_at" }, { "type", "datetime" }, { "required", false } }, new Dictionary { { "key", "status" }, { "type", "enum" }, { "elements", new List { "draft", "published", "archived" } }, { "required", true } }, new Dictionary { { "key", "ip_address" }, { "type", "ip" }, { "required", false } }, new Dictionary { { "key", "website" }, { "type", "url" }, { "required", false } }, new Dictionary { { "key", "location" }, { "type", "point" }, { "required", false } }, new Dictionary { { "key", "path" }, { "type", "line" }, { "required", false } }, new Dictionary { { "key", "area" }, { "type", "polygon" }, { "required", false } }, new Dictionary { { "key", "related_items" }, { "type", "relationship" }, { "relatedTableId", "" }, { "relationType", "manyToMany" }, { "twoWay", true }, { "twoWayKey", "items" }, { "onDelete", "cascade" }, { "required", false } } }, indexes: new List> { new Dictionary { { "key", "idx_email" }, { "type", "unique" }, { "attributes", new List { "email" } } }, new Dictionary { { "key", "idx_name" }, { "type", "key" }, { "attributes", new List { "name" } } }, new Dictionary { { "key", "idx_name_fulltext" }, { "type", "fulltext" }, { "attributes", new List { "name" } } } }); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Databases tablesDB = TablesDB(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = tablesDB.createTable( databaseId: '', tableId: '', name: '', columns: [ { 'key': 'email', 'type': 'email', 'required': true }, { 'key': 'name', 'type': 'string', 'size': 255, 'required': true }, { 'key': 'age', 'type': 'integer', 'required': false }, { 'key': 'score', 'type': 'float', 'required': false }, { 'key': 'is_active', 'type': 'boolean', 'required': true }, { 'key': 'created_at', 'type': 'datetime', 'required': false }, { 'key': 'status', 'type': 'enum', 'elements': ['draft', 'published', 'archived'], 'required': true }, { 'key': 'ip_address', 'type': 'ip', 'required': false }, { 'key': 'website', 'type': 'url', 'required': false }, { 'key': 'location', 'type': 'point', 'required': false }, { 'key': 'path', 'type': 'line', 'required': false }, { 'key': 'area', 'type': 'polygon', 'required': false }, { 'key': 'related_items', 'type': 'relationship', 'relatedTableId': '', 'relationType': 'manyToMany', 'twoWay': true, 'twoWayKey': 'items', 'onDelete': 'cascade', 'required': false } ], indexes: [ { 'key': 'idx_email', 'type': 'unique', 'attributes': ['email'] }, { 'key': 'idx_name', 'type': 'key', 'attributes': ['name'] }, { 'key': 'idx_name_fulltext', 'type': 'fulltext', 'attributes': ['name'] } ], ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val tablesDB = TablesDB(client) val response = tablesDB.createTable( databaseId = "", tableId = "", name = "", columns = listOf( mapOf( "key" to "email", "type" to "email", "required" to true ), mapOf( "key" to "name", "type" to "string", "size" to 255, "required" to true ), mapOf( "key" to "age", "type" to "integer", "required" to false ), mapOf( "key" to "score", "type" to "float", "required" to false ), mapOf( "key" to "is_active", "type" to "boolean", "required" to true ), mapOf( "key" to "created_at", "type" to "datetime", "required" to false ), mapOf( "key" to "status", "type" to "enum", "elements" to listOf("draft", "published", "archived"), "required" to true ), mapOf( "key" to "ip_address", "type" to "ip", "required" to false ), mapOf( "key" to "website", "type" to "url", "required" to false ), mapOf( "key" to "location", "type" to "point", "required" to false ), mapOf( "key" to "path", "type" to "line", "required" to false ), mapOf( "key" to "area", "type" to "polygon", "required" to false ), mapOf( "key" to "related_items", "type" to "relationship", "relatedTableId" to "", "relationType" to "manyToMany", "twoWay" to true, "twoWayKey" to "items", "onDelete" to "cascade", "required" to false ) ), indexes = listOf( mapOf( "key" to "idx_email", "type" to "unique", "attributes" to listOf("email") ), mapOf( "key" to "idx_name", "type" to "key", "attributes" to listOf("name") ), mapOf( "key" to "idx_name_fulltext", "type" to "fulltext", "attributes" to listOf("name") ) ) ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; import java.util.*; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Databases tablesDB = new TablesDB(client); List> columns = Arrays.asList( new HashMap() {{ put("key", "email"); put("type", "email"); put("required", true); }}, new HashMap() {{ put("key", "name"); put("type", "string"); put("size", 255); put("required", true); }}, new HashMap() {{ put("key", "age"); put("type", "integer"); put("required", false); }}, new HashMap() {{ put("key", "score"); put("type", "float"); put("required", false); }}, new HashMap() {{ put("key", "is_active"); put("type", "boolean"); put("required", true); }}, new HashMap() {{ put("key", "created_at"); put("type", "datetime"); put("required", false); }}, new HashMap() {{ put("key", "status"); put("type", "enum"); put("elements", Arrays.asList("draft", "published", "archived")); put("required", true); }}, new HashMap() {{ put("key", "ip_address"); put("type", "ip"); put("required", false); }}, new HashMap() {{ put("key", "website"); put("type", "url"); put("required", false); }}, new HashMap() {{ put("key", "location"); put("type", "point"); put("required", false); }}, new HashMap() {{ put("key", "path"); put("type", "line"); put("required", false); }}, new HashMap() {{ put("key", "area"); put("type", "polygon"); put("required", false); }}, new HashMap() {{ put("key", "related_items"); put("type", "relationship"); put("relatedTableId", ""); put("relationType", "manyToMany"); put("twoWay", true); put("twoWayKey", "items"); put("onDelete", "cascade"); put("required", false); }} ); List> indexes = Arrays.asList( new HashMap() {{ put("key", "idx_email"); put("type", "unique"); put("attributes", Arrays.asList("email")); }}, new HashMap() {{ put("key", "idx_name"); put("type", "key"); put("attributes", Arrays.asList("name")); }}, new HashMap() {{ put("key", "idx_name_fulltext"); put("type", "fulltext"); put("attributes", Arrays.asList("name")); }} ); tablesDB.createTable( "", "", "", columns, indexes, new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let tablesDB = TablesDB(client) let table = try await tablesDB.createTable( databaseId: "", tableId: "", name: "", columns: [ [ "key": "email", "type": "email", "required": true ], [ "key": "name", "type": "string", "size": 255, "required": true ], [ "key": "age", "type": "integer", "required": false ], [ "key": "score", "type": "float", "required": false ], [ "key": "is_active", "type": "boolean", "required": true ], [ "key": "created_at", "type": "datetime", "required": false ], [ "key": "status", "type": "enum", "elements": ["draft", "published", "archived"], "required": true ], [ "key": "ip_address", "type": "ip", "required": false ], [ "key": "website", "type": "url", "required": false ], [ "key": "location", "type": "point", "required": false ], [ "key": "path", "type": "line", "required": false ], [ "key": "area", "type": "polygon", "required": false ], [ "key": "related_items", "type": "relationship", "relatedTableId": "", "relationType": "manyToMany", "twoWay": true, "twoWayKey": "items", "onDelete": "cascade", "required": false ] ], indexes: [ [ "key": "idx_email", "type": "unique", "attributes": ["email"] ], [ "key": "idx_name", "type": "key", "attributes": ["name"] ], [ "key": "idx_name_fulltext", "type": "fulltext", "attributes": ["name"] ] ] ) ``` {% /multicode %} You can also configure **permissions** in the `createTable` method. Learn more about the `createTable` method in the [API references](/docs/references). {% /tabsitem %} {% tabsitem #cli title="CLI" %} {% partial file="cli-disclaimer.md" /%} To create your table using the CLI, first use the `appwrite init tables` command to initialize your table. ```sh appwrite init tables ``` Then push your table using the `appwrite push tables` command. ```sh appwrite push tables ``` This will create your table in the Console with all of your `appwrite.json` configurations. {% arrow_link href="/docs/tooling/command-line/tables#commands" %} Learn more about the CLI tables commands {% /arrow_link %} {% /tabsitem %} {% /tabs %} {% info title="AI suggestions" %} Enable **AI suggestions** to generate columns and indexes based on your table name and existing database structure. [Learn more about AI suggestions](/docs/products/databases/ai-suggestions). {% /info %} ### Permissions {% #permissions %} Appwrite uses permissions to control data access. For security, only users that are granted permissions can access a resource. This helps prevent accidental data leaks by forcing you to make more conscious decisions around permissions. By default, Appwrite doesn't grant permissions to any users when a new table is created. This means users can't create new rows or read, update, and delete existing rows. [Learn about configuring permissions](/docs/products/databases/permissions). ### Columns {% #columns %} All rows in a table follow the same structure. Columns are used to define the structure of your rows and help the Appwrite's API validate your users' input. Add your first column by clicking the **Add column** button. You can choose between the following types. | Column | Description | |--------------|------------------------------------------------------------------| | `string` | String column. | | `integer` | Integer column. | | `float` | Float column. | | `boolean` | Boolean column. | | `datetime` | Datetime column formatted as an ISO 8601 string. | | `enum` | Enum column. | | `ip` | IP address column for IPv4 and IPv6. | | `email` | Email address column. | | `url` | URL column. | | `point` | Geographic point specified as `[longitude, latitude]`. | | `line` | Geographic line represented by an ordered list of coordinates. | | `polygon` | Geographic polygon representing a closed area; supports interior holes. | | `relationship` | Relationship column relates one table to another. [Learn more about relationships.](/docs/products/databases/relationships) | If an column must be populated in all rows, set it as `required`. If not, you may optionally set a default value. Additionally, decide if the column should be a single value or an array of values. If needed, you can change an column's key, default value, size (for strings), and whether it is required or not after creation. You can increase a string column's size without any restrictions. When decreasing size, you must ensure that your existing data is less than or equal to the new size, or the operation will fail. ### Indexes {% #indexes %} Databases use indexes to quickly locate data without having to search through every row for matches. To ensure the best performance, Appwrite recommends an index for every column queried. If you plan to query multiple columns in a single query, creating an index with **all** queried columns will yield optimal performance. The following indexes are currently supported: | Type | Description | |------------|--------------------------------------------------------------------------------------------------------------| | `key` | Plain Index to allow queries. | | `unique` | Unique Index to disallow duplicates. | | `fulltext` | For searching within string columns. Required for the [search query method](/docs/products/databases/queries#query-class). | You can create an index by navigating to your table's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server). --- ## Timestamp overrides https://appwrite.io/docs/products/databases/timestamp-overrides When creating or updating documents, Appwrite automatically sets `$createdAt` and `$updatedAt` timestamps. However, there are scenarios where you might need to set these timestamps manually, such as when migrating data from another system or backfilling historical records. {% info title="Server SDKs required" %} To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These attributes can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk). {% /info %} ### Setting custom timestamps {% #setting-custom-timestamps %} You can override a document's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these attributes are not provided, Appwrite will set them automatically. Custom timestamps work with all document operations: create, update, upsert, and their bulk variants. #### Single document operations {% #single-document-operations %} When working with individual documents, you can set custom timestamps during create, update, and upsert operations. ##### Create with custom timestamps {% #create-custom %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); const databases = new sdk.Databases(client); await databases.createDocument( '', '', sdk.ID.unique(), { '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), // ...your attributes } ); ``` ```server-php use Appwrite\Client; use Appwrite\ID; use Appwrite\Services\Databases; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $databases = new Databases($client); $databases->createDocument( databaseId: '', collectionId: '', documentId: '', [ '$createdAt' => (new DateTime(''))->format(DATE_ATOM), '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), // ...your attributes ] ); ``` ```server-swift import Appwrite import Foundation let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") let databases = Databases(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let customDate = isoFormatter.date(from: "") ?? Date() let createdAt = isoFormatter.string(from: customDate) let updatedAt = isoFormatter.string(from: customDate) do { let created = try await databases.createDocument( databaseId: "", collectionId: "", documentId: "", data: [ "$createdAt": createdAt, "$updatedAt": updatedAt, // ...your attributes ] ) print("Created:", created) } catch { print("Create error:", error) } ``` ```server-python from appwrite.client import Client from appwrite.services.databases import Databases from appwrite.id import ID from datetime import datetime, timezone client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') databases = Databases(client) iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat() databases.create_document( database_id='', collection_id='', document_id=ID.unique(), data={ '$createdAt': iso, '$updatedAt': iso, # ...your attributes } ) ``` ```server-ruby require 'appwrite' require 'time' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') databases = Databases.new(client) custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601 databases.create_document( database_id: '', collection_id: '', document_id: ID.unique(), data: { '$createdAt' => custom_date, '$updatedAt' => custom_date, # ...your attributes } ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); Databases databases = new Databases(client); string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O"); await databases.CreateDocument( databaseId: "", collectionId: "", documentId: ID.Unique(), data: new Dictionary { ["$createdAt"] = customDate, ["$updatedAt"] = customDate, // ...your attributes } ); ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); Databases databases = Databases(client); String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String(); await databases.createDocument( databaseId: '', collectionId: '', documentId: ID.unique(), data: { '\$createdAt': customDate, '\$updatedAt': customDate, // ...your attributes }, ); ``` {% /multicode %} ##### Update with custom timestamps {% #update-custom %} When updating documents, you can also set a custom `$updatedAt` timestamp: {% multicode %} ```server-nodejs await databases.updateDocument( '', '', '', { '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(), // ...your attributes } ); ``` ```server-php $databases->updateDocument( databaseId: '', collectionId: '', documentId: '', [ '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), // ...your attributes ] ); ``` ```server-python from datetime import datetime, timezone databases.update_document( database_id='', collection_id='', document_id='', data={ '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(), # ...your attributes } ) ``` ```server-swift import Appwrite import Foundation let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") let databases = Databases(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { let updated = try await databases.updateDocument( databaseId: "", collectionId: "", documentId: "", data: [ "$updatedAt": updatedAt, // ...your attributes ] ) print("Updated:", updated) } catch { print("Update error:", error) } ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') databases = Databases.new(client) custom_date = Time.parse('').iso8601 databases.update_document( database_id: '', collection_id: '', document_id: '', data: { '$updatedAt' => custom_date, # ...your attributes } ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); Databases databases = new Databases(client); string customDate = DateTimeOffset.Parse("").ToString("O"); await databases.UpdateDocument( databaseId: "", collectionId: "", documentId: "", data: new Dictionary { ["$updatedAt"] = customDate, // ...your attributes } ); ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); Databases databases = Databases(client); String customDate = DateTime.parse('').toIso8601String(); await databases.updateDocument( databaseId: '', collectionId: '', documentId: '', data: { '\$updatedAt': customDate, // ...your attributes }, ); ``` {% /multicode %} #### Bulk operations {% #bulk-operations %} Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch: ##### Bulk create {% #bulk-create %} {% multicode %} ```server-nodejs await databases.createDocuments( '', '', [ { '$id': sdk.ID.unique(), '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), // ...your attributes }, { '$id': sdk.ID.unique(), '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(), // ...your attributes } ] ); ``` ```server-python databases.create_documents( database_id='', collection_id='', documents=[ { '$id': ID.unique(), '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), # ...your attributes }, { '$id': ID.unique(), '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(), # ...your attributes } ] ) ``` ```server-php use Appwrite\Client; use Appwrite\ID; use Appwrite\Services\Databases; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $databases = new Databases($client); $databases->createDocuments( databaseId: '', collectionId: '', documents: [ [ '$id' => ID::unique(), '$createdAt' => (new DateTime(''))->format(DATE_ATOM), '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), // ...your attributes ], [ '$id' => ID::unique(), '$createdAt' => (new DateTime(''))->format(DATE_ATOM), '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), // ...your attributes ], ] ); ``` ```server-swift import Appwrite import Foundation let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") let databases = Databases(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let first = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) let second = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { let bulkCreated = try await databases.createDocuments( databaseId: "", collectionId: "", documents: [ [ "$id": ID.unique(), "$createdAt": first, "$updatedAt": first, // ...your attributes ], [ "$id": ID.unique(), "$createdAt": second, "$updatedAt": second, // ...your attributes ] ] ) print("Bulk create:", bulkCreated) } catch { print("Bulk create error:", error) } ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') databases = Databases.new(client) first = Time.parse('').iso8601 second = Time.parse('').iso8601 databases.create_documents( database_id: '', collection_id: '', documents: [ { '$id' => ID.unique(), '$createdAt' => first, '$updatedAt' => first, # ...your attributes }, { '$id' => ID.unique(), '$createdAt' => second, '$updatedAt' => second, # ...your attributes } ] ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); Databases databases = new Databases(client); string first = DateTimeOffset.Parse("").ToString("O"); string second = DateTimeOffset.Parse("").ToString("O"); await databases.CreateDocuments( databaseId: "", collectionId: "", documents: new List { new Dictionary { ["$id"] = ID.Unique(), ["$createdAt"] = first, ["$updatedAt"] = first, // ...your attributes }, new Dictionary { ["$id"] = ID.Unique(), ["$createdAt"] = second, ["$updatedAt"] = second, // ...your attributes } } ); ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); Databases databases = Databases(client); String first = DateTime.parse('').toIso8601String(); String second = DateTime.parse('').toIso8601String(); await databases.createDocuments( databaseId: '', collectionId: '', documents: [ { '\$id': ID.unique(), '\$createdAt': first, '\$updatedAt': first, // ...your attributes }, { '\$id': ID.unique(), '\$createdAt': second, '\$updatedAt': second, // ...your attributes } ], ); ``` {% /multicode %} ##### Bulk upsert {% #bulk-upsert %} {% multicode %} ```server-nodejs await databases.upsertDocuments( '', '', [ { '$id': '', '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(), '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(), // ...your attributes } ] ); ``` ```server-python databases.upsert_documents( database_id='', collection_id='', documents=[ { '$id': '', '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(), '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(), # ...your attributes } ] ) ``` ```server-php use Appwrite\Client; use Appwrite\ID; use Appwrite\Services\Databases; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); $databases = new Databases($client); $databases->upsertDocuments( databaseId: '', collectionId: '', documents: [ [ '$id' => '', '$createdAt' => (new DateTime(''))->format(DATE_ATOM), '$updatedAt' => (new DateTime(''))->format(DATE_ATOM), // ...your attributes ], ] ); ``` ```server-swift import Appwrite import Foundation let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") let databases = Databases(client) let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let createdAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "") ?? Date()) do { let bulkUpserted = try await databases.upsertDocuments( databaseId: "", collectionId: "", documents: [ [ "$id": "", "$createdAt": createdAt, "$updatedAt": updatedAt, // ...your attributes ] ] ) print("Bulk upsert:", bulkUpserted) } catch { print("Bulk upsert error:", error) } ``` ```server-ruby require 'appwrite' require 'time' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') .set_project('') .set_key('') databases = Databases.new(client) custom_date = Time.parse('').iso8601 databases.upsert_documents( database_id: '', collection_id: '', documents: [ { '$id' => '', '$createdAt' => custom_date, '$updatedAt' => custom_date, # ...your attributes } ] ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndpoint("https://.cloud.appwrite.io/v1") .SetProject("") .SetKey(""); Databases databases = new Databases(client); string createdAt = DateTimeOffset.Parse("").ToString("O"); string updatedAt = DateTimeOffset.Parse("").ToString("O"); await databases.UpsertDocuments( databaseId: "", collectionId: "", documents: new List { new Dictionary { ["$id"] = "", ["$createdAt"] = createdAt, ["$updatedAt"] = updatedAt, // ...your attributes } } ); ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey(''); Databases databases = Databases(client); String createdAt = DateTime.parse('').toIso8601String(); String updatedAt = DateTime.parse('').toIso8601String(); await databases.upsertDocuments( databaseId: '', collectionId: '', documents: [ { '\$id': '', '\$createdAt': createdAt, '\$updatedAt': updatedAt, // ...your attributes } ], ); ``` {% /multicode %} ### Common use cases {% #use-cases %} Custom timestamps are particularly useful in several scenarios: #### Data migration {% #data-migration %} When migrating existing data from another system, you can preserve the original creation and modification times: {% multicode %} ```server-nodejs await databases.createDocument( '', 'blog_posts', sdk.ID.unique(), { '$createdAt': '', '$updatedAt': '', title: '', content: '<CONTENT>' } ) ``` ```server-php $databases->createDocument( databaseId: '<DATABASE_ID>', collectionId: 'blog_posts', documentId: ID::unique(), [ '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', '$updatedAt' => '<LAST_MODIFIED_ISO>', 'title' => '<TITLE>', 'content' => '<CONTENT>' ] ); ``` ```server-swift let _ = try await databases.createDocument( databaseId: "<DATABASE_ID>", collectionId: "blog_posts", documentId: ID.unique(), data: [ "$createdAt": "<ORIGINAL_CREATED_AT_ISO>", "$updatedAt": "<LAST_MODIFIED_ISO>", "title": "<TITLE>", "content": "<CONTENT>" ] ) ``` ```server-python databases.create_document( database_id='<DATABASE_ID>', collection_id='blog_posts', document_id=ID.unique(), data={ '$createdAt': '<ORIGINAL_CREATED_AT_ISO>', '$updatedAt': '<LAST_MODIFIED_ISO>', 'title': '<TITLE>', 'content': '<CONTENT>' } ) ``` ```server-ruby databases.create_document( database_id: '<DATABASE_ID>', collection_id: 'blog_posts', document_id: ID.unique(), data: { '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>', '$updatedAt' => '<LAST_MODIFIED_ISO>', 'title' => '<TITLE>', 'content' => '<CONTENT>' } ) ``` ```server-dotnet await databases.CreateDocument( databaseId: "<DATABASE_ID>", collectionId: "blog_posts", documentId: ID.Unique(), data: new Dictionary<string, object> { ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>", ["$updatedAt"] = "<LAST_MODIFIED_ISO>", ["title"] = "<TITLE>", ["content"] = "<CONTENT>" } ); ``` ```server-dart await databases.createDocument( databaseId: '<DATABASE_ID>', collectionId: 'blog_posts', documentId: ID.unique(), data: { '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>', '\$updatedAt': '<LAST_MODIFIED_ISO>', 'title': '<TITLE>', 'content': '<CONTENT>' }, ); ``` {% /multicode %} #### Backdating records {% #backdating %} For historical data entry or when creating records that represent past events: {% multicode %} ```server-nodejs await databases.createDocument( '<DATABASE_ID>', 'transactions', sdk.ID.unique(), { '$createdAt': '2023-12-31T23:59:59.000Z', '$updatedAt': '2023-12-31T23:59:59.000Z', amount: 1000, type: 'year-end-bonus' } ) ``` ```server-php $databases->createDocument( databaseId: '<DATABASE_ID>', collectionId: 'transactions', documentId: ID::unique(), [ '$createdAt' => '2023-12-31T23:59:59.000Z', '$updatedAt' => '2023-12-31T23:59:59.000Z', 'amount' => 1000, 'type' => 'year-end-bonus' ] ); ``` ```server-swift let _ = try await databases.createDocument( databaseId: "<DATABASE_ID>", collectionId: "transactions", documentId: ID.unique(), data: [ "$createdAt": "2023-12-31T23:59:59.000Z", "$updatedAt": "2023-12-31T23:59:59.000Z", "amount": 1000, "type": "year-end-bonus" ] ) ``` ```server-python databases.create_document( database_id='<DATABASE_ID>', collection_id='transactions', document_id=ID.unique(), data={ '$createdAt': '2023-12-31T23:59:59.000Z', '$updatedAt': '2023-12-31T23:59:59.000Z', 'amount': 1000, 'type': 'year-end-bonus' } ) ``` ```server-ruby databases.create_document( database_id: '<DATABASE_ID>', collection_id: 'transactions', document_id: ID.unique(), data: { '$createdAt' => '2023-12-31T23:59:59.000Z', '$updatedAt' => '2023-12-31T23:59:59.000Z', 'amount' => 1000, 'type' => 'year-end-bonus' } ) ``` ```server-dotnet await databases.CreateDocument( databaseId: "<DATABASE_ID>", collectionId: "transactions", documentId: ID.Unique(), data: new Dictionary<string, object> { ["$createdAt"] = "2023-12-31T23:59:59.000Z", ["$updatedAt"] = "2023-12-31T23:59:59.000Z", ["amount"] = 1000, ["type"] = "year-end-bonus" } ); ``` ```server-dart await databases.createDocument( databaseId: '<DATABASE_ID>', collectionId: 'transactions', documentId: ID.unique(), data: { '\$createdAt': '2023-12-31T23:59:59.000Z', '\$updatedAt': '2023-12-31T23:59:59.000Z', 'amount': 1000, 'type': 'year-end-bonus' }, ); ``` {% /multicode %} #### Synchronization {% #synchronization %} When synchronizing data between systems while maintaining timestamp consistency: {% multicode %} ```server-nodejs await databases.upsertDocument( '<DATABASE_ID>', 'users', '<DOCUMENT_ID_OR_NEW_ID>', { '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', profile: '<PROFILE_DATA>' } ) ``` ```server-php $databases->upsertDocument( databaseId: '<DATABASE_ID>', collectionId: 'users', documentId: '<DOCUMENT_ID_OR_NEW_ID>', [ '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', 'profile' => '<PROFILE_DATA>' ] ); ``` ```server-swift let _ = try await databases.upsertDocument( databaseId: "<DATABASE_ID>", collectionId: "users", documentId: "<DOCUMENT_ID_OR_NEW_ID>", data: [ "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>", "profile": "<PROFILE_DATA>" ] ) ``` ```server-python databases.upsert_document( database_id='<DATABASE_ID>', collection_id='users', document_id='<DOCUMENT_ID_OR_NEW_ID>', data={ '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', 'profile': '<PROFILE_DATA>' } ) ``` ```server-ruby databases.upsert_document( database_id: '<DATABASE_ID>', collection_id: 'users', document_id: '<DOCUMENT_ID_OR_NEW_ID>', data: { '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>', 'profile' => '<PROFILE_DATA>' } ) ``` ```server-dotnet await databases.UpsertDocument( databaseId: "<DATABASE_ID>", collectionId: "users", documentId: "<DOCUMENT_ID_OR_NEW_ID>", data: new Dictionary<string, object> { ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>", ["profile"] = "<PROFILE_DATA>" } ); ``` ```server-dart await databases.upsertDocument( databaseId: '<DATABASE_ID>', collectionId: 'users', documentId: '<DOCUMENT_ID_OR_NEW_ID>', data: { '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>', 'profile': '<PROFILE_DATA>' }, ); ``` {% /multicode %} {% info title="Timestamp format and usage" %} - Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default. - You can set either or both attributes as needed. If omitted, Appwrite sets them automatically. {% /info %} --- ## Transactions https://appwrite.io/docs/products/databases/transactions Transactions let you stage multiple database operations and apply them together, atomically. Use transactions to keep related changes consistent, even when they span multiple databases and tables. ### How transactions work {% #how-transactions-work %} 1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID. 2. Stage operations by passing the `transactionId` parameter to supported row, bulk, and atomic numeric methods. You can stage many operations at once with the [createOperations](#create-operations) method. 3. Call the [updateTransaction](#update-transaction) method to commit or roll back. On commit, Appwrite replays all staged logs in order inside a real database transaction. Staged operations see earlier staged changes (read your own writes). If any affected row changed outside your transaction, the commit fails with a conflict. {% info title="Scope and limitations" %} You can stage operations across any database and table within the same transaction. Schema operations (for example, adding or removing columns) are not included in transactions. {% /info %} ### Limits {% #limits %} The maximum number of operations you can stage per transaction depends on your plan: | Plan | Max operations per transaction | |------|-------------------------------| | Free | 100 | | Pro | 1,000 | | Scale | 2,500 | ### Create a transaction {% #create-a-transaction %} Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations. {% multicode %} ```client-web import { Client, TablesDB } from 'appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const tablesDB = new TablesDB(client); const tx = await tablesDB.createTransaction(); // tx.$id is your transactionId ``` ```client-react-native import { Client, TablesDB } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const tablesDB = new TablesDB(client); const tx = await tablesDB.createTransaction(); // tx.$id is your transactionId ``` ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); final tablesDB = TablesDB(client); final tx = await tablesDB.createTransaction(); // tx.$id is your transactionId ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") let tablesDB = TablesDB(client) let tx = try await tablesDB.createTransaction() // tx.$id is your transactionId ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") val tablesDB = TablesDB(client) val tx = tablesDB.createTransaction() // tx.$id is your transactionId ``` ```client-android-java import io.appwrite.Client; import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>"); TablesDB tablesDB = new TablesDB(client); // Create a transaction (asynchronous) tablesDB.createTransaction(new CoroutineCallback<>((tx, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(tx); })); ``` ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>') .setKey('<API_KEY>'); const tablesDB = new sdk.TablesDB(client); const tx = await tablesDB.createTransaction(); // tx.$id is your transactionId ``` ```server-deno import * as sdk from 'npm:node-appwrite'; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>') .setKey('<API_KEY>'); const tablesDB = new sdk.TablesDB(client); const tx = await tablesDB.createTransaction(); // tx.$id is your transactionId ``` ```server-python from appwrite.client import Client from appwrite.services.tables_db import TablesDB client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') client.set_project('<PROJECT_ID>') client.set_key('<API_KEY>') tablesDB = TablesDB(client) tx = tablesDB.create_transaction() ### tx.$id is your transactionId ``` ```server-php <?php use Appwrite\Client; use Appwrite\Services\TablesDB; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') ->setProject('<PROJECT_ID>') ->setKey('<API_KEY>') ; $tablesDB = new TablesDB($client); $tx = $tablesDB->createTransaction(); // $tx->\$id is your transactionId ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') .set_project('<PROJECT_ID>') .set_key('<API_KEY>') tablesDB = TablesDB.new(client) tx = tablesDB.create_transaction ### tx['$id'] is your transactionId ``` ```server-dotnet using Appwrite; using Appwrite.Services; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") .SetProject("<PROJECT_ID>") .SetKey("<API_KEY>"); var tablesDB = new TablesDB(client); var tx = await tablesDB.CreateTransaction(); // tx.$id is your transactionId ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() async { Client client = Client(); TablesDB tablesDB = TablesDB(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>') .setKey('<API_KEY>'); final tx = await tablesDB.createTransaction(); // tx contains the transaction ID } ``` ```server-go package main import ( "log" "github.com/appwrite/sdk-for-go/appwrite" ) func main() { client := appwrite.NewClient( appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"), appwrite.WithProject("<PROJECT_ID>"), appwrite.WithKey("<API_KEY>"), ) tablesDB := appwrite.NewTablesDB(client) tx, err := tablesDB.CreateTransaction() if err != nil { log.Fatal(err) } _ = tx } ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<API_KEY>") let tablesDB = TablesDB(client) let tx = try await tablesDB.createTransaction() // tx.$id is your transactionId ``` ```server-kotlin import io.appwrite.Client import io.appwrite.services.TablesDB val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<API_KEY>") val tablesDB = TablesDB(client) val tx = tablesDB.createTransaction() // tx.$id is your transactionId ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<API_KEY>"); TablesDB tablesDB = new TablesDB(client); tablesDB.createTransaction(new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); })); ``` {% /multicode %} ### Stage operations {% #stage-operations %} Add the `transactionId` parameter to supported methods to stage them instead of immediately persisting. When you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target table is not modified until you commit the transaction. #### Stage single operations {% #stage-single-operations %} Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createRows, updateRows, upsertRows, deleteRows). {% multicode %} ```client-web // Create inside a transaction await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { name: 'Walter' }, transactionId: tx.$id }); // Increment inside a transaction await tablesDB.incrementRowColumn({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: tx.$id }); ``` ```client-flutter // Create inside a transaction await tablesDB.createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { 'name': 'Walter' }, transactionId: tx.$id ); // Increment inside a transaction await tablesDB.incrementRowColumn( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: tx.$id ); ``` ```client-apple // Create inside a transaction try await tablesDB.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", data: ["name": "Walter"], transactionId: tx.$id ) // Increment inside a transaction try await tablesDB.incrementRowColumn( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", column: "credits", value: 1, transactionId: tx.$id ) ``` ```server-kotlin // Create inside a transaction tablesDB.createRow( databaseId = "<DATABASE_ID>", tableId = "<TABLE_ID>", rowId = "<ROW_ID>", data = mapOf("name" to "Walter"), transactionId = tx.$id ) // Increment inside a transaction tablesDB.incrementRowColumn( databaseId = "<DATABASE_ID>", tableId = "<TABLE_ID>", rowId = "<ROW_ID>", column = "credits", value = 1, transactionId = tx.$id ) ``` ```server-java // Create inside a transaction (asynchronous) tablesDB.createRow( "<DATABASE_ID>", "<TABLE_ID>", "<ROW_ID>", Map.of("name", "Walter"), "<TRANSACTION_ID>", new CoroutineCallback<>((row, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(row); return null; }) ); // Increment inside a transaction (asynchronous) tablesDB.incrementRowColumn( "<DATABASE_ID>", "<TABLE_ID>", "<ROW_ID>", "credits", 1, "<TRANSACTION_ID>", new CoroutineCallback<>((row, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(row); return null; }) ); ``` ```client-react-native // Create inside a transaction await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { name: 'Walter' }, transactionId: tx.$id }); // Increment inside a transaction await tablesDB.incrementRowColumn({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: tx.$id }); ``` ```server-nodejs // Update inside a transaction await tablesDB.updateRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { plan: 'pro' }, transactionId: tx.$id }); // Delete inside a transaction await tablesDB.deleteRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', transactionId: tx.$id }); ``` ```server-python ### Upsert inside a transaction tablesDB.upsert_row( database_id = '<DATABASE_ID>', table_id = '<TABLE_ID>', row_id = '<ROW_ID>', data = { 'name': 'Walter' }, transaction_id = tx['$id'] ) ### Decrement inside a transaction tablesDB.decrement_row_column( database_id = '<DATABASE_ID>', table_id = '<TABLE_ID>', row_id = '<ROW_ID>', column = 'credits', value = 1, transaction_id = tx['$id'] ) ``` ```server-php // Create inside a transaction $tablesDB->createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: ['name' => 'Walter'], transactionId: $tx['$id'] ); // Increment inside a transaction $tablesDB->incrementRowColumn( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: $tx['$id'] ); ``` ```server-ruby ### Create inside a transaction tablesDB.create_row( database_id: '<DATABASE_ID>', table_id: '<TABLE_ID>', row_id: '<ROW_ID>', data: { 'name' => 'Walter' }, transaction_id: tx['$id'] ) ### Increment inside a transaction tablesDB.increment_row_column( database_id: '<DATABASE_ID>', table_id: '<TABLE_ID>', row_id: '<ROW_ID>', column: 'credits', value: 1, transaction_id: tx['$id'] ) ``` ```server-dotnet // Create inside a transaction await tablesDB.CreateRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", data: new Dictionary<string, object> { ["name"] = "Walter" }, transactionId: tx.Id ); // Increment inside a transaction await tablesDB.IncrementRowColumn( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", column: "credits", value: 1, transactionId: tx.Id ); ``` ```server-dart // Create inside a transaction await tablesDB.createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { 'name': 'Walter' }, transactionId: tx.Id ); // Increment inside a transaction await tablesDB.incrementRowColumn( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: tx.Id ); ``` ```server-deno // Create inside a transaction await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', data: { name: 'Walter' }, transactionId: tx.$id }); // Increment inside a transaction await tablesDB.incrementRowColumn({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: '<ROW_ID>', column: 'credits', value: 1, transactionId: tx.$id }); ``` ```server-swift // Create inside a transaction try await tablesDB.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", data: ["name": "Walter"], transactionId: tx.$id ) // Increment inside a transaction try await tablesDB.incrementRowColumn( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: "<ROW_ID>", column: "credits", value: 1, transactionId: tx.$id ) ``` {% /multicode %} #### Stage many with createOperations {% #create-operations %} Use the `createOperations` method to stage multiple operations across databases and tables in a single request. Provide an array of operation objects: ```json [ { "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete", "databaseId": "<DATABASE_ID>", "tableId|collectionId": "<TABLE_ID|COLLECTION_ID>", "rowId|documentId": "<ROW_ID|DOCUMENT_ID>", "data": {} } ] ``` ##### Provide data for each action (createOperations) {% #provide-data-for-each-action %} ##### Create, update, and upsert {% #create-update-upsert %} Pass a raw data object. ```json { "name": "Walter" } ``` ##### Increment and decrement {% #increment-decrement %} Pass a value and optionally `min`/`max` bounds. ```json { "value": 1, "min": 0, "max": 1000, "column": "<COLUMN_NAME>" } ``` ##### Bulk create and bulk upsert {% #bulk-create-upsert %} Pass an array of raw data objects. ```json [{ "$id": "123", "name": "Walter" }] ``` ##### Bulk update {% #bulk-update %} Pass queries and the data to apply. ```json { "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } } ``` ##### Bulk delete {% #bulk-delete %} Pass queries to select rows to delete. ```json { "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] } ``` {% multicode %} ```server-nodejs // Stage multiple operations at once await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'create', databaseId: '<DB_A>', tableId: '<TABLE_1>', rowId: 'u1', data: { name: 'Walter' } }, { action: 'increment', databaseId: '<DB_B>', tableId: '<TABLE_2>', rowId: 'u2', data: { value: 1, min: 0, column: 'credits' } } ] }); ``` ```server-python tablesDB.create_operations( transaction_id = tx['$id'], operations = [ { 'action': 'create', 'databaseId': '<DB_A>', 'tableId': '<TABLE_1>', 'rowId': 'u1', 'data': { 'name': 'Walter' } }, { 'action': 'increment', 'databaseId': '<DB_B>', 'tableId': '<TABLE_2>', 'rowId': 'u2', 'data': { 'value': 1, 'min': 0, 'column': 'credits' } } ] ) ``` ```client-web await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'create', databaseId: '<DB_A>', tableId: '<TABLE_1>', rowId: 'u1', data: { name: 'Walter' } }, { action: 'increment', databaseId: '<DB_B>', tableId: '<TABLE_2>', rowId: 'u2', data: { value: 1, min: 0, column: 'credits' } } ] }); ``` ```client-flutter await tablesDB.createOperations( transactionId: tx.$id, operations: [ { 'action': 'create', 'databaseId': '<DB_A>', 'tableId': '<TABLE_1>', 'rowId': 'u1', 'data': { 'name': 'Walter' } }, { 'action': 'increment', 'databaseId': '<DB_B>', 'tableId': '<TABLE_2>', 'rowId': 'u2', 'data': { 'value': 1, 'min': 0, 'column': 'credits' } } ], ); ``` ```client-apple try await tablesDB.createOperations( transactionId: tx.$id, operations: [ [ "action": "create", "databaseId": "<DB_A>", "tableId": "<TABLE_1>", "rowId": "u1", "data": ["name": "Walter"] ], [ "action": "increment", "databaseId": "<DB_B>", "tableId": "<TABLE_2>", "rowId": "u2", "data": ["value": 1, "min": 0, "column": "credits"] ] ] ) ``` ```server-kotlin tablesDB.createOperations( transactionId = tx.$id, operations = listOf( mapOf( "action" to "create", "databaseId" to "<DB_A>", "tableId" to "<TABLE_1>", "rowId" to "u1", "data" to mapOf("name" to "Walter") ), mapOf( "action" to "increment", "databaseId" to "<DB_B>", "tableId" to "<TABLE_2>", "rowId" to "u2", "data" to mapOf("value" to 1, "min" to 0, "column" to "credits") ) ) ) ``` ```server-java // Stage multiple operations at once (asynchronous) List<Map<String, Object>> operations = Arrays.asList( Map.of( "action", "create", "databaseId", "<DB_A>", "tableId", "<TABLE_1>", "rowId", "u1", "data", Map.of("name", "Walter") ), Map.of( "action", "increment", "databaseId", "<DB_B>", "tableId", "<TABLE_2>", "rowId", "u2", "data", Map.of("value", 1, "min", 0, "column", "credits") ) ); tablesDB.createOperations( "<TRANSACTION_ID>", operations, new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` ```client-react-native await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'create', databaseId: '<DB_A>', tableId: '<TABLE_1>', rowId: 'u1', data: { name: 'Walter' } }, { action: 'increment', databaseId: '<DB_B>', tableId: '<TABLE_2>', rowId: 'u2', data: { value: 1, min: 0, column: 'credits' } } ] }); ``` ```server-deno await tablesDB.createOperations({ transactionId: tx.$id, operations: [ { action: 'create', databaseId: '<DB_A>', tableId: '<TABLE_1>', rowId: 'u1', data: { name: 'Walter' } }, { action: 'increment', databaseId: '<DB_B>', tableId: '<TABLE_2>', rowId: 'u2', data: { value: 1, min: 0, column: 'credits' } } ] }); ``` ```server-php $tablesDB->createOperations( transactionId: $tx['$id'], operations: [ [ 'action' => 'create', 'databaseId' => '<DB_A>', 'tableId' => '<TABLE_1>', 'rowId' => 'u1', 'data' => [ 'name' => 'Walter' ] ], [ 'action' => 'increment', 'databaseId' => '<DB_B>', 'tableId' => '<TABLE_2>', 'rowId' => 'u2', 'data' => [ 'value' => 1, 'min' => 0, 'column' => 'credits' ] ] ] ); ``` ```server-ruby tablesDB.create_operations( transaction_id: tx['$id'], operations: [ { 'action' => 'create', 'databaseId' => '<DB_A>', 'tableId' => '<TABLE_1>', 'rowId' => 'u1', 'data' => { 'name' => 'Walter' } }, { 'action' => 'increment', 'databaseId' => '<DB_B>', 'tableId' => '<TABLE_2>', 'rowId' => 'u2', 'data' => { 'value' => 1, 'min' => 0, 'column' => 'credits' } } ] ) ``` ```server-dotnet await tablesDB.CreateOperations( transactionId: tx.Id, operations: new List<Dictionary<string, object>> { new Dictionary<string, object> { ["action"] = "create", ["databaseId"] = "<DB_A>", ["tableId"] = "<TABLE_1>", ["rowId"] = "u1", ["data"] = new Dictionary<string, object> { ["name"] = "Walter" } }, new Dictionary<string, object> { ["action"] = "increment", ["databaseId"] = "<DB_B>", ["tableId"] = "<TABLE_2>", ["rowId"] = "u2", ["data"] = new Dictionary<string, object> { ["value"] = 1, ["min"] = 0, ["column"] = "credits" } } } ); ``` ```server-dart await tablesDB.createOperations( transactionId: tx.Id, operations: [ { 'action': 'create', 'databaseId': '<DB_A>', 'tableId': '<TABLE_1>', 'rowId': 'u1', 'data': { 'name': 'Walter' } }, { 'action': 'increment', 'databaseId': '<DB_B>', 'tableId': '<TABLE_2>', 'rowId': 'u2', 'data': { 'value': 1, 'min': 0, 'column': 'credits' } } ] ); ``` ```server-swift try await tablesDB.createOperations( transactionId: tx.$id, operations: [ [ "action": "create", "databaseId": "<DB_A>", "tableId": "<TABLE_1>", "rowId": "u1", "data": ["name": "Walter"] ], [ "action": "increment", "databaseId": "<DB_B>", "tableId": "<TABLE_2>", "rowId": "u2", "data": ["value": 1, "min": 0, "column": "credits"] ] ] ) ``` {% /multicode %} ### Commit or roll back {% #commit-or-rollback %} When you are done staging operations, call the `updateTransaction` method to finalize the transaction. {% multicode %} ```client-web // Commit await tablesDB.updateTransaction({ transactionId: tx.$id, commit: true }); // Or roll back await tablesDB.updateTransaction({ transactionId: tx.$id, rollback: true }); ``` ```client-flutter // Commit await tablesDB.updateTransaction( transactionId: tx.$id, commit: true ); // Roll back await tablesDB.updateTransaction( transactionId: tx.$id, rollback: true ); ``` ```client-apple // Commit try await tablesDB.updateTransaction( transactionId: tx.$id, commit: true ) // Roll back try await tablesDB.updateTransaction( transactionId: tx.$id, rollback: true ) ``` ```server-kotlin // Commit tablesDB.updateTransaction( transactionId = tx.$id, commit = true ) // Roll back tablesDB.updateTransaction( transactionId = tx.$id, rollback = true ) ``` ```server-java // Commit (asynchronous) tablesDB.updateTransaction( "<TRANSACTION_ID>", true, false, new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); // Roll back (asynchronous) tablesDB.updateTransaction( "<TRANSACTION_ID>", false, true, new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return null; } System.out.println(result); return null; }) ); ``` ```client-react-native // Commit await tablesDB.updateTransaction({ transactionId: tx.$id, commit: true }); // Roll back await tablesDB.updateTransaction({ transactionId: tx.$id, rollback: true }); ``` ```server-nodejs // Commit await tablesDB.updateTransaction({ transactionId: tx.$id, commit: true }); // Roll back await tablesDB.updateTransaction({ transactionId: tx.$id, rollback: true }); ``` ```server-python ### Commit tablesDB.update_transaction( transaction_id = tx['$id'], commit = True ) ### Roll back tablesDB.update_transaction( transaction_id = tx['$id'], rollback = True ) ``` ```server-php // Commit $tablesDB->updateTransaction( transactionId: $tx['$id'], commit: true ); // Roll back $tablesDB->updateTransaction( transactionId: $tx['$id'], rollback: true ); ``` ```server-ruby ### Commit tablesDB.update_transaction( transaction_id: tx['$id'], commit: true ) ### Roll back tablesDB.update_transaction( transaction_id: tx['$id'], rollback: true ) ``` ```server-dotnet // Commit await tablesDB.UpdateTransaction( transactionId: tx.Id, commit: true ); // Roll back await tablesDB.UpdateTransaction( transactionId: tx.Id, rollback: true ); ``` ```server-dart // Commit await tablesDB.updateTransaction( transactionId: tx.Id, commit: true ); // Roll back await tablesDB.updateTransaction( transactionId: tx.Id, rollback: true ); ``` {% /multicode %} ### Handle conflicts {% #handle-conflicts %} On commit, Appwrite verifies that rows affected by your transaction haven’t changed externally since they were staged. If a conflicting change is detected, the commit fails with a conflict error. Resolve the conflict (for example, refetch and re-stage) and try again. {% info title="Best practices" %} Keep transactions short-lived to reduce the likelihood of conflicts. Stage related updates in the order they must be applied. Prefer `createOperations` when you need to stage many changes across multiple tables. {% /info %} {% arrow_link href="/docs/references" %} Explore the API references {% /arrow_link %} --- ## Type generation https://appwrite.io/docs/products/databases/type-generation The Appwrite CLI provides a simple way to generate types based on your Appwrite database schema. This feature is particularly useful for developers who want to ensure type safety in their applications by generating type definitions that match their database tables and columns. To generate types, the CLI reads the database schema from your project's `appwrite.json` file and generates type definitions for each table. #### Generating types First, ensure you have the [Appwrite CLI](/docs/tooling/command-line/installation#getting-started) installed and your project is [initialised](/docs/tooling/command-line/installation#initialization). Then, run the following command in your terminal to pull tables from your Appwrite project: ```bash appwrite pull tables ``` To generate types, you can use the Appwrite CLI command: ```bash appwrite types [options] <output-directory> ``` The following options are currently available: | Option | Description | |--------|-------------| | `--language` or `-l` | The programming language for which types can be generated. Choices include `ts`, `js`, `php`, `kotlin`, `swift`, `java`, `dart`, `auto`. The CLI will use `auto` as the default option if this option is skipped. | | `--strict` or `-s` | Enables strict type generation. This option ensures that all the columns follow language conventions, even if that leads to mismatches with the schema defined in your Appwrite console. | | `--help` or `-h` | Displays help information for the command. | #### Example usage Suppose you want to generate types for a table with data on books with the following schema from your `appwrite.json` file: ```json { "projectId": "682ca9a50004cf4b330f", "endpoint": "https://<REGION>.cloud.appwrite.io/v1", "projectName": "Appwrite project", "databases": [ { "$id": "684c678b00211ddac082", "name": "Library", "enabled": true } ], "tables": [ { "$id": "684c6790002d457ee89d", "$permissions": [], "databaseId": "684c678b00211ddac082", "name": "Books", "enabled": true, "rowSecurity": false, "columns": [ { "key": "name", "type": "string", "required": true, "array": false, "size": 255, "default": null }, { "key": "author", "type": "string", "required": true, "array": false, "size": 255, "default": null }, { "key": "release_year", "type": "datetime", "required": false, "array": false, "format": "", "default": null }, { "key": "category", "type": "string", "required": false, "array": false, "elements": [ "fiction", "nonfiction" ], "format": "enum", "default": null }, { "key": "genre", "type": "string", "required": false, "array": true, "size": 100, "default": null }, { "key": "is_checked_out", "type": "boolean", "required": true, "array": false, "default": null } ], "indexes": [] } ] } ``` Here's how you can generate types for this table across all supported languages: {% tabs %} {% tabsitem #ts title="TypeScript" %} Run the following command in your terminal: ```bash appwrite types --language ts ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```ts import { type Models } from 'appwrite'; export enum Category { FICTION = "fiction", NONFICTION = "nonfiction", } export type Books = Models.Row & { name: string; author: string; releaseYear: string | null; category: Category | null; genre: string[] | null; isCheckedOut: boolean; } ``` {% /tabsitem %} {% tabsitem #js title="JavaScript" %} Run the following command in your terminal: ```bash appwrite types --language js ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```js /** * @typedef {import('appwrite').Models.Row} Row */ /** * @typedef {Object} Books * @property {string} name * @property {string} author * @property {string|null|undefined} releaseYear * @property {"fiction"|"nonfiction"|null|undefined} category * @property {string[]|null|undefined} genre * @property {boolean} isCheckedOut */ ``` {% /tabsitem %} {% tabsitem #java title="Java" %} Run the following command in your terminal: ```bash appwrite types --language java ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```java package io.appwrite.models; import java.util.*; public class Books { public enum Category { fiction, nonfiction; } private String name; private String author; private String releaseYear; private Category category; private List<String> genre; private boolean isCheckedOut; public Books() { } public Books( String name, String author, String releaseYear, Category category, List<String> genre, boolean isCheckedOut ) { this.name = name; this.author = author; this.releaseYear = releaseYear; this.category = category; this.genre = genre; this.isCheckedOut = isCheckedOut; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getReleaseYear() { return releaseYear; } public void setReleaseYear(String releaseYear) { this.releaseYear = releaseYear; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public List<String> getGenre() { return genre; } public void setGenre(List<String> genre) { this.genre = genre; } public boolean getIsCheckedOut() { return isCheckedOut; } public void setIsCheckedOut(boolean isCheckedOut) { this.isCheckedOut = isCheckedOut; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Books that = (Books) obj; return Objects.equals(name, that.name) && Objects.equals(author, that.author) && Objects.equals(releaseYear, that.releaseYear) && Objects.equals(category, that.category) && Objects.equals(genre, that.genre) && Objects.equals(isCheckedOut, that.isCheckedOut); } @Override public int hashCode() { return Objects.hash(name, author, releaseYear, category, genre, isCheckedOut); } @Override public String toString() { return "Books{" + "name=" + name + "author=" + author + "releaseYear=" + releaseYear + "category=" + category + "genre=" + genre + "isCheckedOut=" + isCheckedOut + '}'; } } ``` {% /tabsitem %} {% tabsitem #php title="PHP" %} Run the following command in your terminal: ```bash appwrite types --language php ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```php <?php namespace Appwrite\Models; enum Category: string { case FICTION = 'fiction'; case NONFICTION = 'nonfiction'; } class Books { private string $name; private string $author; private string|null $releaseYear; private Category|null $category; private array $genre; private bool $isCheckedOut; public function __construct( string $name, string $author, ?string $releaseYear = null, ?Category $category = null, ?array $genre = null, bool $isCheckedOut ) { $this->name = $name; $this->author = $author; $this->releaseYear = $releaseYear; $this->category = $category; $this->genre = $genre; $this->isCheckedOut = $isCheckedOut; } public function getName(): string { return $this->name; } public function setName(string $name): void { $this->name = $name; } public function getAuthor(): string { return $this->author; } public function setAuthor(string $author): void { $this->author = $author; } public function getReleaseYear(): string|null { return $this->releaseYear; } public function setReleaseYear(string|null $releaseYear): void { $this->releaseYear = $releaseYear; } public function getCategory(): Category|null { return $this->category; } public function setCategory(Category|null $category): void { $this->category = $category; } public function getGenre(): array { return $this->genre; } public function setGenre(array $genre): void { $this->genre = $genre; } public function getIsCheckedOut(): bool { return $this->isCheckedOut; } public function setIsCheckedOut(bool $isCheckedOut): void { $this->isCheckedOut = $isCheckedOut; } } ``` {% /tabsitem %} {% tabsitem #dart title="Dart" %} Run the following command in your terminal: ```bash appwrite types --language dart ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```dart enum Category { fiction, nonfiction, } class Books { String name; String author; String? releaseYear; Category? category; List<String>? genre; bool isCheckedOut; Books({ required this.name, required this.author, this.releaseYear, this.category, this.genre, required this.isCheckedOut, }); factory Books.fromMap(Map<String, dynamic> map) { return Books( name: map['name'].toString(), author: map['author'].toString(), releaseYear: map['release_year']?.toString() ?? null, category: map['category'] != null ? Category.values.where((e) => e.name == map['category']).firstOrNull : null, genre: List<String>.from(map['genre'] ?? []) ?? [], isCheckedOut: map['is_checked_out'], ); } Map<String, dynamic> toMap() { return { "name": name, "author": author, "release_year": releaseYear, "category": category?.name ?? null, "genre": genre, "is_checked_out": isCheckedOut, }; } } ``` {% /tabsitem %} {% tabsitem #kotlin title="Kotlin" %} Run the following command in your terminal: ```bash appwrite types --language kotlin ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```kotlin package io.appwrite.models enum class Category { fiction, nonfiction } data class Books( val name: String, val author: String, val releaseYear: String?, val category: Category?, val genre: List<String>?, val isCheckedOut: Boolean, ) ``` {% /tabsitem %} {% tabsitem #swift title="Swift" %} Run the following command in your terminal: ```bash appwrite types --language swift ./types ``` This will generate the following types in the `./types` sub-directory of your project: ```swift import Foundation public enum Category: String, Codable, CaseIterable { case fiction = "fiction" case nonfiction = "nonfiction" } public class Books: Codable { public let name: String public let author: String public let releaseYear: String? public let category: Category? public let genre: [String]? public let isCheckedOut: Bool enum CodingKeys: String, CodingKey { case name = "name" case author = "author" case releaseYear = "release_year" case category = "category" case genre = "genre" case isCheckedOut = "is_checked_out" } init( name: String, author: String, releaseYear: String?, category: Category?, genre: [String]?, isCheckedOut: Bool ) { self.name = name self.author = author self.releaseYear = releaseYear self.category = category self.genre = genre self.isCheckedOut = isCheckedOut } public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) self.author = try container.decode(String.self, forKey: .author) self.releaseYear = try container.decodeIfPresent(String.self, forKey: .releaseYear) self.category = try container.decodeIfPresent(Category.self, forKey: .category) self.genre = try container.decodeIfPresent([String].self, forKey: .genre) self.isCheckedOut = try container.decode(Bool.self, forKey: .isCheckedOut) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(author, forKey: .author) try container.encodeIfPresent(releaseYear, forKey: .releaseYear) try container.encodeIfPresent(category, forKey: .category) try container.encodeIfPresent(genre, forKey: .genre) try container.encode(isCheckedOut, forKey: .isCheckedOut) } public func toMap() -> [String: Any] { return [ "name": name as Any, "author": author as Any, "release_year": releaseYear as Any, "category": category as Any, "genre": genre as Any, "is_checked_out": isCheckedOut as Any ] } public static func from(map: [String: Any]) -> Books { return Books( name: map["name"] as! String, author: map["author"] as! String, releaseYear: map["release_year"] as? String, category: map["category"] as? String, genre: map["genre"] as? [String], isCheckedOut: map["is_checked_out"] as! Bool ) } } ``` {% /tabsitem %} {% /tabs %} --- ## Functions https://appwrite.io/docs/products/functions Appwrite Functions unlock limitless potential for developers to extend Appwrite with code snippets. Appwrite Functions are user-defined functions that can start small and scale big, deploying automatically from source control. These Functions can be triggered by HTTP requests, SDK methods, server events, webhooks, and scheduled executions. Each function will have its own URL, execute in its own isolated container, and have its own configurable environment variables and permissions. ### Getting started {% #getting-started %} Appwrite Functions let you build anything you can imagine, but this flexibility makes it difficult to know where to start. Start exploring by cloning one of the quick start templates or using a template with pre-built integration to quickly implement features. {% only_dark %} ![Create project screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/functions/template.png) {% /only_light %} {% arrow_link href="/docs/products/functions/quick-start" %} Quick start {% /arrow_link %} --- ## Deploy from Git https://appwrite.io/docs/products/functions/deploy-from-git Appwrite Functions are mini-applications in Appwrite with their own endpoints. Each function can have many deployments, which can be thought of as versions of the mini-application. Appwrite Functions can be automatically deployed from Git repositories, so you can track changes to your function's code naturally as a part of you development workflow. ### Create deployment {% #create-deployment %} The recommended way to manage your Appwrite Function deployments is to use a version control system, like Git. This offers simple versioning and collaboration that will easily fit into the rest of your development workflow. You can only use Git deployment for Appwrite Functions connected to Git. [Create a new function with Git](/docs/products/functions/functions#create-function) or connect your existing function to a Git repository in your function's **Settings** > **Configuration** > **Git settings** > **Connect Git**. 1. Using Git, checkout the branch you configured as the production branch when creating the Appwrite Function. 2. Create a new commit. 3. Push your new commit. 4. A new deployment will be automatically created, built and activated. #### Commits to the production branch {% #commits-to-production-branch %} When you push a commit to the production branch, usually `main`, a new deployment will be created, built, and activated. This means, the new deployments will **immediately replace the current active deployment** and handle all incoming requests. #### Commits to other branches {% #commits-to-other-branches %} When you push a commit to a branch other than the production branch, a new deployment will be created, but it will not be activated. This means, the new deployment will not handle any incoming requests until it is activated. ### Git configuration If you need to update your Git configuration, navigate to **Functions** > your function > **Settings** > **Configuration** > **Git settings**. #### Entry point {% #entry-point %} The entry point is the code file contains the exported function that will be executed when the function is called. This entry point has a specific format that must be followed. You can find examples using a [starter template](/docs/products/functions/templates) or following the [developing functions docs](/docs/products/functions/develop). #### Root directory {% #root-directory %} The root directory is the root of the code that will be copied to the executor. If you have a monorepo, you can specify the subdirectory that contains the function's code using the root directory setting. #### Share code between multiple functions {% #share-code-between-multiple-functions %} If you're sharing code between multiple Appwrite Functions in a monorepo, referencing files outside of the entry point file will not work. To share code between multiple functions, set the root directory to be the common root of the mono repo, and use `cd <working directory>` in your **Build settings** to navigate to the function's directory before building. Another option is to use submodules in your Git repository to include shared code in each function's repository. ### Debugging {% #debugging %} - If you updated your function's configuration but the deployment is not working as expected, you may need to first redeploy your function before the changes take effect. - If you notice your function is missing dependencies during build or at runtimes, update it's build settings. Navigate to **Functions** > your function > **Settings** > **Configuration** > **Build settings**. These commands will be ran before the function is built and can be used to install dependencies. - If you're missing some code files at build time, make sure they are included in the Git configuration's **Root directory**. Only files in the root directory folder will be copied to the executor. - If you're self-hosting Appwrite, you will need to configure some [environment variables](/docs/advanced/self-hosting/functions) to enable Git deployments. --- ## Deploy manually https://appwrite.io/docs/products/functions/deploy-manually Appwrite Functions are mini-applications in Appwrite with their own endpoints. Each function can have many deployments, which can be thought of as versions of the mini-application. While we recommend you create deployments through [automatic Git deployments](/docs/products/functions/deploy-from-git), you can also create deployments manually or through the Appwrite CLI. ### CLI {% #cli %} {% partial file="cli-function.md" /%} #### Configure CLI deployments {% #configure-cli-deployments %} If you need to target a different project, API endpoint, change the path or entry point of your function, or update any of the other configuration options, you can do so by editing the `appwrite.config.json` file. {% arrow_link href="/docs/tooling/command-line/functions#appwritejson" %} Learn more about appwrite.config.json {% /arrow_link %} ### Manual Deployment {% #manual %} You can upload your functions to be deployed using the Appwrite Console. The example below shows a simple Node.js function. ```text . ├── package.json └── index.js ``` First, navigate inside the folder that contains your dependency file. Package your code files into the `.tar.gz` format: {% multicode %} ```bash tar --exclude code.tar.gz -czf code.tar.gz . ``` ```cmd tar --exclude code.tar.gz -czf code.tar.gz . ``` ```powershell tar --exclude code.tar.gz -czf code.tar.gz . ``` {% /multicode %} Next, navigate to your Appwrite Console and upload the function. 1. Navigate to the function you want to deploy. 2. Click **Create deployment**. 3. Select the **Manual** tab. 4. Input the entry point of your function under **Entrypoint**. For the example above, it would be `index.js`. 5. Upload `code.tar.gz`. 6. Select **Activate deployment after build**. 7. Click **Create**. ### Debugging {% #debugging %} - If you updated your function's configuration but the deployment is not working as expected, you may need to first redeploy your function before the changes take effect. - If you notice your function is missing dependencies during build or at runtimes, update it's build settings. Navigate to **Functions** > your function > **Settings** > **Configuration** > **Build settings**. These commands will be ran before the function is built and can be used to install dependencies. - If you're missing some code files at build time, make sure they are included in the **Root directory**. Only files in the root directory folder will be copied to the executor. --- ## Deployments https://appwrite.io/docs/products/functions/deployments Each function can have many deployments, which can be thought of as versions of the mini-application. Functions can be created and deployed in different ways to meet your unique development habits. ### Deployment status {% #deployment-status %} Throughout the life cycle of a deployment, it could have the following status. {% table %} * Status * description --- * `active` * The deployment is built and currently activated and ready to be executed. A function can have one active deployment and deployment a must be active before being executed. --- * `ready` * A deployment is built, but is not activated. `ready` deployments can be activated to replace the current active deployment. --- * `building` * A deployment is being built. Check the [build log](#build-logs) for more detailed logs. --- * `processing` * The function deployment has begun and has not finished. --- * `waiting` * The deployment is queued but has not been picked up for processing. --- * `failed` * A deployment was not successful. Check the [build log](#build-logs) for detailed logs for debugging. {% /table %} ### Update deployment {% #update-deployment %} Some Function settings require redeploying your function to be reflected in your active deployment. When you update a function by changing it's **Git settings**, **Build settings**, and **Environment variables**, you need to redeploy your function before they take effect. ### Build logs {% #build-logs %} When you build a deployment, the logs generated will be saved for debugging purposes. You can find build logs by navigating to the **deployments** tab of your function, clicking the three-dots menu beside, and click **Logs**. ### Redeploy {% #redeploy %} After updating the configuration, redeploy your function for changes to take effect. You can also redeploy to retry failed builds. 1. Navigate to **Functions**. 2. Open the function you wish to inspect. 3. Under the **Deployments** tab, find the status of the current active deployment. 4. Redeploy by clicking the triple-dots beside an execution, and hitting the **Redeploy** button. Redeployment behavior varies depending on how the initial deployment was created. {% info title="Benefits for Pro+ users" %} Users subscribed to the Appwrite Pro plan or above receive certain special benefits: - [Express builds](/changelog/entry/2024-08-10) for quicker deployments, resulting in reduced wait times and smoother workflows - Customizable [runtime specifications](/blog/post/introducing-new-compute-capabilities-appwrite-functions), allowing for tailored performance and resource allocation {% /info %} --- ## Develop Appwrite Functions https://appwrite.io/docs/products/functions/develop Appwrite Functions offer a familiar interface if you've developed REST endpoints. Each function is handled following a request and response pattern. ### Lifecycle {% #life-cycle %} There is a clear lifecycle for all Appwrite Functions, from beginning to end. Here's everything that happens during a function execution. 1. The function is invoked. 1. The active [deployment](/docs/products/functions/deployments)'s executor will handle the request. 1. The Executor passes in request information like headers, body or path through the `context.req` object of your exported function. 1. The runtime executes the code you defined, you can log through the `context.log()` or `context.error()` methods. 1. Function terminates when you return results using `return context.res.text()`, `return context.res.json()` or similar. [Locally developed functions](/docs/products/functions/develop-locally) follow the same lifecycle on your local machine. #### Entrypoint {% #entrypoint %} You'll find all of these steps in a simple function like this. Notice the exported entry point that the executor will call. {% multicode %} ```server-nodejs import { Client } from 'node-appwrite'; // This is your Appwrite function // It's executed each time we get a request export default async ({ req, res, log, error }) => { // Why not try the Appwrite SDK? // // Set project and set API key // const client = new Client() // .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) // .setKey(req.headers['x-appwrite-key']); // You can log messages to the console log('Hello, Logs!'); // If something goes wrong, log an error error('Hello, Errors!'); // The `req` object contains the request data if (req.method === 'GET') { // Send a response with the res object helpers // `res.text()` dispatches a string back to the client return res.text('Hello, World!'); } // `res.json()` is a handy helper for sending JSON return res.json({ motto: 'Build like a team of hundreds_', learn: 'https://appwrite.io/docs', connect: 'https://appwrite.io/discord', getInspired: 'https://builtwith.appwrite.io', }); }; ``` ```php require(__DIR__ . '/../vendor/autoload.php'); use Appwrite\Client; use Appwrite\Exception; // This is your Appwrite function // It's executed each time we get a request return function ($context) { // Why not try the Appwrite SDK? // // Set project and set API key // $client = (new Client()) // ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID)) // ->setKey($context->req->headers['x-appwrite-key']); // You can log messages to the console $context->log('Hello, Logs!'); // If something goes wrong, log an error $context->error('Hello, Errors!'); // The `req` object contains the request data if ($context->req->method === 'GET') { // Send a response with the res object helpers // `res.text()` dispatches a string back to the client return $context->res->text('Hello, World!'); } // `res.json()` is a handy helper for sending JSON return $context->res->json([ 'motto' => 'Build like a team of hundreds_', 'learn' => 'https://appwrite.io/docs', 'connect' => 'https://appwrite.io/discord', 'getInspired' => 'https://builtwith.appwrite.io', ]); }; ``` ```python from appwrite.client import Client import os ### This is your Appwrite function ### It's executed each time we get a request def main(context): # Why not try the Appwrite SDK? # # Set project and set API key # client = ( # Client() # .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) # .set_key(context.req.headers["x-appwrite-key"]) # ) # You can log messages to the console context.log("Hello, Logs!") # If something goes wrong, log an error context.error("Hello, Errors!") # The `context.req` object contains the request data if context.req.method == "GET": # Send a response with the res object helpers # `context.res.text()` dispatches a string back to the client return context.res.text("Hello, World!") # `context.res.json()` is a handy helper for sending JSON return context.res.json({ "motto": "Build like a team of hundreds_", "learn": "https://appwrite.io/docs", "connect": "https://appwrite.io/discord", "getInspired": "https://builtwith.appwrite.io", }) ``` ```ruby require "appwrite" ### This is your Appwrite function ### It's executed each time we get a request def main(context) ### Why not try the Appwrite SDK? ### # Set project and set API key ### client = Client.new ### .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID']) ### .set_key(context.req.headers['x-appwrite-key']) ### You can log messages to the console context.log("Hello, Logs!") ### If something goes wrong, log an error context.error("Hello, Errors!") ### The `context.req` object contains the request data if (context.req.method == "GET") # Send a response with the res object helpers # `context.res.text()` dispatches a string back to the client return context.res.text("Hello, World!") end ### `context.res.json()` is a handy helper for sending JSON return context.res.json({ "motto": "Build like a team of hundreds_", "learn": "https://appwrite.io/docs", "connect": "https://appwrite.io/discord", "getInspired": "https://builtwith.appwrite.io", }) end ``` ```deno import { Client } from "npm:node-appwrite"; // This is your Appwrite function // It's executed each time we get a request export default ({ req, res, log, error }: any) => { // Why not try the Appwrite SDK? // // Set project and set API key // const client = new Client() // .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "") // .setKey(req.headers["x-appwrite-key"] || ""); // You can log messages to the console log("Hello, Logs!"); // If something goes wrong, log an error error("Hello, Errors!"); // The `req` object contains the request data if (req.method === "GET") { // Send a response with the res object helpers // `res.text()` dispatches a string back to the client return res.text("Hello, World!"); } // `res.json()` is a handy helper for sending JSON return res.json({ motto: "Build like a team of hundreds_", learn: "https://appwrite.io/docs", connect: "https://appwrite.io/discord", getInspired: "https://builtwith.appwrite.io", }); }; ``` ```go package handler import ( "fmt" "os" "github.com/appwrite/sdk-for-go/appwrite" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) type Response struct { Motto string `json:"motto"` Learn string `json:"learn"` Connect string `json:"connect"` GetInspired string `json:"getInspired"` } func Main(Context openruntimes.Context) openruntimes.Response { // This is your Appwrite function // It's executed each time we get a request service var _ = appwrite.NewClient( appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")), appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]), ) // You can log messages to the console fmt.Println("Hello, Logs!") fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!") // The `Context.Req` object contains the request data if Context.Req.Method == "GET" { // Send a response with the Context.Res object helpers // `Context.Res.Text()` dispatches a string back to the client return Context.Res.Text("Hello, World!") } // `res.json()` is a handy helper for sending JSON return Context.Res.Json( Response{ Motto: "Build like a team of hundreds_", Learn: "https://appwrite.io/docs", Connect: "https://appwrite.io/discord", GetInspired: "https://builtwith.appwrite.io", }) } ``` ```dart import 'dart:async'; import 'package:dart_appwrite/dart_appwrite.dart'; // This is your Appwrite function // It's executed each time we get a request Future main(final context) async { // Why not try the Appwrite SDK? // // Set project and set API key // final client = Client() // .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID']) // .setKey(context.req.headers['x-appwrite-key']); // You can log messages to the console context.log('Hello, Logs!'); // If something goes wrong, log an error context.error('Hello, Errors!'); // The `req` object contains the request data if (context.req.method == 'GET') { // Send a response with the res object helpers // `res.text()` dispatches a string back to the client return context.res.text('Hello, World!'); } // `res.json()` is a handy helper for sending JSON return context.res.json({ 'motto': 'Build like a team of hundreds_', 'learn': 'https://appwrite.io/docs', 'connect': 'https://appwrite.io/discord', 'getInspired': 'https://builtwith.appwrite.io', }); } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput import io.appwrite.Client import java.util.HashMap class Main { // This is your Appwrite function // It's executed each time we get a request fun main(context: RuntimeContext): RuntimeOutput { // Why not try the Appwrite SDK? // // Set project and set API key // val client = Client() // .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) // .setKey(context.req.headers["x-appwrite-key"]) // You can log messages to the console context.log("Hello, Logs!") // If something goes wrong, log an error context.error("Hello, Errors!") // The `context.req` object contains the request data if (context.req.method == "GET") { // Send a response with the res object helpers // `context.res.text()` dispatches a string back to the client return context.res.text("Hello, World!") } // `context.res.json()` is a handy helper for sending JSON return context.res.json(mutableMapOf( "motto" to "Build like a team of hundreds_", "learn" to "https://appwrite.io/docs", "connect" to "https://appwrite.io/discord", "getInspired" to "https://builtwith.appwrite.io" )) } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; import java.util.HashMap; import io.appwrite.Client; public class Main { // This is your Appwrite function // It's executed each time we get a request public RuntimeOutput main(RuntimeContext context) throws Exception { // Why not try the Appwrite SDK? // // Set project and set API key // Client client = new Client(); // .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) // .setKey(context.getReq().getHeaders().get("x-appwrite-key")); // You can log messages to the console context.log("Hello, Logs!"); // If something goes wrong, log an error context.error("Hello, Errors!"); // The `context.getReq()` object contains the request data if (context.getReq().getMethod().equals("GET")) { // Send a response with the res object helpers // `context.getRes().text()` dispatches a string back to the client return context.getRes().text("Hello, World!"); } Map json = new HashMap<>(); json.put("motto", "Build like a team of hundreds_"); json.put("learn", "https://appwrite.io/docs"); json.put("connect", "https://appwrite.io/discord"); json.put("getInspired", "https://builtwith.appwrite.io"); // `context.getRes().json()` is a handy helper for sending JSON return context.getRes().json(json); } } ``` ```swift import Appwrite import AppwriteModels import Foundation // This is your Appwrite function // It's executed each time we get a request func main(context: RuntimeContext) async throws -> RuntimeOutput { // Why not try the Appwrite SDK? // // Set project and set API key // let client = Client() // .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"]) // .setKey(context.req.headers["x-appwrite-key"] ?? "") // You can log messages to the console context.log("Hello, Logs!") // If something goes wrong, log an error context.error("Hello, Errors!") // The `context.req` object contains the request data if context.req.method == "GET" { // Send a response with the res object helpers // `res.text()` dispatches a string back to the client return context.res.text("Hello, World!") } // `context.res.json()` is a handy helper for sending JSON return try context.res.json([ "motto": "Build like a team of hundreds_", "learn": "https://appwrite.io/docs", "connect": "https://appwrite.io/discord", "getInspired": "https://builtwith.appwrite.io", ]) } ``` ```csharp namespace DotNetRuntime; using Appwrite; using Appwrite.Services; using Appwrite.Models; public class Handler { // This is your Appwrite function // It"s executed each time we get a request public async Task Main(RuntimeContext Context) { // Why not try the Appwrite SDK? // // Set project and set API key // var client = new Client() // .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID")) // .SetKey(Context.Req.Headers["x-appwrite-key"]); // You can log messages to the console Context.Log("Hello, Logs!"); // If something goes wrong, log an error Context.Error("Hello, Errors!"); // The `Context.Req` object contains the request data if (Context.Req.Method == "GET") { // Send a response with the res object helpers // `Context.Res.Text()` dispatches a string back to the client return Context.Res.Text("Hello, World!"); } // `Context.Res.Json()` is a handy helper for sending JSON return Context.Res.Json(new Dictionary() { { "motto", "Build like a team of hundreds_" }, { "learn", "https://appwrite.io/docs" }, { "connect", "https://appwrite.io/discord" }, { "getInspired", "https://builtwith.appwrite.io" }, }); } } ``` {% /multicode %} If you prefer to learn through more examples like this, explore the [examples page](/docs/products/functions/examples). ### Context object {% #context-object %} Context is an object passed into every function to handle communication to both the end users, and logging to the Appwrite Console. All input, output, and logging **must be handled through the context object** passed in. You'll find these properties in the context object. | Property | Description | |----------|--------------------------------------------------------------------------------------------------------------------------| | req | Contains request information like method, body, and headers. See full examples [in the request section](#request). | | res | Contains methods to build a response and return information. See full examples [in the response section](#response). | | log() | Method to log information to the Appwrite Console, end users will not be able to see these logs. See full examples [in the logging section](#logging). | | error() | Method to log errors to the Appwrite Console, end users will not be able to see these errors. See full examples [in the logging section](#logging). | {% info title="Depreciation notice" %} Use `req.bodyText` instead of `req.bodyRaw`. Use `res.text` instead of `res.send`. Use `req.bodyText` or `req.bodyJson` instead of `req.body` depending on the expected input data type. {% /info %} ##### Destructuring assignment {% #destructuring %} Some languages, namely JavaScript, support destructuring. You'll see us use destructuring in examples, which has the following syntax. [Learn more about destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). {% multicode %} ```server-nodejs // before destructuring export default async function (context) { context.log("This is a log!"); return context.res.text("This is a response!"); } // after destructuring export default async function ({ req, res, log, error }) { log("This is a log!"); return res.text("This is a response!"); } ``` ```deno // before destructuring export default async function (context: any) { context.log("This is a log!"); return context.res.text("This is a response!"); } // after destructuring export default async function ({ req, res, log, error }: any) { log("This is a log!"); return res.text("This is a response!"); } ``` {% /multicode %} #### Request {% #request %} If you pass data into an Appwrite Function, it'll be found in the request object. This includes all invocation inputs from Appwrite SDKs, HTTP calls, Appwrite events, or browsers visiting the configured domain. Explore the request object with the following function, which logs all request params to the Appwrite Console. ##### Request types {% #request-types %} {% table %} * Request * Description --- * `req.bodyText` * Returns text that has been converted from binary data. --- * `req.bodyJson` * Parses the body text as JSON. --- * `req.bodyBinary` * Returns the binary body. --- {% /table %} {% multicode %} ```server-nodejs export default async ({ req, res, log }) => { log(req.bodyText); // Raw request body, contains request data log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase log(req.scheme); // Value of the x-forwarded-proto header, usually http or https log(req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. log(req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 log(req.host); // Hostname from the host header, such as awesome.appwrite.io log(req.port); // Port from the host header, for example 8000 log(req.path); // Path part of URL, for example /v1/hooks log(req.queryString); // Raw query params string. For example "limit=12&offset=50" log(JSON.stringify(req.query)); // Parsed query params. For example, req.query.limit return res.text("All the request parameters are logged to the Appwrite Console."); }; ``` ```php <?php return function ($context) { $context->log(json_encode($context->req->bodyJson));// Object from parsed JSON request body, otherwise string $context->log(json_encode($context->req->headers)); // String key-value pairs of all request headers, keys are lowercase $context->log($context->req->scheme); // Value of the x-forwarded-proto header, usually http or https $context->log($context->req->method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. $context->log($context->req->url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 $context->log($context->req->host); // Hostname from the host header, such as awesome.appwrite.io $context->log($context->req->port); // Port from the host header, for example 8000 $context->log($context->req->path); // Path part of URL, for example /v1/hooks $context->log($context->req->queryString); // Raw query params string. For example "limit=12&offset=50" $context->log(json_encode($context->req->query)); // Parsed query params. For example, req.query.limit return $context->res->text("All the request parameters are logged to the Appwrite Console."); } ``` ```python import json def main(context): context.log(context.req.body_text) # Raw request body, contains request data context.log(json.dumps(context.req.body_json)) # Object from parsed JSON request body, otherwise string context.log(json.dumps(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https context.log(context.req.method) # Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.req.url) # Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.req.host) # Hostname from the host header, such as awesome.appwrite.io context.log(context.req.port) # Port from the host header, for example 8000 context.log(context.req.path) # Path part of URL, for example /v1/hooks context.log(context.req.query_string) # Raw query params string. For example "limit=12&offset=50" context.log(json.dumps(context.req.query)) # Parsed query params. For example, req.query.limit return context.res.text("All the request parameters are logged to the Appwrite Console.") ``` ```ruby require 'json' def main(context) context.log(context.req.body_text) # Raw request body, contains request data context.log(JSON.generate(context.req.body_json)) # Object from parsed JSON request body, otherwise string context.log(JSON.generate(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https context.log(context.req.method) # Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.req.url) # Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.req.host) # Hostname from the host header, such as awesome.appwrite.io context.log(context.req.port) # Port from the host header, for example 8000 context.log(context.req.path) # Path part of URL, for example /v1/hooks context.log(context.req.query_string) # Raw query params string. For example "limit=12&offset=50" context.log(JSON.generate(context.req.query)) # Parsed query params. For example, req.query.limit return context.res.text("All the request parameters are logged to the Appwrite Console.") end ``` ```deno export default async ({ req, res, log }: any) => { log(req.bodyText); // Raw request body, contains request data log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase log(req.scheme); // Value of the x-forwarded-proto header, usually http or https log(req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. log(req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 log(req.host); // Hostname from the host header, such as awesome.appwrite.io log(req.port); // Port from the host header, for example 8000 log(req.path); // Path part of URL, for example /v1/hooks log(req.queryString); // Raw query params string. For example "limit=12&offset=50" log(JSON.stringify(req.query)); // Parsed query params. For example, req.query.limit return res.text("All the request parameters are logged to the Appwrite Console."); } ``` ```go package handler import ( "encoding/json" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { Context.Log(Context.Req.BodyText) // Raw request body, contains request data Context.Log(json.Marshal(Context.Req.BodyJson)) // Object from parsed JSON request body, otherwise string Context.Log(json.Marshal(Context.Req.Headers)) // String key-value pairs of all request headers, keys are lowercase Context.Log(Context.Req.Scheme) // Value of the x-forwarded-proto header, usually http or https Context.Log(Context.Req.Method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. Context.Log(Context.Req.Url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 Context.Log(Context.Req.Host) // Hostname from the host header, such as awesome.appwrite.io Context.Log(Context.Req.Port) // Port from the host header, for example 8000 Context.Log(Context.Req.Path) // Path part of URL, for example /v1/hooks Context.Log(Context.Req.QueryString) // Raw query params string. For example "limit=12&offset=50" Context.Log(json.Marshal(Context.Req.Query)) // Parsed query params. For example, req.query.limit return Context.Res.Text("All the request parameters are logged to the Appwrite Console.") } ``` ```dart import 'dart:async'; import 'dart:convert'; Future<dynamic> main(final context) async { context.log(context.req.bodyText); // Raw request body, contains request data context.log(json.encode(context.req.bodyJson)); // Object from parsed JSON request body, otherwise string context.log(json.encode(context.req.headers)); // String key-value pairs of all request headers, keys are lowercase context.log(context.req.scheme); // Value of the x-forwarded-proto header, usually http or https context.log(context.req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.req.host); // Hostname from the host header, such as awesome.appwrite.io context.log(context.req.port); // Port from the host header, for example 8000 context.log(context.req.path); // Path part of URL, for example /v1/hooks context.log(context.req.queryString); // Raw query params string. For example "limit=12&offset=50" context.log(json.encode(context.req.query)); // Parsed query params. For example, req.query.limit return context.res.text("All the request parameters are logged to the Appwrite Console."); } ``` ```swift import Foundation import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { context.log(context.req.bodyJson) // Raw request body, contains request data context.log(NSJSONSerialization.jsonObject(with: context.req.bodyJson, options: [])!) // Object from parsed JSON request body, otherwise string context.log(NSJSONSerialization.jsonObject(with: context.req.headers, options: [])!) // String key-value pairs of all request headers, keys are lowercase context.log(context.req.scheme) // Value of the x-forwarded-proto header, usually http or https context.log(context.req.method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.req.url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.req.host) // Hostname from the host header, such as awesome.appwrite.io context.log(context.req.port) // Port from the host header, for example 8000 context.log(context.req.path) // Path part of URL, for example /v1/hooks context.log(context.req.queryString) // Raw query params string. For example "limit=12&offset=50" context.log(NSJSONSerialization.jsonObject(with: context.req.query, options: [])!) // Parsed query params. For example, req.query.limit return context.res.text("All the request parameters are logged to the Appwrite Console.") } ``` ```csharp namespace DotNetRuntime; using System.Text.Json; public class Handler { public async Task<RuntimeOutput> Main(RuntimeContext Context) { Context.Log(JsonSerializer.Serialize<object>(Context.Req.BodyJson)); // Object from parsed JSON request body, otherwise string Context.Log(JsonSerializer.Serialize<object>(Context.Req.Headers)); // String key-value pairs of all request headers, keys are lowercase Context.Log(Context.Req.Scheme); // Value of the x-forwarded-proto header, usually http or https Context.Log(Context.Req.Method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. Context.Log(Context.Req.Url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 Context.Log(Context.Req.Host); // Hostname from the host header, such as awesome.appwrite.io Context.Log(Context.Req.Port); // Port from the host header, for example 8000 Context.Log(Context.Req.Path); // Path part of URL, for example /v1/hooks Context.Log(Context.Req.QueryString); // Raw query params string. For example "limit=12&offset=50" Context.Log(JsonSerializer.Serialize<object>(Context.Req.Query)); // Parsed query params. For example, req.query.limit return Context.Res.Text("All the request parameters are logged to the Appwrite Console."); } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput import com.google.gson.Gson class Main { fun main(context: RuntimeContext): RuntimeOutput { val gson = Gson() context.log(context.req.bodyJson) // Raw request body, contains request data context.log(gson.toString(context.req.bodyJson)) // Object from parsed JSON request body, otherwise string context.log(gson.toString(context.req.headers)) // String key-value pairs of all request headers, keys are lowercase context.log(context.req.scheme) // Value of the x-forwarded-proto header, usually http or https context.log(context.req.method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.req.url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.req.host) // Hostname from the host header, such as awesome.appwrite.io context.log(context.req.port) // Port from the host header, for example 8000 context.log(context.req.path) // Path part of URL, for example /v1/hooks context.log(context.req.queryString) // Raw query params string. For example "limit=12&offset=50" context.log(gson.toString(context.req.query)) // Parsed query params. For example, req.query.limit return context.res.text("All the request parameters are logged to the Appwrite Console.") } } ``` ```java package io.openruntimes.java; import com.google.gson.Gson; import io.openruntimes.java.models.RuntimeContext; import io.openruntimes.java.models.RuntimeOutput; public class Main { public RuntimeOutput main(RuntimeContext context) { Gson gson = new Gson(); context.log(gson.toString(context.getReq().getBody())); // Object from parsed JSON request body, otherwise string context.log(gson.toString(context.getReq().getHeaders())); // String key-value pairs of all request headers, keys are lowercase context.log(context.getReq().getScheme()); // Value of the x-forwarded-proto header, usually http or https context.log(context.getReq().getMethod()); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc. context.log(context.getReq().getUrl()); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50 context.log(context.getReq().getHost()); // Hostname from the host header, such as awesome.appwrite.io context.log(context.getReq().getPort()); // Port from the host header, for example 8000 context.log(context.getReq().getPath()); // Path part of URL, for example /v1/hooks context.log(context.getReq().getQueryString()); // Raw query params string. For example "limit=12&offset=50" context.log(gson.toString(context.getReq().getQuery())); // Parsed query params. For example, req.query.limit return context.getRes().text("All the request parameters are logged to the Appwrite Console."); } } ``` {% /multicode %} ##### Headers {% #headers %} Appwrite Functions will always receive a set of headers that provide meta data about the function execution. These are provided alongside any custom headers sent to the function. | Variable | Description | |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `x-appwrite-trigger` | Describes how the function execution was invoked. Possible values are `http`, `schedule` or `event`. | | `x-appwrite-event` | If the function execution was triggered by an event, describes the triggering event. | | `x-appwrite-key` | The dynamic API key is used for server authentication. [Learn more about dynamic api keys](/docs/products/functions/develop#dynamic-api-key). | | `x-appwrite-user-id` | If the function execution was invoked by an authenticated user, display the user ID. This doesn't apply to Appwrite Console users or API keys. | | `x-appwrite-user-jwt` | JWT token generated from the invoking user's session. Used to authenticate Server SDKs to respect access permissions. [Learn more about JWT tokens](/docs/products/auth/jwt). | | `x-appwrite-country-code` | Displays the country code of the configured locale. | | `x-appwrite-continent-code` | Displays the continent code of the configured locale. | | `x-appwrite-continent-eu` | Describes if the configured local is within the EU. | | `x-appwrite-client-ip` | Displays the IP of the client creating the execution. | | `x-appwrite-execution-id` | Displays the ID of the current execution. | #### Response {% #response %} Use the response object to send a response to the function caller. This could be a user, client app, or an integration. The response information **will not be logged** to the Appwrite Console. There are several possible ways to send a response, explore them in the following Appwrite Function. ##### Response types {% #response-types %} {% table %} * Response * Description --- * `empty` * Sends a response with a `code 204 No Content` status. --- * `json` * Converts the data into a JSON string and sets the content-type header to `application/json`. --- * `binary` * Packages binary bytes, the status code, and the headers into an object. --- * `redirect` * Redirects the client to the specified URL link. --- * `text` * Converts the body using UTF-8 encoding into a binary Buffer. {% /table %} {% multicode %} ```server-nodejs const fs = require('fs'); export default async ({ req, res, log }) => { switch (req.query.type) { case 'empty': return res.empty(); case 'json': return res.json({"type": "This is a JSON response"}); case 'binary': const bytes = await fs.readFile('file.png'); return res.binary(bytes); case 'redirect': return res.redirect("https://appwrite.io", 301); case 'html': return res.text( "<h1>This is an HTML response</h1>", 200, { "content-type": "text/html" }); default: return res.text("This is a text response"); } } ``` ```php <?php return function ($context) { switch ($context->req->query['type']) { case 'empty': return $context->res->empty(); case 'json': return $context->res->json(["type" => "This is a JSON response"]); case 'binary': $fileContent = file_get_contents('file.png'); return $context->res->binary($fileContent); case 'redirect': return $context->res->redirect("https://appwrite.io", 301); case 'html': return $context->res->text("<h1>This is an HTML response</h1>", 200, [ "content-type" => "text/html" ]); default: return $context->res->text("This is a text response"); } }; ``` ```python def main(context): type = context.req.query['type'] if type == 'empty': return context.res.empty() elif type == 'json': return context.res.json({"type": "This is a JSON response"}) elif type == 'binary': with open('file.png', 'rb') as file: file_contents = file.read() return context.res.binary(file_contents) elif type == 'redirect': return context.res.redirect("https://appwrite.io", 301) elif type == 'html': return context.res.text("<h1>This is an HTML response</h1>", 200, { "content-type": "text/html" }) else: return context.res.text("This is a text response") ``` ```ruby def main(context) case context.req.query['type'] when 'empty' return context.res.empty() when 'json' return context.res.json({"type": "This is a JSON response"}) when 'binary' file_contents = File.binread('file.png') return context.res.binary(file_contents) when 'redirect' return context.res.redirect("https://appwrite.io", 301) when 'html' return context.res.text("<h1>This is an HTML response</h1>", 200, { "content-type": "text/html" }) else return context.res.text("This is a text response") end end ``` ```deno export default async ({ req, res, log }) => { switch (req.query.type) { case 'empty': return res.empty(); case 'json': return res.json({type: "This is a JSON response"}); case 'binary': const fileContents = await Deno.readFile('file.png'); return res.binary(fileContents); case 'redirect': return res.redirect("https://appwrite.io", 301); case 'html': return res.text( "<h1>This is an HTML response</h1>", 200, { "content-type": "text/html" }); default: return res.text("This is a text response"); } } ``` ```go package handler import ( "io" "os" "embed" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) //go:embed images/*.png var images embed.FS func Main(Context openruntimes.Context) openruntimes.Response { switch Context.Req.Query["type"] { case "empty": return Context.Res.Empty() case "json": return Context.Res.Json(map[string]string{"type": "This is a JSON response"}) case "binary": imageData, _ := images.ReadFile("file.png") return Context.Res.Binary(imageData) case "redirect": return Context.Res.Redirect("https://appwrite.io") case "html": return Context.Res.Text("<h1>This is an HTML response</h1>") default: return Context.Res.Text("This is a text response") } } ``` ```dart import 'dart:io'; import 'dart:async'; Future<dynamic> main(final context) async { switch (context.req.query['type']) { case 'empty': return context.res.empty(); case 'json': return context.res.json({'type': 'This is a JSON response'}); case 'binary': final file = File('file.png'); final fileContents = await file.readAsBytes(); return context.res.binary(fileContents); case 'redirect': return context.res.redirect('https://appwrite.io', 301); case 'html': return context.res.text('<h1>This is an HTML response</h1>', 200, {'content-type': 'text/html'}); default: return context.res.text('This is a text response'); } } ``` ```swift import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { switch context.req.query["type"] { case "empty": return context.res.empty() case "json": return context.res.text(["type": "This is a JSON response"]) case "binary": let fileContents = FileManager.default.contents(atPath: "file.png") return context.res.binary(fileContents) case "redirect": return context.res.redirect("https://appwrite.io", 301) case "html": return context.res.text("<h1>This is an HTML response</h1>", 200, [ "content-type": "text/html" ]) default: return context.res.text("This is a text response") } } ``` ```csharp public class Handler { public async Task<RuntimeOutput> Main(RuntimeContext Context) { switch (Context.Request.Query["type"]) { case "empty": return Context.Res.Empty(); case "json": return Context.Res.Text(new Dictionary<string, object>() { { "type", "This is a JSON response" } }); case "binary": return Context.Res.Binary(File.ReadAllBytes("file.png")); case "redirect": return Context.Res.Redirect("https://appwrite.io", 301); case "html": return Context.Res.Text("<h1>This is an HTML response</h1>", 200, new Dictionary<string, string>() { { "content-type", "text/html" } }); default: return Context.Res.Text("This is a text response"); } } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput class Main { fun main(context: RuntimeContext): RuntimeOutput { when (context.req.query["type"]) { "empty" -> return context.res.empty() "json" -> return context.res.text(mapOf("type" to "This is a JSON response")) "binary" -> return context.res.binary(File("file.png").readBytes()) "redirect" -> return context.res.redirect("https://appwrite.io", 301) "html" -> return context.res.text("<h1>This is an HTML response</h1>", 200, mapOf("content-type" to "text/html")) else -> return context.res.text("This is a text response") } } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; import java.util.Map; import java.util.HashMap; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { switch (context.getReq().getQuery()["type"]) { case "text": return context.getRes().empty(); case "json": HashMap<String, Object> data = new HashMap<>(); data.put("type", "This is a JSON response"); return context.getRes().text(data); case "binary" return context.getRes().binary(Files.readAllBytes(Paths.get("file.png"))); case "redirect": return context.getRes().redirect("https://appwrite.io", 301); case "html": return context.getRes().text("<h1>This is an HTML response</h1>", 200, Map.of("content-type", "text/html")); default: return context.getRes().text("This is a text response"); } } } ``` ```cpp #include "../RuntimeResponse.h" #include "../RuntimeRequest.h" #include "../RuntimeOutput.h" #include "../RuntimeContext.h" namespace runtime { class Handler { public: static RuntimeOutput main(RuntimeContext &context) { std::string type = context.req.query["type"]; if (type == "empty") { return context.res.empty(); } else if (type == "json") { Json::Value data; data["type"] = "This is a JSON response"; return context.res.text(data); } else if (type == "binary") { std::vector<char> buffer(std::istreambuf_iterator<char>(std::ifstream("file.png", std::ios::binary)), {}); return context.res.binary(buffer) } else if (type == "redirect") { return context.res.redirect("https://appwrite.io", 301); } else if (type == "html") { Json::Value headers; headers["content-type"] = "text/html"; return context.res.text("<h1>This is an HTML response</h1>", 200, headers); } else { return context.res.text("This is a text response"); } } }; } ``` {% /multicode %} To get the different response types, set one of the following query parameters in the [generated domain](/docs/products/functions/domains) of your function. | Type | Query Param | Example | |----------|-----------------|-------------------------------------------------------------| | `text` | `/?type=text` | `https://64d4d22db370ae41a32e.appwrite.global/?type=text` | | `json` | `/?type=json` | `https://64d4d22db370ae41a32e.appwrite.global/?type=json` | | `redirect` | `/?type=redirect` | `https://64d4d22db370ae41a32e.appwrite.global/?type=redirect` | | `html` | `/?type=html` | `https://64d4d22db370ae41a32e.appwrite.global/?type=html` | | `empty` | `/` | `https://64d4d22db370ae41a32e.appwrite.global/` | #### Logging {% #logging %} To protect user privacy, the request and response objects are not logged to the Appwrite Console by default. We support the spread operator across most of the languages, meaning you can write code that is more concise and flexible. This means, to see logs or debug function executions you need to use the `log()` and `error()` methods. These logs are only visible to developers with access to the Appwrite Console. Here's an example of using logs and errors. {% multicode %} ```server-nodejs export default async ({ req, res, log, error }) => { const message = "This is a log, use for logging information to console"; log("Message: ", message); log(`This function was called with ${req.method} method`); const errorMessage = "This is an error, use for logging errors to console" error("Error: ", errorMessage); return res.text("Check the Appwrite Console to see logs and errors!"); }; ``` ```php <?php return function ($context) { $message = "This is a log, use for logging information to console"; $context->log("Message: ", message); $context->log("This function was called with " . $context->req->method . " method"); $errorMessage = "Check the Appwrite Console to see logs and errors!" $context->error("Error: ", errorMessage); return $context->text("Check the Appwrite Console to see logs and errors!"); }; ``` ```python def main(context): message = "This is a log, use for logging information to console" context.log("Message: ", message) context.log(f"This function was called with {context.req.method} method") errorMessage = "This is an error, use for logging errors to console" context.error("Error: ", errorMessage) return context.res.text("Check the Appwrite Console to see logs and errors!") ``` ```ruby def main(context) message = "This is a log, use for logging information to console" context.log("Message: ", message) context.log("This function was called with #{context.req.method} method") errorMessage = "This is an error, use for logging errors to console" context.error("Error: ", errorMessage) return context.res.text("Check the Appwrite Console to see logs and errors!") end ``` ```deno export default async ({ res, log, error }: any) => { let message = "This is a log, use for logging information to console"; log("Message: ", message); log(`This function was called with ${context.req.method} method`); let errorMessage = "This is an error, use for logging errors to console"; error("Error: ", errorMessage); return res.text("Check the Appwrite Console to see logs and errors!"); }; ``` ```go package handler import ( "fmt" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { message := "This is a log, use for logging information to console" Context.Log("Message: ", message) Context.Log(fmt.Sprintf("This function was called with %s method", Context.Req.Method)) errorMessage := "This is an error, use for logging errors to console" Context.Error("Error: ", errorMessage) return Context.Res.Text("Check the Appwrite Console to see logs and errors!") } ``` ```dart import 'dart:async'; Future<dynamic> main(final context) async { var message = "This is a log, use for logging information to console"; context.log("message: ", var); context.log("This function was called with ${context.req.method} method"); var errorMessage = "This is an error, use for logging errors to console"; context.error("Error: ", errorMessage); return context.res.text("Check the Appwrite Console to see logs and errors!"); } ``` ```swift import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { var message: String = "This is a log, use for logging information to console" context.log("Message: ", message) context.log("This function was called with \(context.req.method) method") var message: String = "This is an error, use for logging errors to console" context.error("Error: ", message) return context.res.text("Check the Appwrite Console to see logs and errors!") } ``` ```csharp namespace DotNetRuntime; public class Handler { public async Task<RuntimeOutput> Main(RuntimeContext Context) { string message = "This is a log, use for logging information to console"; Context.Log("Message: ", message); Context.Log($"This function was called with {Context.Req.Method} method"); string errorMessage = "This is an error, use for logging errors to console"; Context.Error("Error: ", errorMessage); return Context.Res.Text("Check the Appwrite Console to see logs and errors!"); } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput class Main { fun main(context: RuntimeContext): RuntimeOutput { var message: String = "This is a log, use for logging information to console" context.log("Message: ", message) context.log("This function was called with ${context.req.method} method") var errorMessage: String = "This is an error, use for logging errors to console" context.error("Error: ", errorMessage) return context.res.text("Check the Appwrite Console to see logs and errors!") } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { String message = "This is a log, use for logging information to console"; context.log("Message: ", message); context.log("This function was called with " + context.req.method + " method"); string errorMessage = "This is an error, use for logging errors to console"; context.error("Error: ", errorMessage); return context.getRes().text("Check the Appwrite Console to see logs and errors!"); } } ``` ```cpp #include "../RuntimeResponse.h" #include "../RuntimeRequest.h" #include "../RuntimeOutput.h" #include "../RuntimeContext.h" namespace runtime { class Handler { public: static RuntimeOutput main(RuntimeContext &context) { const std::string message = "This is a log, use for logging information to console"; context.log("Message: ", message); context.log("This function was called with " + context.req.method + " method"); const std::string errorMessage = "This is an error, use for logging errors to console"; context.error("Error: ", errorMessage); return context.res.text("Check the Appwrite Console to see logs and errors!"); } }; } ``` {% /multicode %} You can access these logs through the following steps. 1. In Appwrite Console, navigate to Functions. 2. Click to open a function you wish to inspect. 3. Under the Executions tab, click on an execution. 4. In the Response section, you'll be able to view logs under the Logs and Errors tabs. ### Accessing environment variables {% #environment-variables %} If you need to pass constants or secrets to Appwrite Functions, you can use environment variables. | Variable | Description | Available at Build and/or Run Time | |-----------------------------------|------------------------------------------------|-----------------------------------------------------| | `APPWRITE_FUNCTION_API_ENDPOINT` | The API endpoint of the running function | Both | | `APPWRITE_VERSION` | The Appwrite version used to run the function | Both | | `APPWRITE_REGION` | The region where the function will run from | Both | | `APPWRITE_FUNCTION_API_KEY` | The function API key is used for server authentication | Build time | | `APPWRITE_FUNCTION_ID` | The ID of the running function. | Both | | `APPWRITE_FUNCTION_NAME` | The Name of the running function. | Both | | `APPWRITE_FUNCTION_DEPLOYMENT` | The deployment ID of the running function. | Both | | `APPWRITE_FUNCTION_PROJECT_ID` | The project ID of the running function. | Both | | `APPWRITE_FUNCTION_RUNTIME_NAME` | The runtime of the running function. | Both | | `APPWRITE_FUNCTION_RUNTIME_VERSION` | The runtime version of the running function. | Both | {% arrow_link href="/docs/products/functions/functions#environment-variables" %} Learn to add variables to you function {% /arrow_link %} You can access the environment variables through the systems library of each language. {% multicode %} ```server-nodejs export default async ({ req, res, log }) => { return res.text(process.env.MY_VAR); } ``` ```php <?php return function ($context) { return $context->res->text(getenv('MY_VAR')); }; ``` ```python def main(context): return context.res.text(os.environ['MY_VAR']) ``` ```ruby def main(context) return context.res.text(ENV['MY_VAR']) end ``` ```deno export default async ({ req, res, log }) => { return res.text(Deno.env.get('MY_VAR')); } ``` ```go package handler import ( "os" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { return res.text(os.Getenv(MY_VAR)) } ``` ```dart import 'dart:io'; import 'dart:async'; Future<dynamic> main(final context) async { return context.res.text(Platform.environment['MY_VAR']); } ``` ```swift import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { return context.res.text(ProcessInfo.processInfo.environment["MY_VAR"]) } ``` ```csharp namespace DotNetRuntime; public class Handler { public async Task<RuntimeOutput> Main(RuntimeContext Context) { return Context.Res.Text(Environment.GetEnvironmentVariable("MY_VAR")); } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput class Main { fun main(context: RuntimeContext): RuntimeOutput { return context.res.text(System.getenv("MY_VAR")) } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { return context.getRes().text(System.getenv("MY_VAR")); } } ``` ```cpp #include "../RuntimeResponse.h" #include "../RuntimeRequest.h" #include "../RuntimeOutput.h" #include "../RuntimeContext.h" namespace runtime { class Handler { public: static RuntimeOutput main(RuntimeContext &context) { return context.res.text(std::getenv("MY_VAR")); } }; } ``` {% /multicode %} ### Dependencies {% #dependencies %} To install your dependencies before your function is built, you should add the relevant install command to the top your function's **Build setting** > **Commands**. You can find this setting under **Functions** > your function > **Settings** > **Configuration** > **Build settings**. Make sure to include dependency files like `package.json`, `composer.json`, `requirements.txt`, etc. in your function's configured [root directory](/docs/products/functions/deploy-from-git#root-directory). Do not include the dependency folders like `node_modules`, `vendor`, etc. in your function's root directory. The dependencies installed for your local OS may not work in the executor environments Your function's dependencies should be managed by the package manager of each language. By default, we include the following package managers in each runtime. {% table %} *   {% width=80 %} * Language * Package Manager * Commands --- * {% only_dark %}{% icon_image src="/images/platforms/dark/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_light %} * Node.js * NPM * `npm install` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/php.svg" alt="PHP logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/php.svg" alt="PHP logo" size="m" /%}{% /only_light %} * PHP * Composer * `composer install` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/python.svg" alt="Python logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/python.svg" alt="Python logo" size="m" /%}{% /only_light %} * Python * pip * `pip install -r requirements.txt` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_light %} * Ruby * Bundler * `bundle install` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/deno.svg" alt="Deno logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/deno.svg" alt="Deno logo" size="m" /%}{% /only_light %} * Deno * deno * `deno cache <ENTRYPOINT_FILE>` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/go.svg" alt="Go logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/go.svg" alt="Go logo" size="m" /%}{% /only_light %} * Go * Go Modules * N/A --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dart.svg" alt="Dart logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dart.svg" alt="Dart logo" size="m" /%}{% /only_light %} * Dart * pub * `pub get` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/swift.svg" alt="Swift logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/swift.svg" alt="Swift logo" size="m" /%}{% /only_light %} * Swift * Swift Package Manager * `swift package resolve` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_light %} * .NET * NuGet * `dotnet restore` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/bun.svg" alt="Bun logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/bun.svg" alt="Bun logo" size="m" /%}{% /only_light %} * Bun * bun * `bun install` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_light %} * Kotlin * Gradle * N/A --- * {% only_dark %}{% icon_image src="/images/platforms/dark/java.svg" alt="Java logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/java.svg" alt="Java logo" size="m" /%}{% /only_light %} * Java * Gradle * N/A --- * {% only_dark %}{% icon_image src="/images/platforms/dark/c.svg" alt="C++ logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/c.svg" alt="C++ logo" size="m" /%}{% /only_light %} * C++ * None * N/A {% /table %} ### Using Appwrite in a function {% #using-appwrite %} Appwrite can be used in your functions by adding the relevant SDK to your function's dependencies. Authenticating with Appwrite is done via a dynamic API key or a JWT token. #### Dynamic API key {% #dynamic-api-key %} Dynamic API keys are the same as [API keys](/docs/advanced/platform/api-keys) but are automatically generated. They are generated in your functions per execution. However, you can only use dynamic API keys inside Appwrite functions. During the build process, dynamic API keys are automatically provided as the environment variable `APPWRITE_FUNCTION_API_KEY`. This environment variable doesn't need to be initialized. During execution, dynamic API keys are automatically provided in the `x-appwrite-key` [header](#headers). Dynamic API keys grant access and operate without sessions. They allow your function to act as an admin-type role instead of acting on behalf of a user. Update the function settings to configure the scopes of the function. 1. In Appwrite Console, navigate to **Functions**. 2. Click to open a function you wish to configure. 3. Under the **Settings** tab, navigate to **Scopes**. 4. Select the scopes you want to grant the dynamic key. 5. It is best practice to allow only necessary permissions. {% multicode %} ```server-nodejs import { Client, TablesDB, ID } from 'node-appwrite'; export default async ({ req, res, log, error }) => { // Set project and set API key const client = new Client() .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(req.headers['x-appwrite-key']); const tablesDB = new TablesDB(client); try { await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID.unique(), data: {} }) } catch (e) { error("Failed to create row: " + e.message) return res.text("Failed to create row") } return res.text("Row created") } ``` ```php <?php require(__DIR__ . '/../vendor/autoload.php'); use Appwrite\Client; use Appwrite\Exception; use Appwrite\Services\TablesDB; use Appwrite\ID; return function ($context) { // Set project and set API key $client = (new Client()) ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID')) ->setKey($context->req->headers['x-appwrite-key']); $tablesDB = new TablesDB($client); try { $tablesDB->createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID::unique(), data: [] ); } catch (Exception $e) { $context->error("Failed to create row: " . $e->getMessage()); return $context->res->text("Failed to create row"); } return $context->res->text("Row created"); }; ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.id import ID import os def main(context): # Set project and set API key client = ( Client() .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) .set_key(context.req.headers["x-appwrite-key"]) ) tablesDB = TablesDB(client) try: tablesDB.create_row( database_id="<DATABASE_ID>", table_id="<TABLE_ID>", row_id=ID.unique(), data={} ) except Exception as e: context.error("Failed to create row: " + e.message) return context.response.text("Failed to create row") return context.response.text("Row created") ``` ```ruby require "appwrite" include Appwrite def main(context) # Set project and set API key client = Appwrite::Client.new .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID']) .set_key(context.req.headers['x-appwrite-key']) tablesDB = Appwrite::TablesDB.new(client) begin tablesDB.create_row( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID.unique(), data: {} ) rescue Exception => e context.error("Failed to create row: " + e.message) return context.response.text("Failed to create row") end return context.response.text("Row created") end ``` ```deno import { Client, TablesDB, ID } from "npm:node-appwrite"; export default function ({req, res, error}: any){ // Set project and set API key const client = new Client() .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID")) .setKey(req.headers["x-appwrite-key"] || ""); const tablesDB = new TablesDB(client); try { tablesDB.createRow( "<DATABASE_ID>", "<TABLE_ID>", ID.unique(), {} ); } catch (e) { error("Failed to create row: " + e.message); return res.text("Failed to create row"); } return res.text("Row created"); } ``` ```go package handler import ( "fmt" "os" "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/id" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { // Set project and set API key client := appwrite.NewClient( appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")), appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]), ) databases := appwrite.NewTablesDB(client) _, err := databases.createRow( "<DATABASE_ID>", "<TABLE_ID>", id.Unique(), map[string]interface{}{}, ) if err != nil { Context.Log(fmt.Sprintf("Failed to create row: %v", err)) return Context.Res.Text("Failed to create row") } return Context.Res.Text("Row created") } ``` ```dart import 'dart:io'; import 'dart:async'; import 'package:dart_appwrite/dart_appwrite.dart'; Future<dynamic> main(final context) async { // Set project and set API key final client = Client() .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID']) .setKey(context.req.headers['x-appwrite-key']); final tablesDB = TablesDB(client); try { await tablesDB.createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID.unique(), data: {} ); } catch (e) { context.error("Failed to create row: " + e.message); return context.res.text("Failed to create row"); } return context.res.text("Row created"); } ``` ```swift import Appwrite import AppwriteModels import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { // Set project and set API key let client = Client() .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"]) .setKey(context.req.headers["x-appwrite-key"] ?? "") let tablesDB = TablesDB(client: client) do { try await tablesDB.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: ID.unique(), data: [:] ) } catch { context.error("Failed to create row: \(error.localizedDescription)") return context.res.text("Failed to create row") } return context.res.text("Row created") } ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; namespace DotNetRuntime { public class Handler { public async Task Main(RuntimeContext Context) { // Set API var client = new Client() .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID")) .SetKey(Context.Req.Headers["x-appwrite-key"]); var tablesDB = new TablesDB(client); try { await databases.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: ID.Unique(), data: new Dictionary<string, object>()); } catch (Exception e) { Context.Error("Failed to create row: " + e.Message); return Context.Response.Text("Failed to create row"); } return Context.Response.Text("Row created"); } } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.ID import java.util.HashMap class Main { fun main(context: RuntimeContext): RuntimeOutput { // Set project and set API key val client = Client() .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) .setKey(context.req.headers["x-appwrite-key"]) val tablesDB = TablesDB(client) try { tablesDB.createRow( databaseId = "<DATABASE_ID>", tableId = "<TABLE_ID>", rowId = ID.unique() data = mapOf() ) } catch (e: Exception) { context.error("Failed to create row: " + e.message) return context.res.text("Failed to create row") } return context.res.text("Row created") } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; import java.util.HashMap; import io.appwrite.Client; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { // Set project and set API key Client client = new Client(); .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) .setKey(context.getReq().getHeaders().get("x-appwrite-key")); Databases tablesDB = new TablesDB(client); try { tablesDB.createRow( "<DATABASE_ID>", "<TABLE_ID>", ID.unique(), new HashMap<>() ); } catch (Exception e) { context.error("Failed to create row: " + e.getMessage()); return context.res.text("Failed to create row"); } return context.res.text("Row created"); } } ``` {% /multicode %} #### Using with JWT {% #using-jwt %} JWTs allow you to act on behalf of an user in your Appwrite Function. When using JWTs, you will be able to access and change **only** the resources with the same permissions as the user account that signed the JWT. This preserves the permissions you configured on each resource. If the Appwrite Function is invoked by an authenticated user, the `x-appwrite-user-jwt` header is automatically passed in. {% multicode %} ```server-nodejs import { Client, TablesDB, ID } from 'node-appwrite'; export default async ({ req, res, log }) => { const client = new Client() .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) if (req.headers['x-appwrite-user-jwt']) { client.setJWT(req.headers['x-appwrite-user-jwt']) } else { return res.text("Access denied: This function requires authentication. Please sign in to continue."); } const tablesDB = new TablesDB(client); try { await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID.unique(), data: {} }) } catch (e) { log("Failed to create row: " + e.message) return res.text("Failed to create row") } return res.text("Row created") } ``` ```php <?php require(__DIR__ . '/../vendor/autoload.php'); use Appwrite\Client; use Appwrite\Exception; use Appwrite\Services\TablesDB; use Appwrite\ID; return function ($context) { $client = new (Client()) ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID')) if (isset($context->req->headers['x-appwrite-user-jwt'])) { $client->setJWT($context->req->headers['x-appwrite-user-jwt']); } else { return $context->res->text("Access denied: This function requires authentication. Please sign in to continue."); } $tablesDB = new TablesDB($client); try { $tablesDB->createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID::unique(), data: [] ); } catch (Exception $e) { $context->error("Failed to create row: " . $e->getMessage()); return $context->res->text("Failed to create row"); } return $context->res->text("Row created"); }; ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.id import ID import os def main(context): client = ( Client() .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) ) if "x-appwrite-user-jwt" in context.req.headers: client.set_jwt(context.req.headers["x-appwrite-user-jwt"]) else: return context.res.text("Access denied: This function requires authentication. Please sign in to continue.") tablesDB = TablesDB(client) try: tablesDB.create_row( database_id="<DATABASE_ID>", table_id="<TABLE_ID>", row_id=ID.unique(), data={} ) except Exception as e: context.error("Failed to create row: " + e.message) return context.response.text("Failed to create row") return context.response.text("Row created") ``` ```ruby require "appwrite" include Appwrite def main(context) client = Client.new .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID']) if context.request.headers['x-appwrite-user-jwt'] client.set_jwt(context.request.headers['x-appwrite-user-jwt']) else return context.response.text("Access denied: This function requires authentication. Please sign in to continue.") end tablesDB = Appwrite::TablesDB.new(client) begin tablesDB.create_row('<DATABASE_ID>', '<TABLE_ID>', Appwrite::ID.unique(), {}) rescue Appwrite::Exception => e context.error("Failed to create row: " + e.message) return context.response.text("Failed to create row") end return context.response.text("Row created") end ``` ```deno import { Client, TablesDB, ID } from "npm:node-appwrite"; export default function ({req, res, error}: any){ const client = new Client() .setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") || "") if (req.headers["x-appwrite-user-jwt"]) { client.setJWT(req.headers["x-appwrite-user-jwt"]); } else { return res.text("Access denied: This function requires authentication. Please sign in to continue."); } const tablesDB = new TablesDB(client); try { tablesDB.createRow( "<DATABASE_ID>", "<TABLE_ID>", ID.unique(), {} ); } catch (e) { error("Failed to create row: " + e.message) return res.text("Failed to create row"); } return res.text("Row created"); } ``` ```go package handler import ( "fmt" "log" "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/id" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { client := appwrite.NewClient( appwrite.WithProject("APPWRITE_FUNCTION_PROJECT_ID"), ) jwt, exists := Context.Req.Headers["x-appwrite-user-jwt"] if !exists || len(jwt) == 0 { appwrite.WithJWT(Context.Req.Headers["x-appwrite-user-jwt"]) } else { return Context.Res.Text("Access denied: This function requires authentication. Please sign in to continue.") } databases := appwrite.NewTablesDB(client) _, err := databases.createRow( "<DATABASE_ID>", "<TABLE_ID>", id.Unique(), map[string]interface{}{}, ) if err != nil { Context.Log(fmt.Sprintf("Failed to create row: %v", err)) return Context.Res.Text(str) } return Context.Res.Text("Row created") } ``` ```dart import 'dart:io'; import 'dart:async'; import 'package:dart_appwrite/dart_appwrite.dart'; Future<dynamic> main(final context) async { final client = Client() .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID']) if (context.req.headers['x-appwrite-user-jwt'] != null) { client.setJWT(context.req.headers['x-appwrite-user-jwt']); } else { return context.res.text("Access denied: This function requires authentication. Please sign in to continue."); } final tablesDB = TablesDB(client); try { await tablesDB.createRow( databaseId: '<DATABASE_ID>', tableId: '<TABLE_ID>', rowId: ID.unique(), data: {} ); } catch (e) { context.error("Failed to create row: " + e.message); return context.res.text("Failed to create row"); } return context.res.text("Row created"); } ``` ```swift import Appwrite import AppwriteModels import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { let client = Client() .setProject(ProcessInfo.processInfo.environment["APPWRITE_FUNCTION_PROJECT_ID"]) if let jwt = context.req.headers["x-appwrite-user-jwt"] { client.setJWT(jwt) } else { return context.res.text("Access denied: This function requires authentication. Please sign in to continue.") } let tablesDB = TablesDB(client: client) do { try await tablesDB.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: ID.unique() data: [:] ) } catch { context.error("Failed to create row: \(error.localizedDescription)") return context.res.text("Failed to create row") } return context.res.text("Row created") } ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; namespace DotNetRuntime { public class Handler { public async Task Main(RuntimeContext Context) { var client = new Client() .SetProject(Environment.GetEnvironmentVariable("APPWRITE_FUNCTION_PROJECT_ID")) if (Context.Req.Headers.ContainsKey("x-appwrite-user-jwt")) { client.SetJWT(Context.Req.Headers["x-appwrite-user-jwt"]); } else { return Context.Res.Text("Access denied: This function requires authentication. Please sign in to continue"); } var tablesDB = new TablesDB(client); try { await databases.createRow( databaseId: "<DATABASE_ID>", tableId: "<TABLE_ID>", rowId: ID.Unique(), data: new Dictionary<string, object>()); } catch (Exception e) { Context.Error("Failed to create row: " + e.Message); return Context.Res.Text("Failed to create row"); } return Context.Res.Text("Row created"); } } } ``` ```kotlin package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput import io.appwrite.Client import io.appwrite.services.TablesDB import io.appwrite.ID import java.util.HashMap class Main { fun main(context: RuntimeContext): RuntimeOutput { val client = Client() .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) if (context.req.headers["x-appwrite-user-jwt"] != null) { client.setJWT(context.req.headers["x-appwrite-user-jwt"]) } else { return context.res.text("Access denied: This function requires authentication. Please sign in to continue.") } val tablesDB = TablesDB(client) try { tablesDB.createRow( databaseId = "<DATABASE_ID>", tableId = "<TABLE_ID>", rowId = ID.unique(), data = mapOf() ) } catch (e: Exception) { context.error("Failed to create row: " + e.message) return context.res.text("Failed to create row") } return context.res.text("Row created") } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; import java.util.HashMap; import io.appwrite.Client; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { Client client = new Client() .setProject(System.getenv("APPWRITE_FUNCTION_PROJECT_ID")) if (context.req.headers.containsKey("x-appwrite-user-jwt")) { client.setJWT(context.req.headers.get("x-appwrite-user-jwt")); } else { return context.res.text("Access denied: This function requires authentication. Please sign in to continue."); } Databases tablesDB = new TablesDB(client); try { tablesDB.createRow( "<DATABASE_ID>", "<TABLE_ID>", ID.unique(), new HashMap<>() ); } catch (Exception e) { context.error("Failed to create row: " + e.getMessage()); return context.res.text("Failed to create row"); } return context.res.text("Row created"); } } ``` {% /multicode %} ### Code structure {% #code-structure %} As your functions grow, you may find yourself needing to split your code into multiple files. This helps you keep your codebase maintainable and easy to read. Here's how you can accomplish code splitting. {% tabs %} {% tabsitem #nodejs title="Node.js" %} ```server-nodejs // src/utils.js export function add(a, b) { return a + b; } ``` ```server-nodejs // src/main.js import { add } from './utils.js'; export default function ({ res }) { return res.text(add(1, 2)); } ``` {% /tabsitem %} {% tabsitem #php title="PHP" %} ```php <?php // src/utils.php function add($a, $b) { return $a + $b; } ``` ```php <?php // src/main.php require_once(__DIR__ . '/utils.php'); return function ($context) { return $context->res->text(add(1, 2)); }; ``` {% /tabsitem %} {% tabsitem #python title="Python" %} ```python ### src/utils.py def add(a, b): return a + b ``` ```python ### src/main.py from .utils import add def main(context): return context.res.text(add(1, 2)) ``` {% /tabsitem %} {% tabsitem #ruby title="Ruby" %} ```ruby ### lib/utils.rb def add(a, b) return a + b end ``` ```ruby ### lib/main.rb require_relative 'utils' def main(context) return context.res.text(add(1, 2)) end ``` {% /tabsitem %} {% tabsitem #deno title="Deno" %} ```deno // src/utils.ts export function add(a: number, b: number): number { return a + b; } ``` ```deno // src/main.ts import { add } from './utils.ts'; export default function ({res}: {res: any}) { return res.text(add(1, 2)); } ``` {% /tabsitem %} {% tabsitem #go title="Go" %} ```go // src/utils/go.mod module example.com/utils go 1.23.0 ``` ```go // src/utils/utils.go package utils func Add(a int, b int) int { return a + b } ``` ```go // src/main/go.mod module example.com/main go 1.23.0 replace example.com/utils => ../utils // Run go mod edit -replace example.com/go=../go require example.com/utils v0.0.0-00010101000000-000000000000 // Run go mod tidy ``` ```go // src/main/main.go package main import "example.com/utils" func main() { // Get a greeting message and print it. message := utils.Add(5, 4) print(message) } ``` {% /tabsitem %} {% tabsitem #dart title="Dart" %} ```dart // lib/utils.dart int add(int a, int b) { return a + b; } ``` ```dart // lib/main.dart import 'dart:async'; import 'package:package_name/utils.dart'; Future<dynamic> main(final context) async { return context.res.text(add(1, 2)); } ``` {% /tabsitem %} {% tabsitem #swift title="Swift" %} ```swift // Sources/utils.swift func add(_ a: Int, _ b: Int) -> Int { return a + b } ``` ```swift // Sources/index.swift import Foundation func main(context: RuntimeContext) async throws -> RuntimeOutput { return context.res.text(add(1, 2)) } ``` {% /tabsitem %} {% tabsitem #dotnet title=".NET" %} ```csharp // src/Utils.cs namespace DotNetRuntime { public static class Utils { public static int Add(int a, int b) { return a + b; } } } ``` ```csharp // src/Index.cs namespace DotNetRuntime { public class Handler { public async Task<RuntimeOutput> Main(RuntimeContext Context) { return Context.Res.Text(Utils.Add(1, 2)); } } } ``` {% /tabsitem %} {% tabsitem #kotlin title="Kotlin" %} ```kotlin // src/Utils.kt package io.openruntimes.kotlin.src object Utils { fun add(a: Int, b: Int): Int { return a + b } } ``` ```kotlin // src/Main.kt package io.openruntimes.kotlin.src import io.openruntimes.kotlin.RuntimeContext import io.openruntimes.kotlin.RuntimeOutput import io.openruntimes.kotlin.Utils class Main { fun main(context: RuntimeContext): RuntimeOutput { return context.res.text(Utils.add(1, 2)) } } ``` {% /tabsitem %} {% tabsitem #java title="Java" %} ```java // src/Utils.java package io.openruntimes.java.src; class Utils { public static int add(int a, int b) { return a + b; } } ``` ```java package io.openruntimes.java.src; import io.openruntimes.java.RuntimeContext; import io.openruntimes.java.RuntimeOutput; import io.openruntimes.java.Utils; public class Main { public RuntimeOutput main(RuntimeContext context) throws Exception { return context.res.text(Utils.add(1, 2)); } } ``` {% /tabsitem %} {% /tabs %} --- ## Develop locally https://appwrite.io/docs/products/functions/develop-locally Develop your Appwrite functions locally to make code changes without redeploying your function on every code change and hot reload your code for faster testing. ### Setup {% #setup %} We use Docker to replicate the production environment for the local deployment of functions. These can be executed locally with the CLI command, which requires initializing a project with an `appwrite.config.json` file and having local code to run the function locally. The CLI also supports various other [CLI commands](/docs/tooling/command-line/commands). 1. Install the [Docker CLI](https://www.docker.com/products/docker-desktop/) 2. Ensure Docker is running in the background 3. Install the [Appwrite CLI](/docs/tooling/command-line/installation#getting-started) 4. [Log in](/docs/tooling/command-line/installation#login) to your Appwrite account using `appwrite login` 5. [Initialize your project](/docs/tooling/command-line/installation#initialization) 6. [Initialize an Appwrite function](/docs/tooling/command-line/functions) and copy and paste your code ### Develop {% #develop %} Use the `appwrite run functions` command to develop your function locally. | Parameter | Description | |---------------------|----------------------------------------------------------------------------------------------------------------------------------------------| |`--port` | Set your function port; it defaults to `3000`, or the closest available, i.e. `3001`, `3002`, etc. | |`--function-id` | Select a function so you don't have to click through the list each time. | |`--user-id <user-id>`| Impersonates a user. Automatically sets `x-appwrite-user-id` and `x-appwrite-user-jwt` headers if the user exists. | |`--with-variables` | Set production environment variables for your function. Do this only if your functions don't have production secrets to avoid security risks.| |`--no-reload` | Set your functions to not hot reload. Any changes to your code won't cause your function to restart. | ```sh appwrite run functions --port 3000 --function-id "<FUNCTION_ID>" runtime | entrypoint | path | commands -----------|-------------|--------------------------------|-------------- node-16.0 | src/main.js | functions/<FUNCTION_ID> | npm install ℹ Info: If you wish to change your local settings, update the appwrite.config.json file and rerun the 'appwrite run' command. ♥ Hint: Permissions, events, CRON and timeouts dont apply when running locally. ℹ Info: Pulling Docker image ... ♥ Hint: This may take a few minutes, but we only need to do this once. ℹ Info: Building function using Docker ... Preparing for build ... Building ... added 4 packages, and audited 5 packages in 2s 1 package is looking for funding run `npm fund` for details found 0 vulnerabilities Packing build ... Build finished. ℹ Info: Starting function using Docker ... ♥ Hint: Function automatically restarts when you edit your code. ✓ Success: Visit http://localhost:3000/ to execute your function. ``` This command helps you efficiently develop your Appwrite functions on your local machine. When developing your Appwrite function locally, it will receive [headers](/docs/products/functions/develop#headers) like a function deployed to Appwrite. {% arrow_link href="/docs/products/functions/develop" %} Learn more about developing a function {% /arrow_link %} ### Dynamic API keys {% #dynamic-api-keys %} You can use headers like dynamic API keys in your function, which give you access to your project services and allow you to operate without sessions. To configure your dynamic API key scopes, modify the scopes in the `appwrite.config.json` file. {% arrow_link href="/docs/products/functions/develop#dynamic-api-key" %} Learn more about dynamic API keys {% /arrow_link %} ### Hot reload {% #hot-reload %} By default, the Appwrite CLI hot-reloads your functions, which means you can update the function code and the changes will be applied automatically. How this happens differs between runtimes with compiled languages versus interpreted ones. Because runtimes with compiled languages must translate the source code into machine code, the function must rebuild on change. When the source code in a runtime with an interpreted languages is updated, the function only needs to restart with the updated file. However, if a dependency file for the interpreted language is updated, the function must rebuild to update the dependencies. Refer to the table below for the dependency files of each language. {% table %} *   {% width=80 %} * Language * Dependency File --- * {% only_dark %}{% icon_image src="/images/platforms/dark/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_light %} * Node.js * package.json, package-lock.json --- * {% only_dark %}{% icon_image src="/images/platforms/dark/php.svg" alt="PHP logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/php.svg" alt="PHP logo" size="m" /%}{% /only_light %} * PHP * composer.json, composer.lock --- * {% only_dark %}{% icon_image src="/images/platforms/dark/python.svg" alt="Python logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/python.svg" alt="Python logo" size="m" /%}{% /only_light %} * Python * requirements.txt, requirements.lock --- * {% only_dark %}{% icon_image src="/images/platforms/dark/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_light %} * Ruby * Gemfile, Gemfile.lock --- * {% only_dark %}{% icon_image src="/images/platforms/dark/deno.svg" alt="Deno logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/deno.svg" alt="Deno logo" size="m" /%}{% /only_light %} * Deno * Import URLs --- * {% only_dark %}{% icon_image src="/images/platforms/dark/go.svg" alt="Go logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/go.svg" alt="Go logo" size="m" /%}{% /only_light %} * Go * go.mod --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dart.svg" alt="Dart logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dart.svg" alt="Dart logo" size="m" /%}{% /only_light %} * Dart * Pubspec.yaml --- * {% only_dark %}{% icon_image src="/images/platforms/dark/swift.svg" alt="Swift logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/swift.svg" alt="Swift logo" size="m" /%}{% /only_light %} * Swift * Package.swift (Swift Package Manager Files) --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_light %} * .NET * .nupkg (NuGet Packages) --- * {% only_dark %}{% icon_image src="/images/platforms/dark/bun.svg" alt="Bun logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/bun.svg" alt="Bun logo" size="m" /%}{% /only_light %} * Bun * package.json, package-lock.json, bun.lockb --- * {% only_dark %}{% icon_image src="/images/platforms/dark/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_light %} * Kotlin * JAR Files (Java ARchive) --- * {% only_dark %}{% icon_image src="/images/platforms/dark/java.svg" alt="Java logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/java.svg" alt="Java logo" size="m" /%}{% /only_light %} * Java * JAR Files (Java ARchive) --- * {% only_dark %}{% icon_image src="/images/platforms/dark/c.svg" alt="C++ logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/c.svg" alt="C++ logo" size="m" /%}{% /only_light %} * C++ * .h (Header Files) {% /table %} ### Impersonate user {% #impersonate-user %} You can also impersonate a user when you develop a function locally. Impersonate a user using the `--user-id <USER_ID>` option to select a user you want to use for testing. This allows you to test if the user can perform specific actions, such as creating a row. When using the `--user-id <USER_ID>` endpoint, the CLI will check and return an error if the user does not exist. But if a user does exist, a [JWT token](/docs/products/auth/jwt#jwt) will be generated and last for 1 hour, similar to API tokens. If the user exists, the header `x-appwrite-user-id` will be set with the userId value, and the `x-appwrite-user-jwt` header will be set with the generated JWT token value. ```sh appwrite run functions --user-id "<USER_ID>" ``` ### Push function {% #push-function %} Once you've developed your function, push it by running the following CLI command ```sh appwrite push functions ``` --- ## Domains https://appwrite.io/docs/products/functions/domains Each deployed function can have its own domain, generated or developer defined. You can use this domain to execute Appwrite Functions through HTTP methods. You can use common practices like using paths, query parameters, headers, HTTP methods, formdata, and all the typical HTTP concepts to implement Appwrite Functions. Appwrite generates TLS certificates to enforce HTTPS on all Appwrite Functions domains, generated or custom. These domains are safe to use and access in production. {% arrow_link href="/docs/products/functions/develop" %} Learn about Function development {% /arrow_link %} ### Generated domains {% #generated-domains %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Under the **Domains** tab, you'll find the generated domain from Appwrite. The domain usually has this format. ```bash https://64d4d22db370ae41a32e.appwrite.global ``` ### Add a custom domain {% #add-a-custom-domain %} 1. Navigate to the Appwrite Console's **Functions** page. 2. Navigate to the **Domains** tab. 3. Click on **Create domain**. 4. Input your domain and click **Next**. 5. Copy the **CNAME** record and add it to your domain registrar. 6. Click **Go to console** and wait for verification and certificate generation. DNS records can take up to 48 hours to propagate. When both **VERIFICATION STATUS** and **CERTIFICATE STATUS** are green, the new domain is ready to use. --- ## Examples https://appwrite.io/docs/products/functions/examples Appwrite Functions is all about flexibility. Behind the simple workflow hides some useful examples that can help you accomplish your goals faster. Take a look at the following. {% section #currency-conversion step=1 title="Currency conversion API" %} Here's a currency conversion API that converts from Euros and Indian Rupees to US Dollars. We'll use an external API to get the latest exchange rates and query it using a dependency specific to each runtime. #### Prerequisites {% tabs %} {% tabsitem #node title="Node.js" %} Run the following bash command to create a `package.json` file. This file is used to manage your Node.js project's dependencies. ```bash npm init -y ``` Install the `undici` library. This library includes a `fetch` function that you can use to make HTTP requests. ```bash npm install undici ``` Finally, add `npm install` to your function's build commands in the Appwrite Console. {% /tabsitem %} {% tabsitem #php title="PHP" %} Run the following bash command to create a `composer.json` file. This file is used to manage your PHP project's dependencies. ```bash composer init -y ``` Install the `guzzlehttp/guzzle` library. This library includes a `get` function that you can use to make HTTP requests. ```bash composer require guzzlehttp/guzzle ``` Finally, add `composer install` to your function's build commands in the Appwrite Console. {% /tabsitem %} {% tabsitem #python title="Python" %} Run the following bash command to create a `requirements.txt` file. This file is used to manage your Python project's dependencies. ```bash touch requirements.txt ``` Install the `requests` library. This library includes a `get` function that you can use to make HTTP requests. ```bash echo "requests" >> requirements.txt pip install -r requirements.txt ``` Finally, add `pip install -r requirements.txt` to your function's build commands in the Appwrite Console. {% /tabsitem %} {% tabsitem #dart title="Dart" %} Create a `pubspec.yaml` file with the following contents. This file is used to manage your Dart project's dependencies. ```yaml name: appwrite_function description: Appwrite Function version: 1.0.0 environment: sdk: '>=2.12.0 <3.0.0' ``` Install the `http` library. This library includes a `get` function that you can use to make HTTP requests. ```bash pub install http ``` Finally, add `pub get` to your function's build commands in the Appwrite Console. {% /tabsitem %} {% tabsitem #ruby title="Ruby" %} Create a `Gemfile` file with the following contents. This file is used to manage your Ruby project's dependencies. ```ruby source 'https://rubygems.org' ``` Install the `httparty` library. This library includes a `get` function that you can use to make HTTP requests. ```bash echo "gem 'httparty'" >> Gemfile bundle install ``` Finally, add `bundle install` to your function's build commands in the Appwrite Console. {% /tabsitem %} {% /tabs %} #### Code {% multicode %} ```server-nodejs import { fetch } from 'undici'; export default async function ({ req, res }) { if (req.path === '/eur') { const amountInEuros = Number(req.query.amount); const response = await fetch('https://api.exchangerate.host/latest?base=EUR&symbols=USD'); const data = await response.json(); const amountInDollars = amountInEuros * data.rates.USD; return res.text(amountInDollars.toString()); } if (req.path === '/inr') { const amountInRupees = Number(req.query.amount); const response = await fetch('https://api.exchangerate.host/latest?base=INR&symbols=USD'); const data = await response.json(); const amountInDollars = amountInRupees * data.rates.USD; return res.text(amountInDollars.toString()); } return res.text('Invalid path'); }; ``` ```php <?php require(__DIR__ . '/../vendor/autoload.php'); use Appwrite\Client; use Appwrite\Exception; use Appwrite\Services\Database; use GuzzleHttp\Client as GuzzleClient; return function ($context) { $client = new GuzzleClient(); if ($context->req->path === '/eur') { $amountInEuros = floatval($context->req->query['amount']); $response = $client->get('https://api.exchangerate.host/latest?base=EUR&symbols=USD'); $data = $response->json(); $amountInDollars = $amountInEuros * $data['rates']['USD']; return $context->res->text(strval($amountInDollars)); } if ($context->req->path === '/inr') { $amountInRupees = floatval($context->req->query['amount']); $response = $client->get('https://api.exchangerate.host/latest?base=INR&symbols=USD'); $data = $response->json(); $amountInDollars = $amountInRupees * $data['rates']['USD']; return $context->res->text(strval($amountInDollars)); } return $context->res->text('Invalid path'); }; ``` ```python import requests def main(context): if context.req.path == '/eur': amount_in_euros = float(context.req.query['amount']) response = requests.get('https://api.exchangerate.host/latest?base=EUR&symbols=USD') data = response.json() amount_in_dollars = amount_in_euros * data['rates']['USD'] return context.res.text(str(amount_in_dollars)) if context.req.path == '/inr': amount_in_rupees = float(context.req.query['amount']) response = requests.get('https://api.exchangerate.host/latest?base=INR&symbols=USD') data = response.json() amount_in_dollars = amount_in_rupees * data['rates']['USD'] return context.res.text(str(amount_in_dollars)) return 'Invalid path' ``` ```dart import 'dart:async'; import 'package:http/http.dart' as http; import 'dart:io'; Future<dynamic> main(final context) async { if (context.req.path == '/eur') { final amountInEuros = double.parse(context.req.query['amount']) final response = await http.get(Uri.parse('https://api.exchangerate.host/latest?base=EUR&symbols=USD')); final data = json.decode(response.body); final amountInDollars = amountInEuros * data['rates']['USD']; return context.res.text(amountInDollars.toString()); } if (context.req.path == '/inr') { final amountInRupees = double.parse(context.req.query['amount']) final response = await http.get(Uri.parse('https://api.exchangerate.host/latest?base=INR&symbols=USD')); final data = json.decode(response.body); final amountInDollars = amountInRupees * data['rates']['USD']; return context.res.text(amountInDollars.toString()); } return 'Invalid path'; } ``` ```ruby require 'httparty' def main(context) if context.req.path == '/eur' amount_in_euros = context.req.query['amount'].to_f response = HTTParty.get('https://api.exchangerate.host/latest?base=EUR&symbols=USD') data = JSON.parse(response.body) amount_in_dollars = amount_in_euros * data['rates']['USD'] return context.res.text(amount_in_dollars.to_s) end if context.req.path == '/inr' amount_in_rupees = context.req.query['amount'].to_f response = HTTParty.get('https://api.exchangerate.host/latest?base=INR&symbols=USD') data = JSON.parse(response.body) amount_in_dollars = amount_in_rupees * data['rates']['USD'] return context.res.text(amount_in_dollars.to_s) end return 'Invalid path' end ``` {% /multicode %} {% /section %} {% section #voting-system step=2 title="Voting system" %} Here's a simple voting system that allows users to vote on various topics. Appwrite Functions and the server SDK are used to enforce voting rules and prevent multiple votes from the same user for a single topic. #### Prerequisites Create a Topics table with the following columns: | Name | Type | Description | |---------------|--------|----------------------------------| | `title` | string | The name of the topic | | `description` | string | Long form description of the topic| Create a Votes table with the following columns: | Name | Type | Description | |---------------|--------|------------------------------------------| | `userId` | string | The ID of the user who cast the vote | | `topicId` | string | The ID of the topic that was voted on | | `vote` | string | The vote cast by the user. Must be either "yes" or "no" | #### Code {% multicode %} ```server-nodejs import { Client, TablesDB, Query, ID } from 'node-appwrite'; export default async function ({ req, res }) { const vote = { userId: req.query.userId, topicId: req.query.topicId, vote: req.query.vote }; if (vote.vote !== 'yes' && vote.vote !== 'no') { return res.json({ ok: false, message: 'You must vote yes or no.' }, 400); } // Set project and set API key const client = new Client(); client .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(req.headers['x-appwrite-key']); const tablesDB = new TablesDB(client); const existingVotes = await tablesDB.listRows({ databaseId: '<DATABASE_ID>', tableId: '<VOTES_TABLE_ID>', queries: [ Query.equals('userId', vote.userId), Query.equals('topicId', vote.topicId) ] }); if (existingVotes.total > 0) { return res.json({ ok: false, message: 'You have already voted on this topic.' }, 400); } const voteDocument = await tablesDB.createRow({ databaseId: '<DATABASE_ID>', tableId: '<VOTES_TABLE_ID>', rowId: ID.unique(), data: { userId: vote.userId, topicId: vote.topicId, vote: vote.vote, } }); return res.json({ ok: true, message: 'Vote cast.', vote: voteDocument }); }; ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.query import Query import os def main(context): vote = { 'userId': context.req.query['userId'], 'topicId': context.req.query['topicId'], 'vote': context.req.query['vote'] } if vote['vote'] != 'yes' and vote['vote'] != 'no': return context.res.json({'ok': False, 'message': 'You must vote yes or no.'}, 400) # Set project and set API key client = ( Client() .set_project(os.environ['APPWRITE_FUNCTION_PROJECT_ID']) .set_key(context.req.headers['x-appwrite-key']) ) tablesDB = TablesDB(client) existing_votes = tablesDB.list_rows('<VOTES_TABLE_ID>', [ Query.equals('userId', vote['userId']), Query.equals('topicId', vote['topicId']) ]) if existing_votes['total'] > 0: return context.res.json({ 'ok': False, 'message': 'You have already voted on this topic.' }, 400) vote_row = database.create_row('<VOTES_TABLE_ID>', { 'userId': vote['userId'], 'topicId': vote['topicId'], 'vote': vote['vote'], }) return context.res.json({'ok': True, 'message': 'Vote cast.', 'vote': vote_row}) ``` ```php <?php require(__DIR__ . '/../vendor/autoload.php'); use Appwrite\Client; use Appwrite\Exception; use Appwrite\Services\Database; use Appwrite\Query; return function ($context) { $vote = [ 'userId' => $context->req->query['userId'], 'topicId' => $context->req->query['topicId'], 'vote' => $context->req->query['vote'] ]; if ($vote['vote'] !== 'yes' && $vote['vote'] !== 'no') { return $context->res->json(['ok' => false, 'message' => 'You must vote yes or no.'], 400); } // Set project and set API key $client = new Client(); $client ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID')) ->setKey($context->req->headers['x-appwrite-key']); $tablesDB = new TablesDB($client); $existingVotes = $database->listRows('<VOTES_TABLE_ID>', [ Query->equal('userId', $vote['userId']), Query->equal('topicId', $vote['topicId']) ]); if ($existingVotes['total'] > 0) { return $context->res->json([ 'ok' => false, 'message' => 'You have already voted on this topic.' ], 400); } $voteDocument = $database->createRow('<VOTES_TABLE_ID>', [ 'userId' => $vote['userId'], 'topicId' => $vote['topicId'], 'vote' => $vote['vote'], ]); return $context->res->json([ 'ok' => true, 'message' => 'Vote cast.', 'vote' => $voteDocument ]); }; ``` ```dart import 'dart:async'; import 'package:dart_appwrite/dart_appwrite.dart'; import 'dart:io'; Future main(final context) async { final vote = { 'userId': context.req.query['userId'], 'topicId': context.req.query['topicId'], 'vote': context.req.query['vote'] }; if (vote['vote'] != 'yes' && vote['vote'] != 'no') { return context.res.json({'ok': false, 'message': 'You must vote yes or no.'}, 400); } // Set project and set API key final client = Client() .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID']) .setKey(context.req.headers['x-appwrite-key']); final tablesDB = TablesDB(client); final existingVotes = await tablesDB.listRows('<VOTES_TABLE_ID>', [ Query.equals('userId', vote['userId']), Query.equals('topicId', vote['topicId']) ]); if (existingVotes['total'] > 0) { return context.res.json({ 'ok': false, 'message': 'You have already voted on this topic.' }, 400); } final voteDocument = await database.createRow('<VOTES_TABLE_ID>', { 'userId': vote['userId'], 'topicId': vote['topicId'], 'vote': vote['vote'], }); return context.res.json({ 'ok': true, 'message': 'Vote cast.', 'vote': voteDocument }); } ``` ```ruby require "appwrite" def main(context) vote = { 'userId' => context.req.query['userId'], 'topicId' => context.req.query['topicId'], 'vote' => context.req.query['vote'] } if vote['vote'] != 'yes' and vote['vote'] != 'no' return context.res.json({'ok': false, 'message': 'You must vote yes or no.'}, 400) end # Set project and set API key client = Appwrite::Client.new() client .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID']) .set_key(context.req.headers['x-appwrite-key']) tablesDB = Appwrite::TablesDB.new(client) existing_votes = tablesDB.list_rows('<DATABASE_ID>', '<VOTES_TABLE_ID>', [ Appwrite::Query.equal('userId', vote['userId']), Appwrite::Query.equal('topicId', vote['topicId']) ]) if existing_votes['total'] > 0 return context.res.json({ 'ok': false, 'message': 'You have already voted on this topic.' }, 400) end vote_row = database.create_row('<VOTES_TABLE_ID>', { 'userId': vote['userId'], 'topicId': vote['topicId'], 'vote': vote['vote'], }) return context.res.json({ 'ok': true, 'message': 'Vote cast.', 'vote': vote_row }) end ``` {% /multicode %} Use the function by navigating to the function URL in the browser. The URL should contain the required parameters. For example, `<YOUR_FUNCTION_URL>/?userId=<USER_ID>&topicId=<TOPIC_ID>&vote=yes` to cast a vote. {% /section %} {% section #form-submission step=3 title="HTML contact form" %} Here's a simple form page that handles form submissions, and can be used to store a user's message in a table. The form is submitted to the function using the `POST` method and the form data is sent as a URL-encoded string in the request body. #### Prerequisites Create a Messages table with the following columns: | Name | Type | Description | |------------|--------|----------------------------------| | `name` | string | The name of the message author | | `email` | string | The email of the message author | | `content` | string | The content of the message | #### Code {% multicode %} ```server-nodejs import { Client, TablesDB, Query, ID } from 'node-appwrite'; import querystring from 'node:querystring'; const html = `<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Contact Form
` export default async function ({ req, res }) { if (req.method === 'GET') { return res.text(html, 200, {'content-type': 'text/html'}); } if (req.method === 'POST' && req.headers['content-type'] === 'application/x-www-form-urlencoded') { const formData = querystring.parse(req.body); const message = { name: formData.name, email: formData.email, content: formData.content }; // Set project and set API key const client = new Client() .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(req.headers['x-appwrite-key']); const tablesDB = new TablesDB(client); const row = await tablesDB.createRow({ databaseId: '', tableId: '', rowId: ID.unique(), data: message }); return res.text("Message sent"); } return res.text('Not found', 404); } ``` ```python from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.query import Query from urllib.parse import parse_qs import os html = ''' Contact Form
''' def main(context): if context.req.method == 'GET': return context.res.text(html, 200, {'content-type': 'text/html'}) if context.req.method == 'POST' and context.req.headers['content-type'] == 'application/x-www-form-urlencoded': formData = parse_qs(context.req.body) message = { 'name': formData['name'][0], 'email': formData['email'][0], 'content': formData['content'][0] } # Set project and set API key client = ( Client() .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) .set_key(context.req.headers["x-appwrite-key"]) ) tablesDB = TablesDB(client) row = tablesDB.create_row('', '', ID.unique(), message) return context.res.text("Message sent") return context.res.text('Not found', 404) ``` ```php Contact Form
'; return function ($context) { global $html; if ($context->req->method === 'GET') { return $context->res->text($html, 200, ['content-type' => 'text/html']); } if ($context->req->method === 'POST' && $context->req->headers['content-type'] === 'application/x-www-form-urlencoded') { \parse_str($context->req->body, $formData); $message = [ 'name' => $formData['name'], 'email' => $formData['email'], 'content' => $formData['content'] ]; // Set project and set API key $client = (new Client()) ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID')) ->setKey($context->req->headers['x-appwrite-key']); $tablesDB = new TablesDB($client); $row = $tablesDB->createRow('', '', ID::unique(), $message); return $context->res->text("Message sent"); } return $context->res->text('Not found', 404); }; ``` ```ruby require "appwrite" html = ''' Contact Form
''' def main(context) if context.req.method == 'GET' return context.res.text(html, 200, {'content-type': 'text/html'}) end if context.req.method == 'POST' and context.req.headers['content-type'] == 'application/x-www-form-urlencoded' formData = URI.decode_www_form(context.req.body).to_h message = { 'name' => formData['name'], 'email' => formData['email'], 'content' => formData['content'] } # Set project and set API key client = Appwrite::Client.new .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID']) .set_key(context.req.headers['x-appwrite-key']) tablesDB = Appwrite::TablesDB.new(client) row = tablesDB.create_row('', '', ID.unique(), message) return context.res.text("Message sent") end return context.res.text('Not found', 404) end ``` ```dart import 'dart:async'; import 'package:dart_appwrite/dart_appwrite.dart'; Future main(final context) async { final html = ''' Contact Form
'''; if (context.req.method == 'GET') { return context.res.text(html, 200, {'content-type': 'text/html'}); } if (context.req.method == 'POST' && context.req.headers['content-type'] == 'application/x-www-form-urlencoded') { final formData = Uri.splitQueryString(context.req.body); final message = { 'name': formData['name'], 'email': formData['email'], 'content': formData['content'] }; // Set project and set API key final client = Client() .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID']) .setKey(context.req.headers['x-appwrite-key']); final tablesDB = TablesDB(client); final row = await tablesDB.createRow('', '', ID.unique(), message); return context.res.text("Message sent"); } return context.res.text('Not found', 404); } ``` {% /multicode %} Use the function by navigating to the function URL in the browser. Submit the form to store the message in the table. {% /section %} --- ## Execution https://appwrite.io/docs/products/functions/execute Appwrite Functions can be executed in several ways. Executions can be invoked through the Appwrite SDK and visiting its REST endpoint. Functions can also be triggered by events and scheduled executions. Here are all the different ways to consume your Appwrite Functions. ### Domains {% #domains %} You can execute a function through HTTP requests, using a browser or by sending an HTTP request. 1. In the Appwrite Console's sidebar, click **Functions**. 1. Under **Execute access**, set the access to `Any` so that anyone can execute the function. You will use [JWTs](/docs/products/auth/jwt) to authenticate users. 1. Under the **Domains** tab, you'll find the generated domain from Appwrite and your custom domains. [Learn about adding a custom domain](/docs/products/functions/domains). ```bash https://64d4d22db370ae41a32e.appwrite.global ``` When requests are made to this domain, whether through a browser or through an HTTP requests, the request information like request URL, request headers, and request body will be passed to the function. ```bash curl -X POST https://64d4d22db370ae41a32e.appwrite.global \ -H "X-Custom-Header: 123" \ -H "x-appwrite-user-jwt: " \ -H "Content-Type: application/json" \ -d '{"data":"this is json data"}' ``` Notice how a `x-appwrite-user-jwt` header is passed in the request, you will use this to authenticate users. [Learn more about JWTs](/docs/products/auth/jwt). This unlocks ability for you to develop custom HTTP endpoints with Appwrite Functions. It also allows accepting incoming webhooks for handling online payments, hosting social platform bots, and much more. ### SDK {% #sdk %} You can invoke your Appwrite Functions directly from the [Appwrite SDKs](/docs/sdks). {% tabs %} {% tabsitem #client title="Client SDKs" %} {% multicode %} ```client-web import { Client, Functions } from "appwrite"; const client = new Client(); const functions = new Functions(client); client .setProject('') // Your project ID ; const promise = functions.createExecution({ functionId: '', body: '', // optional async: false, // optional xpath: '', // optional method: 'GET', // optional headers: {} // optional }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```dart import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Functions functions = Functions(client); client .setProject('') // Your project ID ; Future result = functions.createExecution( functionId: '', body: '', // optional xasync: false, // optional path: '', // optional method: 'GET', // optional headers: {}, // optional ); result .then((response) { print(response); // Success }).catchError((error) { print(error.response); // Failure }); } ``` ```swift import Appwrite let client = Client() .setProject("") // Your project ID let functions = Functions(client) let execution = try await functions.createExecution( functionId: "", body: "", // optional async: xfalse, // optional path: "", // optional method: "GET", // optional headers: [:] // optional ) ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Functions val client = Client(context) .setProject("") // Your project ID val functions = Functions(client) val response = functions.createExecution( functionId = "", body = "", // optional async = false, // optional path = "", // optional method = "GET", // optional headers = mapOf( "a" to "b" ) // optional ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; Client client = new Client(context) .setProject(""); // Your project ID Functions functions = new Functions(client); functions.createExecution( "", // functionId "", // body (optional) false, // async (optional) "", // path (optional) "GET", // method (optional) mapOf( "a" to "b" ) // headers (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } Log.d("Appwrite", result.toString()); }) ); ``` {% /multicode %} {% /tabsitem %} {% tabsitem #server title="Server SDKs" %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const functions = new sdk.Functions(client); client .setProject('') // Your project ID ; const promise = functions.createExecution({ functionId: '', body: '', // optional async: false, // optional path: '', // optional method: 'GET', // optional headers: {} // optional }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let functions = new sdk.Functions(client); client .setProject('') // Your project ID ; const promise = functions.createExecution({ functionId: '', body: '', // optional async: false, // optional xpath: '', // optional method: 'GET', // optional headers: {} // optional }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/appwrite" ) func main() { client := appwrite.NewClient( appwrite.WithProject(""), ) functions := appwrite.NewFunctions(client) execution, err := functions.CreateExecution( "", // functionId functions.WithCreateExecutionBody(""), // body (optional) functions.WithCreateExecutionAsync(false), // async (optional) functions.WithCreateExecutionPath(""), // path (optional) functions.WithCreateExecutionMethod("GET"), // method (optional) functions.WithCreateExecutionHeaders(map[string]interface{}{})) // headers (optional) fmt.Println(execution) if err != nil { fmt.Println(err) } } ``` ```php setProject('') // Your project ID ; $functions = new Functions($client); $result = $functions->createExecution( functionId: '', body: '', // optional async: false, // optional path: '', // optional method: 'GET', // optional headers: [] // optional ); ``` ```python from appwrite.client import Client from appwrite.services.functions import Functions client = Client() (client .set_project('') # Your project ID ) functions = Functions(client) result = functions.create_execution( function_id = '', body = '', # optional async = False, # optional path = '', # optional method = 'GET', # optional headers = {} # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_project('') # Your project ID functions = Functions.new(client) response = functions.create_execution( function_id: '', body: '', # optional async: false, # optional path: '', # optional method: 'GET', # optional headers: {} # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetProject(""); // Your project ID var functions = new Functions(client); Execution result = await functions.CreateExecution( functionId: "" body: "" // optional async: false // optional path: "" // optional method: "GET" // optional headers: [object]); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Functions functions = Functions(client); client .setProject('') // Your project ID ; Future result = functions.createExecution( functionId: '', body: '', // optional xasync: false, // optional path: '', // optional method: 'GET', // optional headers: {}, // optional ); result .then((response) { print(response); // Success }).catchError((error) { print(error.response); // Failure }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; Client client = new Client() .setProject(""); // Your project ID Functions functions = new Functions(client); functions.createExecution( "", // functionId "", // body (optional) false, // async (optional) "", // path (optional) "GET", // method (optional) mapOf( "a" to "b" ) // headers (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; Client client = new Client() .setProject(""); // Your project ID Functions functions = new Functions(client); functions.createExecution( "", // functionId "", // body (optional) false, // async (optional) "", // path (optional) "GET", // method (optional) mapOf( "a" to "b" ) // headers (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setProject("") // Your project ID let functions = Functions(client) let execution = try await functions.createExecution( functionId: "", body: "", // optional async: xfalse, // optional path: "", // optional method: "GET", // optional headers: [:] // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Console {% #console %} Another easy way to test a function is directly in the Appwrite Console. You test a function by hitting the **Execute now** button, which will display with modal below. You'll be able to mock executions by configuring the path, method, headers, and body. {% only_dark %} ![Create project screen](/images/docs/functions/execution/dark/execute-function.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/functions/execution/execute-function.png) {% /only_light %} ### Events {% #events %} Changes in Appwrite emit events. You can configure Functions to be executed in response to these events. 1. In Appwrite Console, navigate to **Functions**. 2. Click to open a function you wish to configure. 3. Under the **Settings** tab, navigate to **Events**. 4. Add one or multiple events as triggers for the function. 5. Be careful to avoid selecting events that can be caused by the function itself. This can cause the function to trigger its own execution, resulting in infinite recursions. In these executions, the event that triggered the function will be passed as the header `x-appwrite-event` to the function. The `request.body` parameter will contain the event data. [Learn more about events](/docs/advanced/platform/events). You can use one of the following events. {% accordion %} {% accordion_item title="Authentication" %} {% partial file="auth-events.md" /%} {% /accordion_item %} {% accordion_item title="Databases" %} {% partial file="databases-events.md" /%} {% /accordion_item %} {% accordion_item title="Storage" %} {% partial file="storage-events.md" /%} {% /accordion_item %} {% accordion_item title="Functions" %} {% partial file="functions-events.md" /%} {% /accordion_item %} {% accordion_item title="Messaging" %} {% partial file="messaging-events.md" /%} {% /accordion_item %} {% /accordion %} ### Schedule {% #schedule %} Appwrite supports scheduled function executions. You can schedule executions using [cron expressions](https://en.wikipedia.org/wiki/Cron) in the settings of your function. Cron supports recurring executions as frequently as **every minute**. Here are some cron expressions for common intervals: | Cron Expression | Schedule | | ---------------- | --------------------- | | `*/15 * * * *` | Every 15 minutes | | `0 * * * *` | Every Hour | | `0 0 * * *` | Every day at 00:00 | | `0 0 * * 1` | Every Monday at 00:00 | ### Delayed executions {% #delayed-executions %} You can also delay function executions, which trigger the function only once at a future date and time. You can schedule a function execution using the Appwrite Console, a Client SDK, or a Server SDK. {% tabs %} {% tabsitem #console title="Console" %} To schedule an execution, navigate to **Your function** > **Executions** > **Execute now** > **Schedule** in the Appwrite Console. {% only_dark %} ![Scheduled execution details screen](/images/docs/functions/execution/dark/scheduled-execution-function.png) {% /only_dark %} {% only_light %} ![Scheduled execution details screen](/images/docs/functions/execution/scheduled-execution-function.png) {% /only_light %} {% /tabsitem %} {% tabsitem #client-sdk title="Client SDK" %} You can also schedule your function executions using a supported [Client SDK](/docs/sdks/#client). {% multicode %} ```client-web import { Client, Functions, ExecutionMethod } from "appwrite"; const client = new Client() .setProject(''); // Your project ID const functions = new Functions(client); const result = await functions.createExecution({ functionId: '', body: '', // optional async: true, // Scheduled executions need to be async xpath: '', // optional method: ExecutionMethod.GET, // optional headers: {}, // optional scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) }); console.log(result); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; Client client = Client() .setProject(''); // Your project ID Functions functions = Functions(client); Execution result = await functions.createExecution( functionId: '', // functionId body: '', // optional xasync: true, // Scheduled executions need to be async path: '', // optional method: ExecutionMethod.gET, // optional headers: {}, // optional scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) ); ``` ```client-react-native import { Client, Functions, ExecutionMethod } from "react-native-appwrite"; const client = new Client() .setProject(''); // Your project ID const functions = new Functions(client); const result = await functions.createExecution({ functionId: '', body: '', // optional async: true, // Scheduled executions need to be async xpath: '', // optional method: ExecutionMethod.GET, // optional headers: {}, // optional scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) }); console.log(result); ``` ```client-apple import Appwrite import AppwriteEnums let client = Client() .setProject("") // Your project ID let functions = Functions(client) let execution = try await functions.createExecution( functionId: "", // functionId body: "", // optional async: true, // Scheduled executions need to be async path: "", // optional method: .gET, // optional headers: [:], // optional scheduledAt: "2020-10-15T06:38:00.000+00:00" // Schedule execution (optional) ) ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Functions val client = Client(context) .setProject("") // Your project ID val functions = Functions(client) val result = functions.createExecution( functionId = "", // functionId body = "", // (optional) async = true, // Scheduled executions need to be async path = "", // (optional) method = ExecutionMethod.GET, // (optional) headers = mapOf( "a" to "b" ), // (optional) scheduledAt = "2020-10-15T06:38:00.000+00:00" // Schedule execution (optional) ) ``` ```graphql mutation { functionsCreateExecution( functionId: "", body: "", async: true, path: "", method: "GET", headers: "{}", scheduledAt: "2020-10-15T06:38:00.000+00:00" ) { _id _createdAt _updatedAt _permissions functionId trigger status requestMethod requestPath requestHeaders { name value } responseStatusCode responseBody responseHeaders { name value } logs errors duration } } ``` ```http POST https://.cloud.appwrite.io/v1/functions//executions HTTP/1.1 X-Appwrite-Project: "" X-Appwrite-Response-Format: 1.5.0 Content-Type: application/json { "body": "", "async": true, "path": "", "method": "GET", "headers": {}, "scheduledAt": "2025-10-15T06:38:00.000+00:00" } ``` {% /multicode %} {% /tabsitem %} {% tabsitem #sdk title="Server SDK" %} You can also schedule your function executions using a supported [Server SDK](/docs/sdks/#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const functions = new sdk.Functions(client); client .setProject('') // Your project ID ; const promise = functions.createExecution( '', // functionId '', // body (optional) true, // Scheduled executions need to be async '', // path (optional) ExecutionMethod.GET, // method (optional) {}, // headers (optional) '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let functions = new sdk.Functions(client); client .setProject('') // Your project ID ; const promise = functions.createExecution( '', // functionId '', // body (optional) true, // Scheduled executions need to be async '', // path (optional) ExecutionMethod.GET, // method (optional) {}, // headers (optional) '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/appwrite" ) func main() { client := appwrite.NewClient( appwrite.WithProject(""), ) functions := appwrite.NewFunctions(client) execution, err := functions.CreateExecution( "", // functionId functions.WithCreateExecutionBody(""), // body (optional) functions.WithCreateExecutionAsync(true), // Scheduled executions need to be async functions.WithCreateExecutionPath(""), // path (optional) functions.WithCreateExecutionMethod("GET"), // method (optional) functions.WithCreateExecutionHeaders(map[string]interface{}{}), // headers (optional) functions.WithCreateExecutionScheduledAt("2025-10-15T06:38:00.000+00:00")) // Schedule execution (optional) fmt.Println(execution) if err != nil { fmt.Println(err) } } ``` ```php setProject('') // Your project ID ; $functions = new Functions($client); $result = $functions->createExecution( '', // functionId '', // body (optional) true, // Scheduled executions need to be async '', // path (optional) ExecutionMethod.GET, // method (optional) {}, // headers (optional) '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) ); ``` ```python from appwrite.client import Client from appwrite.services.functions import Functions client = Client() (client .set_project('') # Your project ID ) functions = Functions(client) result = functions.create_execution( function_id = '', # functionId body = '', # body (optional) async = True, # Scheduled executions need to be async path = '', # path (optional) method = ExecutionMethod.GET, # method (optional) headers = {} # headers (optional) scheduled_at = '2020-10-15T06:38:00.000+00:00' # Schedule execution (optional) ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_project('') # Your project ID functions = Functions.new(client) response = functions.create_execution( function_id: '', # functionId body: '', # body (optional) async: true, # Scheduled executions need to be async path: '', # path (optional) method: ExecutionMethod::GET, # method (optional) headers: {} # headers (optional) scheduled_at: '2020-10-15T06:38:00.000+00:00' # Schedule execution (optional) ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetProject(""); // Your project ID var functions = new Functions(client); Execution result = await functions.CreateExecution( functionId: "", // functionId body: "", // body (optional) async: true, // Scheduled executions need to be async path: "", // path (optional) method: ExecutionMethod.GET, // method (optional) headers: [object] // headers (optional) scheduledAt: "2020-10-15T06:38:00.000+00:00"; // Schedule execution (optional) ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Functions functions = Functions(client); client .setProject('') // Your project ID ; Future result = functions.createExecution( functionId: '', // functionId body: '', // (optional) xasync: true, // Scheduled executions need to be async path: '', // (optional) method: ExecutionMethod.GET, // (optional) headers: {}, // (optional) scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional) ); result .then((response) { print(response); // Success }).catchError((error) { print(error.response); // Failure }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; Client client = new Client() .setProject(""); // Your project ID Functions functions = new Functions(client); functions.createExecution( "", // functionId "", // body (optional) true, // Scheduled executions need to be async "", // path (optional) "GET", // method (optional) mapOf( "a" to "b" ), // headers (optional) "2020-10-15T06:38:00.000+00:00", // Schedule execution (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; Client client = new Client() .setProject(""); // Your project ID Functions functions = new Functions(client); functions.createExecution( "", // functionId "", // body (optional) true, // Scheduled executions need to be async "", // path (optional) ExecutionMethod.GET, // method (optional) mapOf( "a" to "b" ), // headers (optional) "2020-10-15T06:38:00.000+00:00" // Schedule execution (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setProject("") // Your project ID let functions = Functions(client) let execution = try await functions.createExecution( functionId: "", body: "", // optional async: true, // Scheduled executions need to be async path: "", // optional method: .gET, // optional headers: [:] // optional scheduledAt: "2020-10-15T06:38:00.000+00:00" // Schedule execution (optional) ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Permissions {% #permission %} Appwrite Functions can be executed using Client or Server SDKs. Client SDKs must be authenticated with an account that has been granted execution [permissions](/docs/advanced/platform/permissions) on the function's settings page. Server SDKs require an API key with the correct scopes. If your function has a generated or custom domain, executions are not authenticated. Anyone visiting the configured domains will be considered a guest, so make sure to give `Any` execute permission in order for domain executions to work. If you need to enforce permissions for functions with a domain, use authentication methods like JWT. --- ## Execution https://appwrite.io/docs/products/functions/executions Each time an Appwrite Function runs, an **execution** is created. Each execution has a unique ID. If [you enable execution logs](/docs/products/functions/functions#execution-logs) in your function, you can find function executions logged in the **Executions** tab. ### Execution table {% #execution-table %} In your function's **Executions** tab, you will see a table of your recent executions. Here's the information shown on this table. {% table %} - Column - Description --- - Execution ID - Unique identifier for each execution --- - Status - The current status of the execution --- - Created - Timestamp of when the execution was created --- - Trigger - The [platform event](/docs/advanced/platform/events) that triggered the execution --- - Method - The HTTP method used to create the execution --- - Path - The URL path the function execution was called with --- - Duration - The time taken for the execution {% /table %} #### Execution status {% #execution-status %} Each execution can have one of the follow status. {% table %} - Status - description --- - `scheduled` - The function execution will trigger later. --- - `waiting` - The execution is queued but has not been picked up for processing. --- - `processing` - The function execution has begun and has not finished. --- - `completed` - The function executed successfully. --- - `failed` - The function execution was not successful. {% /table %} ### Execution details {% #execution-details %} When you click on an execution, you will be taken to an execution detail screen. {% only_dark %} ![Execution details screen](/images/docs/functions/execution/dark/execute-function.png) {% /only_dark %} {% only_light %} ![Execution details screen](/images/docs/functions/execution/execute-function.png) {% /only_light %} You can find both request and response details. Request and response body are **not logged** to protect user privacy. This ensures that developers do not see user data by default and no sensitive data is retained. If you need to log debug data or audit logs, you can use [function logging features](/docs/products/functions/develop#logging) to explicitly log the information you need. ### Log retention {% #log-retention %} Logs are not retained forever in order to be compliant with GDPR and other data privacy standards. Free plan organizations will retain logs for 24 hours, Pro plan organizations will retain logs for 7 days. If you need longer log retention, you can log to an Appwrite table. Remember to configure proper permissions and implement Appwrite Functions or other scheduled tasks to expire and clean up logs. --- ## Functions https://appwrite.io/docs/products/functions/functions Each Appwrite Function is a piece of developer defined code that can be executed on demand. When you create a new Appwrite Function, you select a name, ID, and [runtime language](/docs/products/functions/runtimes). Each time a function's code is updated, a [deployment](/docs/products/functions/deployments) is created, which is like a version of a function. Each function has a single active deployment, which is the version of code that's executed when it's called. You can update the Appwrite Function's code by creating new [deployments](/docs/products/functions/deploy-from-git). You can also switch between different deployments by activating them. ### Create function {% #create-function %} You can create Appwrite Functions in three different ways. {% tabs %} {% tabsitem #git title="Git" %} It's recommended to create functions that are connected to version control. This lets you track your code using Git, which makes it easy to integrate Appwrite Functions into your existing code base. 1. In the Appwrite Console's sidebar, click **Functions**. 2. Click **Create function**. 3. Connect your project to your Git provider. You will be asked to authorize Appwrite and grant access to some resources necessary for the Git deployments to work. 4. If you already have a repository containing an Appwrite Function, select it under **Connect Git repository**. If you need to create a new function, select a **Quick start** template or search for more templates under **All templates**. 5. Follow the wizard to configure your new Appwrite Function. {% only_dark %} ![Create project screen](/images/docs/functions/dark/template.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/functions/template.png) {% /only_light %} {% /tabsitem %} {% tabsitem #manual title="Manual" %} You can also create Appwrite Functions manually by uploading your code in a zipped file. In the **Create Function** modal, click **create a function manually** at the bottom to switch to manual create wizard. You will be asked to upload a zip file with your code. First, navigate inside the folder that contains your dependency file. For example, when you list the content of your folder for a Node.js function, it will look like this. ```text . ├── package.json └── index.js ``` Package your code files into the `.tar.gz` format. **Don't include your dependencies folder**, such as `node_modules`. {% multicode %} ```bash tar --exclude code.tar.gz -czf code.tar.gz . ``` ```cmd tar --exclude code.tar.gz -czf code.tar.gz . ``` ```powershell tar --exclude code.tar.gz -czf code.tar.gz . ``` {% /multicode %} Upload your `.tar.gz` file and specify the entry point of your function, in this case `index.js`. Remember to specify the build commands for your function to install dependencies. {% /tabsitem %} {% tabsitem #cli title="CLI" %} {% partial file="cli-function.md" /%} {% /tabsitem %} {% /tabs %} ### Configuration {% #configuration %} #### Name {% #name %} You can update the name of your function by navigating to your function > **Settings** > **Name**. Update your function's name and click **Update**. Functions are executed using it's ID, updating name does not affect references to your function. #### Runtime {% #runtime %} Every deployment of a function uses the same runtime. You can update the runtime of a function by navigating to function > **Settings** > **Runtime**. Select a new runtime and click **Update**. {% info title="Redeployment required" %} This change requires your function to be redeployed to take effect. {% /info %} #### Build configuration {% #build-configuration %} You can update the entrypoint file and build settings of your function by navigating to your function > **Settings** > **Configuration**. {% info title="Redeployment required" %} This change requires your function to be redeployed to take effect. {% /info %} The **Entrypoint** refers to the file imported and executed by the function executor. It must export a [valid function entrypoint function](/docs/products/functions/develop#entrypoint). It's recommended you use one of the [starter function templates](/docs/products/functions/templates) and edit from there. Under **Build settings**, you can update your build commands. These are terminal commands that will be executed in the runtime containers in the build step of the deployment process. #### Git integration {% #git-integration %} You can update the entrypoint file and build settings of your function by navigating to your function > **Settings** > **Configuration**. {% info title="Redeployment required" %} This change requires your function to be redeployed to take effect. {% /info %} Under **Git settings** you can configure the Git repository and branch that your function is connected to. Build commands and entrypoint are executed relative to the configured **Root directory** of your Git respository. By default, Appwrite will create comments in PRs to your connected branch. You can use **Silent mode** to suppress these comments. #### Execution logs {% #execution-logs %} You can enable and disable execution logs for your functions by navigating to your function > **Settings** > **Execution logs** In production environments, you can choose to disable execution logs to protect user privacy. #### Execute access {% #execution-access %} You can control who can execute your functions by navigating to your function > **Settings** > **Execute access** and granting access to select [permission roles](/docs/advanced/platform/permissions#permission-roles). If this is left empty, no user can execute your function. Server SDKs, scheduled executions, and event function triggers don't require permissions to execute a function. #### Events{% #events %} Functions can be triggered by [platform events](/docs/advanced/platform/events) which reflect changes that occur in your Appwrite project. You can configure events triggers by navigating to your function > **Settings** > **Events**. #### Schedule {% #schedule %} Appwrite supports scheduled function executions. You can schedule executions using [cron expressions](https://en.wikipedia.org/wiki/Cron) in the settings of your function. Cron supports recurring executions as frequently as **every minute**. You can configure events triggers by navigating to your function > **Settings** > **Schedule**. Here are some cron expressions for common intervals: | Cron Expression | Schedule | | ---------------- | --------------------- | | `*/15 * * * *` | Every 15 minutes | | `0 * * * *` | Every Hour | | `0 0 * * *` | Every day at 00:00 | | `0 0 * * 1` | Every Monday at 00:00 | #### Environment variables {% #environment-variables %} Set static environment variables that will be passed to your function by navigating to your function > **Settings** > **Environment variables**. You can also configure global variables that apply to all your functions in the **Settings** of your project. {% info title="Redeployment required" %} This change requires your function to be redeployed to take effect. {% /info %} You can access environment variables inside functions using your [runtime language's system methods](/docs/products/functions/develop#environment-variables). #### Timeout {% #timeout %} You can limit the execution time of your function by navigating to your function > **Settings** > **Timeout**. There is a system wide maximum timeout of 900 seconds (15 minutes). #### Scopes {% #scopes %} You can configure the permission scopes for the function [dynamic API key](/docs/products/functions/develop#dynamic-api-key). The dynamic API key is automatically generated to access your project resources like users and buckets but can only be used inside of Appwrite functions. Navigate to your function > **Settings** > **Scopes** to configure your dynamic API key permission scopes. {% arrow_link href="/docs/advanced/platform/api-keys#scopes" %} Learn more about scopes {% /arrow_link %} --- ## Start with Functions https://appwrite.io/docs/products/functions/quick-start You can create and execute your first Appwrite Function in minutes. ### Create function {% #create-function %} Before deploying your function with Git, create a new function attached to your Git repository. {% only_dark %} ![Create function screen](/images/docs/functions/quick-start/dark/create-function.png) {% /only_dark %} {% only_light %} ![Create function screen](/images/docs/functions/quick-start/create-function.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 2. Click **Create function**. 3. Under **Connect Git repository**, select your provider. 4. After connecting to GitHub, under **Quick start**, select a starter template. 5. Follow the step-by-step wizard and create the function. 6. The function will be created and a build will begin. Once the build completes, you'll have created your first function. You can find the code used by the starter template in your newly created Git repository. Each push to your Git repo will trigger a new deployment. #### Execute {% #execute %} You can execute your Appwrite Function through [many different triggers](/docs/products/functions/execute). The easiest way to execute your first function is to use the Appwrite Console. {% only_dark %} ![Execution-screen](/images/docs/functions/quick-start/dark/function-execution.png) {% /only_dark %} {% only_light %} ![Execution-screen](/images/docs/functions/quick-start/function-execution.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Functions**. 1. Under the **Executions** tab, click **Execute now**. 1. Click **Execute** to execute the starter template function. 1. Wait for the execution to be marked **completed** and click to view the execution logs. ### Explore {% #explore %} Use this your first function as a springboard to explore the flexible and powerful features of Appwrite Functions. {% cards %} {% cards_item href="/docs/products/functions/templates" title="Template" %} Get a template function up and running with a single click. {% /cards_item %} {% cards_item href="/docs/products/functions/develop" title="Develop" %} Learn about developing your own Appwrite Function. {% /cards_item %} {% cards_item href="/docs/products/functions/deploy-from-git" title="Deploy" %} Learn to deploy Appwrite Functions from Git. {% /cards_item %} {% cards_item href="/docs/products/functions/execute" title="Execute" %} Explore the different ways an Appwrite Function can be executed. {% /cards_item %} {% cards_item href="/docs/products/functions/runtimes" title="Runtimes" %} Write Appwrite Functions in your favorite language. {% /cards_item %} {% /cards %} --- ## Runtimes https://appwrite.io/docs/products/functions/runtimes Appwrite Functions supports an extensive list of runtimes to meet your unique tech preferences. Not all runtimes are available on Appwrite Cloud yet. Check the list below to know which ones are available on Appwrite Cloud. ### Available runtimes {% #available-runtimes %} Below is a list of available Functions runtimes. The Appwrite team continually adds support for new runtimes. While still in beta, Appwrite Cloud has limited support for Cloud runtimes. As we continue to improve our Cloud offering, we will add support for more runtimes. {% tabs %} {% tabsitem #all title="All runtimes" %} {% table %} *   {% width=48 %} * Name {% width=100 %} * Versions * Architectures --- * {% icon icon="node_js" size="l" /%} * [Node.js](https://hub.docker.com/r/openruntimes/node/tags) * `node-14.5` `node-16.0` `node-18.0` `node-19.0` `node-20.0` `node-21.0` `node-22` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="bun-sh" size="l" /%} * [Bun](https://hub.docker.com/r/openruntimes/bun/tags) * `bun-1.0` `bun-1.1` * x86 / arm64 --- * {% icon icon="deno" size="l" /%} * [Deno](https://hub.docker.com/r/openruntimes/deno/tags) * `deno-1.21` `deno-1.24` `deno-1.35` `deno-1.40` `deno-1.46` `deno-2.0` * x86 --- * {% icon icon="go" size="l" /%} * [Go](https://hub.docker.com/r/openruntimes/go/tags) * `go-1.23` * x86 / arm64 --- * {% icon icon="python" size="l" /%} * [Python](https://hub.docker.com/r/openruntimes/python/tags) * `python-3.8` `python-3.9` `python-3.10` `python-3.11` `python-3.12` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="python" size="l" /%} * [Python ML](https://hub.docker.com/r/openruntimes/python-ml) * `python-ml-3.11` * x86 / arm64 --- * {% icon icon="dart" size="l" /%} * [Dart](https://hub.docker.com/r/openruntimes/dart/tags) * `dart-2.15` `dart-2.16` `dart-2.17` `dart-2.18` `dart-3.0` `dart-3.1` `dart-3.3` `dart-3.5` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="php" size="l" /%} * [PHP](https://hub.docker.com/r/openruntimes/php/tags) * `php-8.0` `php-8.1` `php-8.2` `php-8.3` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="ruby" size="l" /%} * [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags) * `ruby-3.0` `ruby-3.1` `ruby-3.2` `ruby-3.3` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="dotnet" size="l" /%} * [.NET](https://hub.docker.com/r/openruntimes/dotnet/tags) * `dotnet-6.0` `dotnet-7.0` `dotnet-8.0` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="java" size="l" /%} * [Java](https://hub.docker.com/r/openruntimes/java/tags) * `java-8.0` `java-11.0` `java-17.0` `java-18.0` `java-21.0` `java-22` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="swift" size="l" /%} * [Swift](https://hub.docker.com/r/openruntimes/swift/tags) * `swift-5.5` `swift-5.8` `swift-5.9` `swift-5.10` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="kotlin" size="l" /%} * [Kotlin](https://hub.docker.com/r/openruntimes/kotlin/tags) * `kotlin-1.6` `kotlin-1.8` `kotlin-1.9` `kotlin-2.0` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="cpp" size="l" /%} * [C++](https://hub.docker.com/r/openruntimes/cpp/tags) * `cpp-17` `cpp-20` * x86 / arm64 / armv7 / armv8 --- {% /table %} {% /tabsitem %} {% tabsitem #available-on-cloud title="Available on Cloud" %} {% table %} *   {% width=48 %} * Name {% width=120 %} * Versions * Architectures --- * {% icon icon="node_js" size="l" /%} * [Node.js](https://hub.docker.com/r/openruntimes/node/tags) * `node-16.0` `node-18.0` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="bun-sh" size="l" /%} * [Bun](https://hub.docker.com/r/openruntimes/bun/tags) * `bun-1.0` * x86 / arm64 --- * {% icon icon="deno" size="l" /%} * [Deno](https://hub.docker.com/r/openruntimes/deno/tags) * `deno-2.0` * x86 --- * {% icon icon="go" size="l" /%} * [Go](https://hub.docker.com/r/openruntimes/go/tags) * `go-1.23` * x86 / arm64 --- * {% icon icon="python" size="l" /%} * [Python](https://hub.docker.com/r/openruntimes/python/tags) * `python-3.9` `python-3.12` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="python" size="l" /%} * [Python ML](https://hub.docker.com/r/openruntimes/python-ml) * `python-ml-3.11` * x86 / arm64 --- * {% icon icon="dart" size="l" /%} * [Dart](https://hub.docker.com/r/openruntimes/dart/tags) * `dart-2.17` `dart-3.1` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="php" size="l" /%} * [PHP](https://hub.docker.com/r/openruntimes/php/tags) * `php-8.0` `php-8.3` * x86 / arm64 / armv7 / armv8 --- * {% icon icon="ruby" size="l" /%} * [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags) * `ruby-3.0` `ruby-3.3` * x86 / arm64 / armv7 / armv8 --- {% /table %} {% /tabsitem %} {% /tabs %} --- ## Templates https://appwrite.io/docs/products/functions/templates Appwrite provides a variety of Function Templates to help you jump start your function development. You can use Appwrite Function Templates as examples or boilerplates to add new functionality to your Appwrite project. ### Find templates {% #find-templates %} You can find all available templates by navigating to the Appwrite Console, under your project > **Functions** > **Templates**. {% only_dark %} ![Templates screen](/images/docs/functions/templates/dark/templates.png) {% /only_dark %} {% only_light %} ![Templates screen](/images/docs/functions/templates/templates.png) {% /only_light %} You can filter functions by searching, filter by use case, or filter by runtime. Click **Create function** to create a function from a template. ### Create with templates {% #create-with-templates %} The create function wizard has five steps. #### Configuration {% #configuration %} Pick a display name for your function and an ID. You will later use the ID to programmatically execute or configure the function. Pick the runtime language you wish the function to be created in, not all runtimes are available for all templates. #### Variables {% #variables %} Appwrite Functions uses [environment variables](/docs/products/functions/develop#environment-variables) to pass constants and secrets to your Appwrite Functions. You'll provide information like API keys and other secrets to integrations in this step. If you need an Appwrite API key, you'll be propted to generate one. #### Connect {% #connect %} You can choose to clone a new repository to your GitHub profile or organization, or to connect to an existing repository. If you choose connect to an existing repository, the function's code will be cloned to the root folder you specify under the **Branch** step. #### Repository {% #repository %} Configure the connected respository for your Appwrite Function. This will be the repository holding the source code for your function. When the code in this repository is updated, new deployments will be created. #### Branch {% #branch %} Production branch specifies the branch connected to your Appwrite Function. When new commits are made to this branch, a new deployment is automatically created and deployed. The root directory specifies the folder holding your function template's code. When a PR is made to the branch, a new deployment is built, but not activated. A comment is made to your PR about the build, unless you enable **Silent mode**. ### Available templates {% #available-templates %} {% table %} * Template {% width=100 %} * Description * Runtimes {% width=200 %} --- * Starter * A simple starter function that returns "Hello, world!" * Node.js, Python, PHP, Dart, Node.js (TypeScript), Bun, Deno, Ruby, Kotlin, C++, .NET, Java, Swift, Go --- * Sync with Meilisearch * Syncs rows in an Appwrite database table to a Meilisearch index to add search-as-you-type search boxes to your app. * Node.js, Python, PHP, Node.js (TypeScript), Bun, Deno, Ruby, Kotlin --- * WhatsApp with Vonage * Simple bot to answer WhatsApp messages. * Node.js, Python, PHP, Dart, Node.js (TypeScript), Bun, Deno, Ruby --- * Prompt ChatGPT * Ask question, and let OpenAI GPT-3.5-turbo answer. * Node.js, Python, PHP, Dart --- * Censor with Redact * Automatically remove sensitive data from messages. * Node.js, Python, Dart --- * Email Contact Form * Sends an email with the contents of a HTML form. * Node.js, Python, PHP --- * Sync with Algolia * Intuitive search bar for any data in Appwrite Databases. * Node.js, Python, PHP --- * Discord Command Bot * Add Discord commands to your servers using Discord Interactions. * Node.js, Python, Go --- * Github Issue Bot * Automate the process of responding to newly opened issues on a GitHub repository. * Node.js, Node.js (TypeScript) --- * Analyze with PerspectiveAPI * Automate moderation by using AI to measure the toxicity of messages. * Node.js --- * Generate PDF * Generate PDFs programmatically with Appwrite Functions. * Node.js --- * Payments with Stripe * Receive card payments and store paid orders. * Node.js --- * Push Notification with FCM * Send push notifications to your users using Firebase Cloud Messaging (FCM). * Node.js --- * Slack Command Bot * Simple command bot using Slack API * Node.js --- * Storage Cleaner * Storage cleaner function to remove all files older than X number of days from the specified bucket. * Node.js --- * Subscriptions with Stripe * Receive recurring card payments and grant subscribers extra permissions. * Node.js --- * URL Shortener * Generate URL with short ID and redirect to the original URL when visited. * Node.js {% /table %} --- ## Messaging https://appwrite.io/docs/products/messaging Appwrite Messaging helps you communicate with your users through push notifications, emails, and SMS text messages. Sending personalized communication for marketing, updates, and realtime alerts can increase user engagement and retention. You can also use Appwrite Messaging to implement security checks and custom authentication flows. {% only_dark %} ![Messaging overview](/images/docs/messaging/dark/message-overview.png) {% /only_dark %} {% only_light %} ![Messaging overview](/images/docs/messaging/message-overview.png) {% /only_light %} Explore what you can build with Appwrite Messaging. {% cards %} {% cards_item href="/docs/products/messaging/send-email-messages" title="Emails" icon="icon-mail" %} Send newsletters, invoices, promotions and other emails. {% /cards_item %} {% cards_item href="/docs/products/messaging/send-sms-messages" title="SMS messages" icon="icon-annotation" %} Send SMS messages straight to your user's phone. {% /cards_item %} {% cards_item href="/docs/products/messaging/send-push-notifications" title="Push notifications" icon="icon-device-mobile" %} Send push notifications to your user's devices. {% /cards_item %} {% /cards %} --- ## Apple Push Notification service https://appwrite.io/docs/products/messaging/apns Apple Push Notification service (APNs) lets you send push notifications to Apple devices like macOS, iOS, tvOS, iPadOS, and watchOS devices. APNs is a best-effort service, and will attempt to deliver you messages to your device when it's online and available again. APNs will save the last message for 30 days or less and attempt delivery as soon as it's online. {% section #add-provider step=1 title="Add provider" %} To add APNs as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Create provider** > **Push notification**. {% only_dark %} ![Add a FCM provider](/images/docs/messaging/providers/apns/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a FCM provider](/images/docs/messaging/providers/apns/provider.png) {% /only_light %} Give your provider a name > choose **APNS** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Apple developer account to connect your Appwrite project with your Apple developer account. You will need to provide the following information from the **Apple Developer Member Center**. {% accordion %} {% accordion_item title="Team ID" %} Head to **Apple Developer Member Center** > **Membership details** > **Team ID** {% only_dark %} ![Team ID](/images/docs/messaging/providers/apns/dark/team-id.png) {% /only_dark %} {% only_light %} ![Team ID](/images/docs/messaging/providers/apns/team-id.png) {% /only_light %} {% /accordion_item %} {% accordion_item title="Bundle ID" %} Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Identifiers** {% only_dark %} ![Bundle ID](/images/docs/messaging/providers/apns/dark/bundle-id.png) {% /only_dark %} {% only_light %} ![Bundle ID](/images/docs/messaging/providers/apns/bundle-id.png) {% /only_light %} {% /accordion_item %} {% accordion_item title="Authentication key ID" %} Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. Click on your key to view details. The key needs **Apple Push Notification Service** enabled. {% only_dark %} ![Authentication Key ID](/images/docs/messaging/providers/apns/dark/key-id.png) {% /only_dark %} {% only_light %} ![Authentication Key ID](/images/docs/messaging/providers/apns/key-id.png) {% /only_light %} {% /accordion_item %} {% accordion_item title="Authentication key (.p8 file)" %} Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. Create a key and give it a name. Enable the Apple Push Notifications service (APNS), and register your key. The key needs **Apple Push Notification Service** enabled. {% only_dark %} ![Authentication Key](/images/docs/messaging/providers/apns/dark/authentication-key.png) {% /only_dark %} {% only_light %} ![Authentication Key](/images/docs/messaging/providers/apns/authentication-key.png) {% /only_light %} {% /accordion_item %} {% accordion_item title="Sandbox" %} Enable sandbox mode for testing on apps signed with development provisioning profiles. APNs offers two environments, **Development** (sandbox) and **Production**. Development builds on XCode signed with a development provisioning profile will use the development environment. Production builds signed with a production provisioning profile will use the production environment. {% /accordion_item %} {% /accordion %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #configure-app step=3 title="Configure app" %} Some additional configuration is required to enable push notifications in your iOS app. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. {% only_dark %} ![Enable PN on Xcode](/images/docs/messaging/providers/apns/dark/xcode-enable-pn.png) {% /only_dark %} {% only_light %} ![Enable PN on Xcode](/images/docs/messaging/providers/apns/xcode-enable-pn.png) {% /only_light %} {% /section %} {% section #test-provider step=4 title="Test provider" %} Push notification requires special handling on the client side. Follow the [Send push notification](/docs/products/messaging/send-push-notifications) flow to test your provider. {% /section %} {% section #manage-provider step=5 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateApnsProvider( '', // providerId '', // name (optional) false, // enabled (optional) '', // authKey (optional) '', // authKeyId (optional) '', // teamId (optional) '' // bundleId (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateApnsProvider( '', // providerId '', // name (optional) false, // enabled (optional) '', // authKey (optional) '', // authKeyId (optional) '', // teamId (optional) '' // bundleId (optional) ); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateApnsProvider( providerId: '', name: '', // optional enabled: false, // optional authKey: '', // optional authKeyId: '', // optional teamId: '', // optional bundleId: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_apns_provider( provider_id = '', name = '', # optional enabled = False, # optional auth_key = '', # optional auth_key_id = '', # optional team_id = '', # optional bundle_id = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_apns_provider( provider_id: '', name: '', # optional enabled: false, # optional auth_key: '', # optional auth_key_id: '', # optional team_id: '', # optional bundle_id: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.updateApnsProvider( providerId: "" name: "" // optional enabled: false // optional authKey: "" // optional authKeyId: "" // optional teamId: "" // optional bundleId: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.updateApnsProvider( providerId: '', name: '', // optional enabled: false, // optional authKey: '', // optional authKeyId: '', // optional teamId: '', // optional bundleId: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateApnsProvider( "", // providerId "", // name (optional) false, // enabled (optional) "", // authKey (optional) "", // authKeyId (optional) "", // teamId (optional) "" // bundleId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateApnsProvider( "", // providerId "", // name (optional) false, // enabled (optional) "", // authKey (optional) "", // authKeyId (optional) "", // teamId (optional) "" // bundleId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateApnsProvider( providerId: "", name: "", // optional enabled: xfalse, // optional authKey: "", // optional authKeyId: "", // optional teamId: "", // optional bundleId: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Firebase Cloud Messaging https://appwrite.io/docs/products/messaging/fcm Firebase Cloud Messaging (FCM) lets you send push notifications to your iOS, Android, and web apps through Appwrite Messaging. Before you can deliver messages, you must connect to a messaging provider. {% section #add-provider step=1 title="Add provider" %} To add FCM as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **Push notification**. {% only_dark %} ![Add a FCM provider](/images/docs/messaging/providers/fcm/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a FCM provider](/images/docs/messaging/providers/fcm/provider.png) {% /only_light %} Give your provider a name > choose **FCM** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Firebase console to connect your Appwrite project. You will need to provide the following information from the **Firebase console**. {% info title="Enable FCM" %} FCM must be enabled on your Firebase project. Head to Firebase console -> Settings -> Project settings -> Cloud Messaging. If FCM is disabled, click the three-dots menu and open the link. On the following page, click **Enable** (it might take a few minutes for the action to complete). {% /info %} Head to **Project settings** > **Service accounts** > **Generate new private key**. {% only_dark %} ![FCM admin key](/images/docs/messaging/providers/fcm/dark/admin-key.png) {% /only_dark %} {% only_light %} ![FCM admin key](/images/docs/messaging/providers/fcm/admin-key.png) {% /only_light %} After all the relevant details are provided, you can enable the provider. {% /section %} {% section #configure-app step=3 title="Configure app" %} Some additional configuration is required to enable push notifications in your mobile app. {% tabs %} {% tabsitem #fcm-android title="Android with FCM" %} 1. Install the `com.google.firebase:firebase-messaging` Firebase SDK. 1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **Android** app. 1. Register and download your `google-services.json` config file. 1. Add `google-services.json` at the root of your project. 1. Add Google Services class path to your app-level Gradle dependencies block `"com.google.gms:google-services:4.4.0"`. 1. Add Google Services plugin to your app-level Gradle in the plugins block as `"com.google.gms.google-services"`. 1. Add notification handler service to `AndroidManifest.xml` inside the application tag, alongside other activities. Find an example of this service in the [Send push notification](/docs/products/messaging/send-push-notifications#add-targets) journey. ```xml ``` {% /tabsitem %} {% tabsitem #fcm-ios title="iOS with FCM" %} 1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **iOS** app. 1. Register and download your `GoogleService-Info.plist` and add it to the root of your project. 1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled. 1. Create a new key, note down the key ID and download your key. 1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here. 1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. 1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`. {% /tabsitem %} {% tabsitem #fcm-flutter title="Flutter with FCM" %} 1. Install the [Firebase CLI](https://firebase.google.com/docs/cli) and [FlutterFire CLI](https://pub.dev/packages/flutterfire_cli). 1. From your Flutter project directory, configure Firebase by running `flutterfire configure`. 1. Add the Firebase messaging plugin to your Flutter project with `flutter pub add firebase_messaging`. 1. Add the Firebase core plugin if not already added with `flutter pub add firebase_core`. 1. Initialize Firebase in your `lib/main.dart` file: ```dart import 'package:flutter/widgets.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(MyApp()); } ``` 1. **For iOS**: - Enable push notifications and background modes in XCode by opening `ios/Runner.xcworkspace` and adding the **Push Notifications** capability and **Background Modes** (Background fetch and Remote notifications) - Upload your APNs authentication key to Firebase console under **Cloud Messaging** settings - Request notification permission at runtime using `FirebaseMessaging.instance.requestPermission()` 1. **For Android**: FCM requires devices running Android 5.0 or higher with Google Play services installed. 1. **For Web**: Add a `firebase-messaging-sw.js` file in your `web/` directory that imports the Firebase messaging SDK and handles background messages. {% /tabsitem %} {% /tabs %} {% /section %} {% section #test-provider step=4 title="Test provider" %} Push notification requires special handling on the client side. Follow the [Send push notification](/docs/products/messaging/send-push-notifications) flow to test your provider. {% /section %} {% section #manage-provider step=5 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateFCMProvider( '', // providerId '', // name (optional) false, // enabled (optional) {} // serviceAccountJSON (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateFCMProvider( '', // providerId '', // name (optional) false, // enabled (optional) {} // serviceAccountJSON (optional) ); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateFCMProvider( providerId: '', name: '', // optional enabled: false, // optional serviceAccountJSON: [] // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_fcm_provider( provider_id = '', name = '', # optional enabled = False, # optional service_account_json = {} # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_fcm_provider( provider_id: '', name: '', # optional enabled: false, # optional service_account_json: {} # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateFCMProvider( providerId: "" name: "" // optional enabled: false // optional serviceAccountJSON: [object]); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.updateFCMProvider( providerId: '', name: '', // optional enabled: false, // optional serviceAccountJSON: {}, // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateFCMProvider( "", // providerId "", // name (optional) false, // enabled (optional) mapOf( "a" to "b" ) // serviceAccountJSON (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateFCMProvider( "", // providerId "", // name (optional) false, // enabled (optional) mapOf( "a" to "b" ) // serviceAccountJSON (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateFCMProvider( providerId: "", name: "", // optional enabled: xfalse, // optional serviceAccountJSON: [:] // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Mailgun https://appwrite.io/docs/products/messaging/mailgun Mailgun lets you send customized email messages to your users. These emails can be sent immediately or scheduled. You can send emails for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add Mailgun as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **Email**. {% only_dark %} ![Add a SMTP provider](/images/docs/messaging/providers/mailgun/dark/add-mailgun.png) {% /only_dark %} {% only_light %} ![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.png) {% /only_light %} Give your provider a name > choose **Mailgun** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Mailgun dashboard to connect your Appwrite project. {% only_dark %} ![Configure SMTP provider](/images/docs/messaging/providers/mailgun/dark/configure-mailgun.png) {% /only_dark %} {% only_light %} ![Configure SMTP provider](/images/docs/messaging/providers/mailgun/configure-mailgun.png) {% /only_light %} You will need to provide the following information from your **Mailgun dashboard**. {% table %} * Field name * --- * API key * Head to Profile -> API Security -> Add new key. --- * Domain * Head to Sending -> Domains -> Add new domain. Follow [Mailgun's instructions](https://help.mailgun.com/hc/en-us/articles/360026833053-Domain-Verification-Walkthrough) to verify the domain name. --- * EU region * Enable the EU region setting if your domain is within the European Union. --- * Sender email * The provider sends emails from this sender email. The sender email needs to be an email under the configured domain. --- * Sender name * The sender name that appears in the emails sent from this provider. --- * Reply-to email * The reply-to email that appears in the emails sent from this provider. The reply-to email needs to be an email under the configured domain. --- * Reply-to name * The reply-to name that appears in the emails sent from this provider. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **Email**. {% only_dark %} ![Create email message](/images/docs/messaging/messages/dark/create-email-message.png) {% /only_dark %} {% only_light %} ![Create email message](/images/docs/messaging/messages/create-email-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('') // Your secret API key ; const message = await messaging.createEmail({ messageId: '', subject: '', content: '' }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('') // Your secret API key ; const message = await messaging.createEmail({ messageId: '', subject: '', content: '' }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail('', '', ''); ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email('', '', '') ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email(message_id: '', subject: '', content: '') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; using Appwrite.Enums; using Appwrite.Enums; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey(""); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "", subject: "", content: ""); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('') // Your secret API key ; Future result = await messaging.createEmail( messageId:'' , subject:'' , content:'' , ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Messaging val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key val messaging = Messaging(client) val response = messaging.createEmail( messageId = "", subject = "", content = "", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey(""); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "", "", "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "", subject: "", content: "" ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send email messages](/docs/products/messaging/send-push-notifications) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('') // Your secret API key ; // update provider messaging.updateSendgridProvider( '', '', '', '', '', '', '', '', '', '', ).then(function (response) { console.log(response); }, function (error) { console.log(error); }); // delete provider messaging.deleteProvider('') .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); // update provider messaging.updateSendgridProvider( '', '', '', '', '', '', '', '', '', '', ).then(function (response) { console.log(response); }, function (error) { console.log(error); }); // delete provider messaging.deleteProvider('') .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey('') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateSendgridProvider( '', '', '', '', '', '', '', '', '', '', ); ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your secret API key ) messaging = Messaging(client) result = messaging.update_sendgrid_provider( '', '', '', '', '', '', '', '', '', '', ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_key('') # Your secret API key messaging = Messaging.new(client) response = messaging.update_sendgrid_provider( provider_id: "", name: "", api_key: "", domain: "", isEuRegion: "", from_name: "", from_email: "", reply_to_name: "", reply_to_email: "", enabled: "", ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetKey(""); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateSendgridProvider( providerId: "", name: "", apiKey: "", domain: "", isEuRegion: "", fromName: "", fromEmail: "", replyToName: "", replyToEmail: "", enabled: "", ); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('') // Your secret API key ; Future result = messaging.updateSendgridProvider( providerId: "", name: "", apiKey: "", domain: "", isEuRegion: "", fromName: "", fromEmail: "", replyToName: "", replyToEmail: "", enabled: "", ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Messaging val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key val messaging = Messaging(client) val response = messaging.updateSendgridProvider( providerId = "", name = "", apiKey = "", domain = "", isEuRegion = "", fromName = "", fromEmail = "", replyToName = "", replyToEmail = "", enabled = "", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey(""); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateSendgridProvider( "", "", "", "", "", "", "", "", "", "", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setKey("") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateSendgridProvider( providerId: "", name: "", apiKey: "", domain: "", isEuRegion: "", fromName: "", fromEmail: "", replyToName: "", replyToEmail: "", enabled: "", ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Messages https://appwrite.io/docs/products/messaging/messages Each time you send or schedule a push notification, email, or SMS text, it's recorded in Appwrite as a **message** is displayed in the **Messages** tab. {% only_dark %} ![Add a target](/images/docs/messaging/messages/dark/messages-overview.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/messages/messages-overview.png) {% /only_light %} ### Messages {% #messages %} Each message displays with the following information. {% table %} * Column {% width=200 %} * Description --- * Message ID * The unique ID of the message. --- * Description * The developer defined description of the message. End users do not see this description. --- * Message * The message delivered to end users. --- * Type * Type of message, either `Push`, `Email`, and `SMS`. --- * Status * Indicates the status of the message, can be one of `draft`, `scheduled`, `processing`, `failed`, `success`. --- * Scheduled at * Indicates the scheduled delivery time of the message. --- * Delivered at * Indicates the time at which the message was successfully delivered. {% /table %} ### Messages types {% #messages-types %} There are three types of messages {% table %} * Message type {% width=200 %} * Description --- * Push notifications * Push notifications are alerts that show up on a user device's notification center. This can be used to deliver messages to the user whether their application is open or not. --- * Emails * Emails let you deliver rich content to a users' inbox. Appwrite allows you to send customized HTML email messages so you can include links, styling, and more. --- * SMS * SMS messages let you deliver text messages to your user's phone. This helps you reach your user, even when their device do not have internet access. {% /table %} ### Messages lifecycle {% #messages-lifecycle %} Messages can begin as a `draft`, or proceed directly to `processing` if it's sent immediately. If the message is scheduled to be sent later, its status is set to `scheduled`, then to `processing` at schedule time. After attempted delivery, it is marked as `sent` or `failed` depending on if the message was successfully delivered. {% only_dark %} ![Message lifecycle](/images/docs/messaging/dark/message-status.png) {% /only_dark %} {% only_light %} ![Message lifecycle](/images/docs/messaging/message-status.png) {% /only_light %} ### Choosing a message type {% #choosing-a-message-type %} Choosing the right type of notification to reach your audience is important for your app's success. Here are some common factors to consider when deciding what type of message should be sent. {% table %} * Message type {% width=200 %} * Description --- * Time-sensitive messages * Push notifications or SMS messages are ideal for time-sensitive messages, as they are typically checked frequently and opened within minutes, ensuring prompt attention. --- * Guaranteed delivery * Emails and SMS messages are more reliable for guaranteed delivery of important messages like invoices and order confirmations, as push notifications can be easily missed. --- * Content-rich messages * Emails are best suited for delivering content-rich messages like promotional letters, detailed updates, and newsletters, thanks to support for HTML, allowing for rich text, links, and styling. --- * Increasing engagement * Push notifications are effective for increasing engagement with users, as they can be clicked on to link directly to your app, promoting immediate interaction. --- * Accessibility and reach * Emails and SMS messages allow you to reach users even before they have installed your app, making them suitable for announcement-type messages that require broad accessibility. {% /table %} ### Composing messages {% #composing-messages %} Different types of messages have different content and configurable options. Here are the different components that make up a message. {% tabs %} {% tabsitem #push-notification title="Push notifications" %} {% table %} * Parameter {% width=100 %} * Required {% width=100 %} * Description --- * `messageId` * required * The title of the push notification. This is the headline text that recipients see first. --- * `title` * optional * The title of the push notification. This is the headline text that recipients see first. Can be omitted for background notifications. --- * `body` * optional * The main content or body of the push notification. Provides the details or message you want to convey. Can be omitted for background notifications. --- * `data` * optional * Extra key-value pairs that apps can use to handle the notification more effectively, such as directing users to a specific part of the app. --- * `action` * optional * Specifies which activity or view controller to open within the app when the notification is tapped. --- * `icon` * optional * Sets the icon of the notification, used only for Android devices. This can help in branding the notification. --- * `sound` * optional * Sets the sound to use for the notification. For Android, the sound file must be located in `/res/raw`; for Apple devices, it must be in the app's main bundle or the `Library/Sounds` folder of the app container. --- * `color` * optional * Specifies a color tint for the notification icon, used only for Android devices. This can be used to align with brand colors. --- * `tag` * optional * Can be used to replace an existing notification with the same tag, used only for Android devices. Useful for updating or canceling notifications. --- * `badge` * optional * Sets the number to display next to the app's icon, indicating the number of notifications or updates. Setting to 0 removes any existing badge. Must be an integer. For Apple devices only. --- * `contentAvailable` * optional * For iOS devices only. When set, wakes up the app in the background without showing a notification. Used to update app data remotely. Requires priority to be set to normal. **Note:** APNS may throttle if sending more than 2-3 background notifications per hour. For Android, similar functionality can be achieved by sending a data-only notification without title and body. --- * `critical` * optional * For iOS devices only. Marks the notification as critical to bypass silent and do not disturb settings. Requires the app to have the critical notification entitlement from Apple. --- * `priority` * optional * Sets notification priority to normal or high. Normal priority delivers at the most convenient time based on battery life and may group notifications. High priority delivers immediately. --- * `draft` * optional * If the message is a draft, can be `true` or `false`. --- * `scheduledAt` * optional * An ISO date time string specifying when the push notification should be sent. {% /table %} {% /tabsitem %} {% tabsitem #emails title="Emails" %} {% table %} * Parameter {% width=100 %} * Required {% width=100 %} * Description --- * `subject` * required * The subject line of the email. This is what recipients see first in their inbox. --- * `content` * required * The main content of the email. This can be plain text or HTML, depending on the `html` flag. --- * `cc` * optional * An array of target IDs to be included in the carbon copy (CC) field. These recipients can see each other's email addresses. --- * `bcc` * optional * An array of target IDs to be included in the blind carbon copy (BCC) field. These recipients cannot see each other's email addresses. --- * `html` * optional * A boolean indicating whether the `content` is in HTML format. This allows for rich text, links, and styling in the email content. --- * `draft` * optional * If the message is a draft, can be `true` or `false`. --- * `scheduledAt` * optional * An ISO date time string specifying when the email should be sent. {% /table %} {% /tabsitem %} {% tabsitem #sms title="SMS" %} {% table %} * Parameter {% width=100 %} * Required {% width=100 %} * Description --- * `content` * required * The main content of the SMS. This should be concise and clear, as SMS messages have character limits. --- * `draft` * optional * If the message is a draft, can be `true` or `false`. --- * `scheduledAt` * optional * An ISO date time string specifying when the SMS should be sent. {% /table %} {% /tabsitem %} {% /tabs %} ### Sending a message {% #create-a-message %} You can create a message with a Server SDK. You can send a push notification like this. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createPush( '', // messageId '', // title '<BODY>', // body [], // topics (optional) [], // users (optional) [], // targets (optional) {}, // data (optional) '<ACTION>', // action (optional) '<ICON>', // icon (optional) '<SOUND>', // sound (optional) '<COLOR>', // color (optional) '<TAG>', // tag (optional) 1, // badge (optional) false, // contentAvailable (optional) false, // critical (optional) 'normal', // priority (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createPush( '<MESSAGE_ID>', // messageId '<TITLE>', // title '<BODY>', // body [], // topics (optional) [], // users (optional) [], // targets (optional) {}, // data (optional) '<ACTION>', // action (optional) '<ICON>', // icon (optional) '<SOUND>', // sound (optional) '<COLOR>', // color (optional) '<TAG>', // tag (optional) 1, // badge (optional) false, // contentAvailable (optional) false, // critical (optional) 'normal', // priority (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createPush( messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: [], // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: 1, // optional contentAvailable: false, // optional critical: false, // optional priority: 'normal', // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_push( message_id = '<MESSAGE_ID>', title = '<TITLE>', body = '<BODY>', topics = [], # optional users = [], # optional targets = [], # optional data = {}, # optional action = '<ACTION>', # optional icon = '<ICON>', # optional sound = '<SOUND>', # optional color = '<COLOR>', # optional tag = '<TAG>', # optional badge = 1, # optional content_available = False, # optional critical = False, # optional priority = 'normal', # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_push( message_id: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], # optional users: [], # optional targets: [], # optional data: {}, # optional action: '<ACTION>', # optional icon: '<ICON>', # optional sound: '<SOUND>', # optional color: '<COLOR>', # optional tag: '<TAG>', # optional badge: '<BADGE>', # optional content_available: false, # optional critical: false, # optional priority: 'normal', # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreatePush( messageId: "<MESSAGE_ID>", title: "<TITLE>", body: "<BODY>" topics: new List<string> {}, // optional users: new List<string> {}, // optional targets: new List<string> {}, // optional data: [object] // optional action: "<ACTION>", // optional icon: "<ICON>", // optional sound: "<SOUND>", // optional color: "<COLOR>", // optional tag: "<TAG>", // optional badge: 1, // optional contentAvailable: false, // optional critical: false, // optional priority: "normal", // optional draft: true, // optional scheduledAt: "" // optional ); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createPush( messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: {}, // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: 1, // optional content_available: false, // optional critical: false, // optional priority: 'normal', // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createPush( "<MESSAGE_ID>", // messageId "<TITLE>", // title "<BODY>", // body listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) mapOf( "a" to "b" ), // data (optional) "<ACTION>", // action (optional) "<ICON>", // icon (optional) "<SOUND>", // sound (optional) "<COLOR>", // color (optional) "<TAG>", // tag (optional) 1, // badge (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createPush( "<MESSAGE_ID>", // messageId "<TITLE>", // title "<BODY>", // body listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) mapOf( "a" to "b" ), // data (optional) "<ACTION>", // action (optional) "<ICON>", // icon (optional) "<SOUND>", // sound (optional) "<COLOR>", // color (optional) "<TAG>", // tag (optional) 1, // badge (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createPush( messageId: "<MESSAGE_ID>", title: "<TITLE>", body: "<BODY>", topics: [], // optional users: [], // optional targets: [], // optional data: [:], // optional action: "<ACTION>", // optional icon: "<ICON>", // optional sound: "<SOUND>", // optional color: "<COLOR>", // optional tag: "<TAG>", // optional badge: 1, // optional content_available: false, // optional critical: false, // optional priority: "normal", // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% arrow_link href="/docs/products/messaging/send-push-notifications" %} Learn more about sending a push notification {% /arrow_link %} You can send an email like this. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message - await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) true, // draft (optional) false, // html (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message - await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) true, // draft (optional) false, // html (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: true, // optional html: false, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email( message_id = '<MESSAGE_ID>', subject = '<SUBJECT>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional cc = [], # optional bcc = [], # optional draft = True, # optional html = False, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email( message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional cc: [], # optional bcc: [], # optional draft: true, # optional html: false, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional cc: new List<string> {} // optional bcc: new List<string> {} // optional draft: true // optional html: false // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: true, // optional html: false, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) true, // draft (optional) false, // html (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) true, // draft (optional) false, // html (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: true, // optional html: xfalse, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% arrow_link href="/docs/products/messaging/send-email-messages" %} Learn more about sending an email {% /arrow_link %} You can send an SMS message like this. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% arrow_link href="/docs/products/messaging/send-sms-messages" %} Learn more about sending a SMS message {% /arrow_link %} --- ## MSG91 https://appwrite.io/docs/products/messaging/msg91 MSG91 lets you send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add MSG91 as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS**. {% only_dark %} ![Add a MSG91 provider](/images/docs/messaging/providers/msg91/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a MSG91 provider](/images/docs/messaging/providers/msg91/provider.png) {% /only_light %} Give your provider a name > choose **MSG91** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your MSG91 dashboard to connect your Appwrite project. You will need to provide the following information from your **MSG91 dashboard**. {% table %} * Field name * --- * Auth key * Click to open the Username dropdown > **Authkey** > **Verify your mobile number** > **Create Authkey**. --- * Sender ID * Head to MSG91 dashboard > **SMS** > **Sender ID** > **Create sender ID**. --- * Sender number * {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **SMS**. {% only_dark %} ![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.png) {% /only_dark %} {% only_light %} ![Create an SMS message](/images/docs/messaging/messages/create-sms-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send email messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.updateMsg91Provider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<SENDER_ID>', // senderId (optional) '<AUTH_KEY>', // authKey (optional) '<FROM>' // from (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.updateMsg91Provider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<SENDER_ID>', // senderId (optional) '<AUTH_KEY>', // authKey (optional) '<FROM>' // from (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateMsg91Provider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional senderId: '<SENDER_ID>', // optional authKey: '<AUTH_KEY>', // optional from: '<FROM>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_msg91_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional enabled = False, # optional sender_id = '<SENDER_ID>', # optional auth_key = '<AUTH_KEY>', # optional from = '<FROM>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_msg91_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional enabled: false, # optional sender_id: '<SENDER_ID>', # optional auth_key: '<AUTH_KEY>', # optional from: '<FROM>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateMsg91Provider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional enabled: false // optional senderId: "<SENDER_ID>" // optional authKey: "<AUTH_KEY>" // optional from: "<FROM>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.updateMsg91Provider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional senderId: '<SENDER_ID>', // optional authKey: '<AUTH_KEY>', // optional from: '<FROM>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateMsg91Provider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<SENDER_ID>", // senderId (optional) "<AUTH_KEY>", // authKey (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateMsg91Provider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<SENDER_ID>", // senderId (optional) "<AUTH_KEY>", // authKey (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateMsg91Provider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional enabled: xfalse, // optional senderId: "<SENDER_ID>", // optional authKey: "<AUTH_KEY>", // optional from: "<FROM>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Providers https://appwrite.io/docs/products/messaging/providers Appwrite allows you to connect to a variety of third-party messaging providers to deliver push notifications, emails, and SMS messages to your users. Before you can deliver messages, you must connect to a messaging provider. ### Push notifications {% #push-notifications %} Send push notifications, which are little notification messages that appear on a user's browser or device to alert them of events or updates. Configure one of the following providers to send push notifications. {% cards %} {% cards_item href="/docs/products/messaging/apns" title="APNS" icon="icon-apple" %} Send push notifications to apps on Apple devices through Apple Push Notification service (APNs). {% /cards_item %} {% cards_item href="/docs/products/messaging/fcm" title="FCM" icon="web-icon-firebase" %} Send push notifications to Android, Apple, or Web app with Firebase Cloud Messaging (FCM). {% /cards_item %} {% /cards %} ### Email {% #email %} Deliver customized emails to users to send reminders, updates, promotions, and custom authentication logic. {% cards %} {% cards_item href="/docs/products/messaging/mailgun" title="Mailgun" icon="web-icon-mailgun" %} Deliver custom email messages to users using Mailgun. {% /cards_item %} {% cards_item href="/docs/products/messaging/sendgrid" title="SendGrid" icon="web-icon-sendgrid" %} Deliver custom email messages to users using SendGrid. {% /cards_item %} {% cards_item href="/docs/products/messaging/smtp" title="SMTP" icon="icon-mail" %} Deliver custom email messages to users using standard SMTP settings. {% /cards_item %} {% /cards %} ### SMS {% #sms %} Send customized SMS messages to users by phone to send reminders, updates, promotions, and one-time passwords. {% cards %} {% cards_item href="/docs/products/messaging/twilio" title="Twilio" icon="icon-twilio" %} Deliver custom SMS messages to users using Twilio. {% /cards_item %} {% cards_item href="/docs/products/messaging/msg91" title="MSG91" icon="icon-msg91" %} Deliver custom SMS messages to users using MSG91. {% /cards_item %} {% cards_item href="/docs/products/messaging/telesign" title="Telesign" icon="icon-telesign" %} Deliver custom SMS messages to users using Telesign. {% /cards_item %} {% cards_item href="/docs/products/messaging/textmagic" title="Textmagic" icon="icon-textmagic" %} Deliver custom SMS messages to users using Textmagic. {% /cards_item %} {% cards_item href="/docs/products/messaging/vonage" title="Vonage" icon="icon-vonage" %} Deliver custom SMS messages to users using Vonage. {% /cards_item %} {% /cards %} --- ## Send email messages https://appwrite.io/docs/products/messaging/send-email-messages You can send custom email messages to your app's users using Appwrite Messaging and a connected SMTP service. This guide takes you through the implementation path of adding email messaging to your app. ### Add a provider {% #add-a-provider %} Appwrite supports [Mailgun](/docs/products/messaging/mailgun/) and [Sendgrid](/docs/products/messaging/sendgrid/) as SMTP providers. You must configure one of them as a provider. {% only_dark %} ![Add a SMTP provider](/images/docs/messaging/providers/mailgun/dark/add-mailgun.png) {% /only_dark %} {% only_light %} ![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.png) {% /only_light %} To add a new provider navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **Email** and follow the wizard. You can find more details about configuring in the provider guides for [Mailgun](/docs/products/messaging/mailgun#configure-provider) and [Sendgrid](/docs/products/messaging/sendgrid#configure-provider). ### Add targets {% #add-targets %} In Appwrite Messaging, each user has **targets** like their email, phone number, and devices with your app installed. You can deliver messages to users through their **targets**. {% only_dark %} ![Target overview](/images/docs/messaging/targets/dark/target-overview.png) {% /only_dark %} {% only_light %} ![Target overview](/images/docs/messaging/targets/target-overview.png) {% /only_light %} If the user signed up with email and password, their account would already have email as a target. During development, you can add targets to existing accounts by navigating to **Authentication** > **Users** > **Select a user** > **Targets** > **Add a subscriber**. {% only_dark %} ![Add a target](/images/docs/messaging/targets/dark/add-targets.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/targets/add-targets.png) {% /only_light %} You can also implement forms in your app to collect contact information and add it as a target with the [createTarget](/docs/references/cloud/server-nodejs/users#createTarget) endpoint. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const users = new sdk.Users(client); const target = await users.createTarget( '<USER_ID>', // userId '<TARGET_ID>', // targetId sdk.MessagingProviderType.Email, // providerType '<IDENTIFIER>', // identifier '<PROVIDER_ID>', // providerId (optional) '<NAME>' // name (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token const users = new sdk.Users(client); const target = await users.createTarget( '<USER_ID>', // userId '<TARGET_ID>', // targetId sdk.MessagingProviderType.Email, // providerType '<IDENTIFIER>', // identifier '<PROVIDER_ID>', // providerId (optional) '<NAME>' // name (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Users; use Appwrite\Enums\MessagingProviderType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key $users = new Users($client); $target = $users->createTarget( userId: '<USER_ID>', targetId: '<TARGET_ID>', providerType: MessagingProviderType::EMAIL(), identifier: '<IDENTIFIER>', providerId: '<PROVIDER_ID>', // optional name: '<NAME>' // optional ); ``` ```python from appwrite.client import Client from appwrite.enums import MessagingProviderType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key users = Users(client) target = users.create_target( user_id = '<USER_ID>', target_id = '<TARGET_ID>', provider_type = MessagingProviderType.EMAIL, identifier = '<IDENTIFIER>', provider_id = '<PROVIDER_ID>', # optional name = '<NAME>' # optional ) ``` ```ruby require 'appwrite' include Appwrite include Appwrite::Enums client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key users = Users.new(client) target = users.create_target( user_id: '<USER_ID>', target_id: '<TARGET_ID>', provider_type: MessagingProviderType::EMAIL, identifier: '<IDENTIFIER>', provider_id: '<PROVIDER_ID>', # optional name: '<NAME>' # optional ) puts target.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Users users = new Users(client); Target target = await users.CreateTarget( userId: "<USER_ID>", targetId: "<TARGET_ID>", providerType: MessagingProviderType.Email, identifier: "<IDENTIFIER>", providerId: "<PROVIDER_ID>", // optional name: "<NAME>" // optional ); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key Users users = Users(client); final target = await users.createTarget( userId: '<USER_ID>', targetId: '<TARGET_ID>', providerType: MessagingProviderType.email, identifier: '<IDENTIFIER>', providerId: '<PROVIDER_ID>', // (optional) name: '<NAME>', // (optional) ); ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Users import io.appwrite.enums.MessagingProviderType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val users = Users(client) val target = users.createTarget( userId = "<USER_ID>", targetId = "<TARGET_ID>", providerType = MessagingProviderType.EMAIL, identifier = "<IDENTIFIER>", providerId = "<PROVIDER_ID>", // optional name = "<NAME>" // optional ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Users; import io.appwrite.enums.MessagingProviderType; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Users users = new Users(client); users.createTarget( "<USER_ID>", // userId "<TARGET_ID>", // targetId MessagingProviderType.EMAIL, // providerType "<IDENTIFIER>", // identifier "<PROVIDER_ID>", // providerId (optional) "<NAME>", // name (optional) new CoroutineCallback<>((target, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(target); }) ); ``` ```swift import Appwrite import AppwriteEnums let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let users = Users(client) let target = try await users.createTarget( userId: "<USER_ID>", targetId: "<TARGET_ID>", providerType: .email, identifier: "<IDENTIFIER>", providerId: "<PROVIDER_ID>", // optional name: "<NAME>" // optional ) ``` {% /multicode %} ### Create topics (optional) {% #create-topics %} You can use topics to organize targets that should receive the same messages, so you can send emails to groups of targets instead of one at time. This step is optional if you plan to only send emails to individual targets. To create a topic in the Appwrite Console, navigate to **Messaging** > **Topics** > {% icon icon="plus" size="m" /%} **Create topic**. {% only_dark %} ![Add a target](/images/docs/messaging/topics/dark/create-topics.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/topics/create-topics.png) {% /only_light %} You can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const topic = await messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>' // name ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const topic = await messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>' // name ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createTopic( topicId: '<TOPIC_ID>', name: '<NAME>' ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_topic( topic_id = '<TOPIC_ID>', name = '<NAME>' ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_topic( topic_id: '<TOPIC_ID>', name: '<NAME>' ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Topic result = await messaging.CreateTopic( topicId: "<TOPIC_ID>", name: "<NAME>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createTopic( topicId: '<TOPIC_ID>', name: '<NAME>', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>" // name new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>" // name new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let topic = try await messaging.createTopic( topicId: "<TOPIC_ID>", name: "<NAME>" ) ``` {% /multicode %} ### Send emails {% #send-emails %} You can send emails using a Server SDK. To send an email immediately, you can call the `createEmail` endpoint with `schedule` left empty. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) [], // attachments (optional) false, // draft (optional) false, // html (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) [], // attachments (optional) false, // draft (optional) false, // html (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: false, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email( message_id = '<MESSAGE_ID>', subject = '<SUBJECT>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional cc = [], # optional bcc = [], # optional draft = False, # optional html = False, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email( message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional cc: [], # optional bcc: [], # optional draft: false, # optional html: false, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional cc: new List<string> {} // optional bcc: new List<string> {} // optional draft: false // optional html: false // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: false, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) false, // draft (optional) false, // html (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) false, // draft (optional) false, // html (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: xfalse, // optional scheduledAt: "" // optional ) ``` {% /multicode %} ### Schedule emails {% #schedule-emails %} To send a scheduled email, you can call the `createEmail` endpoint with `status` set to `'scheduled'` and `schedule` as a ISO 8601 date time string for the scheduled time. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) false, // draft (optional) false, // html (optional) '2025-02-13T22:01:00+0000' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message - await messaging.createEmail( '<MESSAGE_ID>', // messageId '<SUBJECT>', // subject '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) [], // cc (optional) [], // bcc (optional) false, // draft (optional) false, // html (optional) '2025-02-13T22:01:00+0000' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: false, // optional scheduledAt: '2025-02-13T22:01:00+0000' ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email( message_id = '<MESSAGE_ID>', subject = '<SUBJECT>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional cc = [], # optional bcc = [], # optional draft = False, # optional html = False, # optional scheduled_at = '2025-02-13T22:01:00+0000' ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email( message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional cc: [], # optional bcc: [], # optional draft: false, # optional html: false, # optional scheduled_at: '2025-02-13T22:01:00+0000' ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2");// Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional cc: new List<string> {} // optional bcc: new List<string> {} // optional draft: false // optional html: false // optional scheduledAt: "2025-02-13T22:01:00+0000"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.createEmail( messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: false, // optional scheduledAt: '2025-02-13T22:01:00+0000', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) false, // draft (optional) false, // html (optional) "2025-02-13T22:01:00+0000" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2");// Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", // messageId "<SUBJECT>", // subject "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) listOf(), // cc (optional) listOf(), // bcc (optional) false, // draft (optional) false, // html (optional) "2025-02-13T22:01:00+0000" new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional cc: [], // optional bcc: [], // optional draft: false, // optional html: xfalse, // optional scheduledAt: "2025-02-13T22:01:00+0000" ) ``` {% /multicode %} --- ## Send push notification https://appwrite.io/docs/products/messaging/send-push-notifications You can send, schedule, and manage push notifications to your apps using Appwrite Messaging. Push notifications can be used to deliver new message notifications, app updates, promotional offers, and other messages straight to your user's devices. {% section #add-provider step=1 title="Add provider" %} Push notifications must be sent through third-party providers, like Apple Push Notification service and Firebase Cloud Messaging. The push notification APIs for Apple and Android devices can only be accessed through these services. You must configure these services before you can send your first push notification. {% cards %} {% cards_item href="/docs/products/messaging/apns" title="APNS" icon="icon-apple" %} Configure APNs for push notification to Apple devices. {% /cards_item %} {% cards_item href="/docs/products/messaging/fcm" title="FCM" icon="web-icon-firebase" %} Configure FCM for push notification to Android and Apple devices. {% /cards_item %} {% /cards %} {% /section %} {% section #add-targets step=2 title="Add targets" %} Before sending your first push notification, your application must register itself for push notification, then provide the device token to Appwrite. {% tabs %} {% tabsitem #apple-apns title="APNs for Apple" %} First, enable push notification in your app. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. {% only_dark %} ![Authentication Key](/images/docs/messaging/providers/apns/dark/xcode-enable-pn.png) {% /only_dark %} {% only_light %} ![Authentication Key](/images/docs/messaging/providers/apns/xcode-enable-pn.png) {% /only_light %} First, register for remote notifications in your app delegate's `application(_:didFinishLaunchingWithOptions:)` method. ```swift func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { DispatchQueue.main.async { application.registerForRemoteNotifications() } } } return true } ``` Next, create a handler for when the app receives the push notification device token. ```swift func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { /* store this `token` */ let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined() } ``` Since the token is saved in `UserDefaults`, you can access it from anywhere in your app. With this saved `apnsToken`, you can create a push target with Appwrite when the user logs in. Each push target is associated with an account, heres an example with an email password login. The same logic applies to all types of login methods. ```swift func login() async { do { let session = try await account.createEmailPasswordSession(email: username, password: password) let token = /* Retrieve the stored push token */ try await account.createPushTarget({ targetId: ID.unique(), identifier: token }) } catch { print("Login failed: \(error.localizedDescription)") } } ``` {% /tabsitem %} {% tabsitem #android-fcm title="FCM for Android" %} Before you can send push notifications using FCM, make sure you'd followed the steps to [Add Firebase to your Android project](https://firebase.google.com/docs/android/setup). After adding Firebase to your Android project and adding the `google-services.json` to your project, initialize Firebase in your main activity and fetch the FCM registration token. ```kotlin class MainActivity : AppCompatActivity() { override fun onCreate() { // Initialize Firebase FirebaseApp.initializeApp(this) // Set the FCM token FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> if (task.isSuccessful) { /* store this `token` */ val token = task.result } }) } } ``` Appwrite's push targets are associated with accounts. Typically, you would create a push target when the user logs in. For example, when the user logs in with email and password, your app can register itself as a target after handling the login. ```kotlin fun login(email: String, password: String) { viewModelScope.launch { try { val session = account.createEmailPasswordSession(email, password) let token = /* Retrieve the stored push token */ /* store the `target.id` */ val target = account.createPushTarget( targetId = ID.unique(), identifier = token ) } catch (e: AppwriteException) { Log.e("Login", "Failed: ${e.message}") } } } ``` Lastly, because FCM push tokens can change, we need to add a service to handle FCM token refreshes and update the target with Appwrite Messaging. Create a new service that extends `FirebaseMessagingService` which handles the event where the FCM token is updated. ```kotlin class MessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) /* store the `token` */ /* If the user is logged in, update the push target */ runBlocking { account?.updatePushTarget(/* retrieve saved `target.id` */, token) } } } ``` In your `AndroidManifest.xml`, register this new service. ```xml <service android:name="<YOUR_NOTIFICATION_HANDLER_SERVICE>" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> ``` {% /tabsitem %} {% tabsitem #apple-fcm title="FCM for Apple" %} Before you can send push notifications using FCM, make sure you'd followed the steps to [Add Firebase to your iOS project](https://firebase.google.com/docs/ios/setup). After adding Firebase to your iOS project and adding the `GoogleService-Info.plist` to the root of your project. Next, add your APNs key to Firebase. 1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled. 1. Create a new key, note down the key ID and download your key. 1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here. 1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. 1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`. Initialize Firebase in your app delegate's `application(_:didFinishLaunchingWithOptions:)` method, implement the messaging delegate protocol, and register for remote notifications. ```swift func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { FirebaseApp.configure() Messaging.messaging().delegate = self UNUserNotificationCenter.current().delegate = self UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { DispatchQueue.main.async { application.registerForRemoteNotifications() } } } return true } ``` Your APNS token can change, so you need to handle the token refresh event and update the target with Appwrite Messaging. Implement `didReceiveRegistrationToken`, which is called when the FCM token is updated. ```swift func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { /* store the fcmToken */ guard let fcmToken = fcmToken else { return } Task { do { _ = try await account.get() try await account.createPushTarget(targetId: ID.unique(), identifier: fcmToken) } catch { print("Failed to create push target: \(error.localizedDescription)") } } } ``` Since the token is saved in `UserDefaults`, you can access it from anywhere in your app. With this saved `fcmToken`, you can create a push target with Appwrite when the user logs in. Each push target is associated with an account, here's an example with an email password login. The same logic applies to all types of login methods. ```swift func login() async { do { let session = try await account.createEmailPasswordSession(email: username, password: password) let token = /* Retrieve stored push token */ let target = try await account.createPushTarget(targetId: ID.unique(), identifier: token) } catch { print("Login failed: \(error.localizedDescription)") } } ``` If you have disabled method swizzling, or you are building a SwiftUI app, you'll need to explicitly map your APNs token to the FCM registration token. Implement the `didRegisterForRemoteNotificationsWithDeviceToken` method to get the device token and save it to FCM. ```swift func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { Messaging.messaging().apnsToken = deviceToken } ``` {% /tabsitem %} {% /tabs %} {% /section %} {% section #request-permissions step=3 title="Request permissions" %} Your app must ask for permission to receive push notification from the user. {% tabs %} {% tabsitem #apple-apns title="Apple with APNs" %} Before your app can receive push notifications, you need to request the user for permissions. Appwrite provides a utility to help request permissions to display notificaitons. You can learn more about requesting permissions from the [Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications). {% /tabsitem %} {% tabsitem #android-fcm title="FCM for Android" %} First, add `POST_NOTIFICATIONS` to your `AndroidManifest.xml`. ```xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="YOUR_PACKAGE"> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- ... rest of your manifest --> ``` Then, you'll also need to request [runtime permissions](https://developer.android.com/training/permissions/requesting) from your users using the `android.permission.POST_NOTIFICATIONS` permission. {% /tabsitem %} {% tabsitem #apple-fcm title="FCM for Apple" %} Before your app can receive push notifications, you need to request the user for permissions. Appwrite provides a utility to help request permissions to display notificaitons. You can learn more about requesting permissions from the [Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications). When an FCM registration token is generated, the library uploads the identifier and configuration data to Firebase. If you wish to give your users the ability to explicitly opt out of sending data to Firebase, you can disable automatic initialization and manually initialize the library when the user grants permission. Disable auto-initialization by setting `FirebaseMessagingAutoInitEnabled` to `NO` in your `Info.plist`. ```text FirebaseMessagingAutoInitEnabled = NO ``` Then, manually initialize the library when the user grants permission. ``` swift Messaging.messaging().autoInitEnabled = true ``` {% /tabsitem %} {% /tabs %} {% /section %} {% section #send-message step=4 title="Send message" %} You can send messages in both the Appwrite Console and programmatically using the Appwrite Server SDK. {% info title="Sandbox" %} If you enabled **Sandbox** on your APNs provider, Appwrite will send push notifications to the development APNs environment. This requires you to use a **Development profile** in XCode. If XCode is not default to a development profile, click your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > uncheck **Automatically manage signing**. Then manually select a **Provisioning profile** that is a **Distribution profile**. {% /info %} {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **Push notification**. {% only_dark %} ![Create email message](/images/docs/messaging/messages/dark/create-push-notification.png) {% /only_dark %} {% only_light %} ![Create email message](/images/docs/messaging/messages/create-push-notification.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createPush({ messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: {}, // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: '<BADGE>', // optional draft: false, // optional scheduledAt: '' // optional }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createPush({ messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: {}, // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: '<BADGE>', // optional draft: false, // optional scheduledAt: '' // optional }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createPush( messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: [], // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: '<BADGE>', // optional draft: false, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_push( message_id = '<MESSAGE_ID>', title = '<TITLE>', body = '<BODY>', topics = [], # optional users = [], # optional targets = [], # optional data = {}, # optional action = '<ACTION>',# optional icon = '<ICON>', # optional sound = '<SOUND>', # optional color = '<COLOR>', # optional tag = '<TAG>', # optional badge = '<BADGE>', # optional draft = False, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_push( message_id: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], # optional users: [], # optional targets: [], # optional data: {}, # optional action: '<ACTION>', # optional icon: '<ICON>', # optional sound: '<SOUND>', # optional color: '<COLOR>', # optional tag: '<TAG>', # optional badge: '<BADGE>', # optional draft: false, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreatePush( messageId: "[MESSAGE_ID]", title: "[TITLE]", body: "[BODY]" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional data: [object] // optional action: "[ACTION]" // optional icon: "[ICON]" // optional sound: "[SOUND]" // optional color: "[COLOR]" // optional tag: "[TAG]" // optional badge: "[BADGE]" // optional draft: false // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createPush( messageId: '<MESSAGE_ID>', title: '<TITLE>', body: '<BODY>', topics: [], // optional users: [], // optional targets: [], // optional data: {}, // optional action: '<ACTION>', // optional icon: '<ICON>', // optional sound: '<SOUND>', // optional color: '<COLOR>', // optional tag: '<TAG>', // optional badge: '<BADGE>', // optional draft: false, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Messaging val client = Client(context) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val messaging = Messaging(client) val response = messaging.createPush( messageId = "<MESSAGE_ID>", title = "<TITLE>", body = "<BODY>", topics = listOf(), // optional users = listOf(), // optional targets = listOf(), // optional data = mapOf( "a" to "b" ), // optional action = "<ACTION>", // optional icon = "<ICON>", // optional sound = "<SOUND>", // optional color = "<COLOR>", // optional tag = "<TAG>", // optional badge = "<BADGE>", // optional draft = false, // optional scheduledAt = "" // optional ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createPush( "<MESSAGE_ID>", // messageId "<TITLE>", // title "<BODY>", // body listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) mapOf( "a" to "b" ), // data (optional) "<ACTION>", // action (optional) "<ICON>", // icon (optional) "<SOUND>", // sound (optional) "<COLOR>", // color (optional) "<TAG>", // tag (optional) "<BADGE>", // badge (optional) false, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createPush( messageId: "<MESSAGE_ID>", title: "<TITLE>", body: "<BODY>", topics: [], // optional users: [], // optional targets: [], // optional data: [:], // optional action: "<ACTION>", // optional icon: "<ICON>", // optional sound: "<SOUND>", // optional color: "<COLOR>", // optional tag: "<TAG>", // optional badge: "<BADGE>", // optional draft: false, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Send SMS messages https://appwrite.io/docs/products/messaging/send-sms-messages You can send custom SMS messages to your app's users using Appwrite Messaging and a connected SMTP service. This guide takes you through the implementation path of adding SMS messaging to your app. ### Add a provider {% #add-a-provider %} Appwrite supports [Twilio](/docs/products/messaging/twilio/), [MSG91](/docs/products/messaging/msg91/), [Telesign](/docs/products/messaging/telesign/), [Textmagic](/docs/products/messaging/textmagic/), and [Vonage](/docs/products/messaging/vonage/) as SMS providers. You must configure one of them as a provider. {% only_dark %} ![Add a SMTP provider](/images/docs/messaging/providers/twilio/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a SMTP provider](/images/docs/messaging/providers/twilio/provider.png) {% /only_light %} To add a new provider navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS** and follow the wizard. You can find more details about configuring in the provider guides for [Twilio](/docs/products/messaging/twilio/), [MSG91](/docs/products/messaging/msg91/), [Telesign](/docs/products/messaging/telesign/), [Textmagic](/docs/products/messaging/textmagic/), and [Vonage](/docs/products/messaging/vonage/). ### Add targets {% #add-targets %} In Appwrite Messaging, each user has **targets** like their email, phone number, and devices with your app installed. You can deliver messages to users through their **targets**. {% only_dark %} ![Target overview](/images/docs/messaging/targets/dark/target-overview.png) {% /only_dark %} {% only_light %} ![Target overview](/images/docs/messaging/targets/target-overview.png) {% /only_light %} If the user signed up with phone (SMS) authentication, their account would already have a phone number as a target. During development, you can add targets to existing accounts by navigating to **Authentication** > **Users** > **Select a user** > **Targets** > **Add a subscriber**. {% only_dark %} ![Add a target](/images/docs/messaging/targets/dark/add-targets.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/targets/add-targets.png) {% /only_light %} You can also implement forms in your app to collect contact information and add it as a target with the [createSubscriber](/docs/references/cloud/server-nodejs/messaging#createSubscriber) endpoint. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const users = new sdk.Users(client); const target = await users.createTarget( '<USER_ID>', // userId '<TARGET_ID>', // targetId sdk.MessagingProviderType.Phone, // providerType '<IDENTIFIER>', // identifier '<PROVIDER_ID>', // providerId (optional) '<NAME>' // name (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token const users = new sdk.Users(client); const target = await users.createTarget( '<USER_ID>', // userId '<TARGET_ID>', // targetId sdk.MessagingProviderType.Phone, // providerType '<IDENTIFIER>', // identifier '<PROVIDER_ID>', // providerId (optional) '<NAME>' // name (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Users; use Appwrite\Enums\MessagingProviderType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key $users = new Users($client); $target = $users->createTarget( userId: '<USER_ID>', targetId: '<TARGET_ID>', providerType: MessagingProviderType::EMAIL(), identifier: '<IDENTIFIER>', providerId: '<PROVIDER_ID>', // optional name: '<NAME>' // optional ); ``` ```python from appwrite.client import Client from appwrite.enums import MessagingProviderType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key users = Users(client) target = users.create_target( user_id = '<USER_ID>', target_id = '<TARGET_ID>', provider_type = MessagingProviderType.PHONE, identifier = '<IDENTIFIER>', provider_id = '<PROVIDER_ID>', # optional name = '<NAME>' # optional ) ``` ```ruby require 'appwrite' include Appwrite include Appwrite::Enums client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key users = Users.new(client) target = users.create_target( user_id: '<USER_ID>', target_id: '<TARGET_ID>', provider_type: MessagingProviderType::EMAIL, identifier: '<IDENTIFIER>', provider_id: '<PROVIDER_ID>', # optional name: '<NAME>' # optional ) puts target.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Users users = new Users(client); Target target = await users.CreateTarget( userId: "<USER_ID>", targetId: "<TARGET_ID>", providerType: MessagingProviderType.Phone, identifier: "<IDENTIFIER>", providerId: "<PROVIDER_ID>", // optional name: "<NAME>" // optional ); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key Users users = Users(client); Target target = await users.createTarget( userId: '<USER_ID>', targetId: '<TARGET_ID>', providerType: MessagingProviderType.phone, identifier: '<IDENTIFIER>', providerId: '<PROVIDER_ID>', // (optional) name: '<NAME>', // (optional) ); ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Users import io.appwrite.enums.MessagingProviderType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val users = Users(client) val target = users.createTarget( userId = "<USER_ID>", targetId = "<TARGET_ID>", providerType = MessagingProviderType.PHONE, identifier = "<IDENTIFIER>", providerId = "<PROVIDER_ID>", // optional name = "<NAME>" // optional ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Users; import io.appwrite.enums.MessagingProviderType; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Users users = new Users(client); users.createTarget( "<USER_ID>", // userId "<TARGET_ID>", // targetId MessagingProviderType.PHONE, // providerType "<IDENTIFIER>", // identifier "<PROVIDER_ID>", // providerId (optional) "<NAME>", // name (optional) new CoroutineCallback<>((target, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(target); }) ); ``` ```swift import Appwrite import AppwriteEnums let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let users = Users(client) let target = try await users.createTarget( userId: "<USER_ID>", targetId: "<TARGET_ID>", providerType: .phone, identifier: "<IDENTIFIER>", providerId: "<PROVIDER_ID>", // optional name: "<NAME>" // optional ) ``` {% /multicode %} ### Create topics (optional) {% #create-topics %} You can use topics to organize targets that should receive the same messages, so you can send SMS messages to groups of targets instead of one at time. This step is optional if you plan to only send SMS messages to individual targets. To create a topic in the Appwrite Console, navigate to **Messaging** > **Topics** > {% icon icon="plus" size="m" /%} **Create topic**. {% only_dark %} ![Add a target](/images/docs/messaging/topics/dark/create-topics.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/topics/create-topics.png) {% /only_light %} You can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const topic = await messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>' // name ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const topic = await messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>' // name ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key $messaging = new Messaging($client); $result = $messaging->createTopic( topicId: '<TOPIC_ID>', name: '<NAME>' ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging(client) topic = messaging.create_topic( topic_id = '<TOPIC_ID>', name = '<NAME>' ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) topic = messaging.create_topic( topic_id: '<TOPIC_ID>', name: '<NAME>' ) ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); Topic topic = await messaging.CreateTopic( topicId: "<TOPIC_ID>", name: "<NAME>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key Messaging messaging = Messaging(client); Topic topic = await messaging.createTopic( topicId: '<TOPIC_ID>', name: '<NAME>', ); ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; val client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val messaging = new Messaging(client) val topic = messaging.createTopic( topicId = "<TOPIC_ID>", name = "<NAME>" ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>", // name new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let topic = try await messaging.createTopic( topicId: "<TOPIC_ID>", name: "<NAME>" ) ``` {% /multicode %} ### Send SMS messages {% #send-sms %} You can send SMS messages using a Server SDK. To send an SMS messages immediately, you can call the `createSms` endpoint without passing either the `draft` or `scheduledAt` parameters. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) false, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) false, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = false, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: false, # optional scheduled_at: '' # optional ) ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); Message message = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: false, // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client(); .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key Messaging messaging = Messaging(client); Message message result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: '' // optional ); ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val messaging = Messaging(client) val message - await messaging.createSms( messageId = "<MESSAGE_ID>", content = "<CONTENT>", topics = listOf(), users = listOf(), targets = listOf(), draft = false, scheduledAt = "" ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) false, // draft (optional) "", // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: "" // optional ) ``` {% /multicode %} ### Schedule SMS message {% #schedule-sms %} To send an scheduled SMS message, you can call the `createSms` endpoint with `scheduledAt` as a ISO 8601 date time string for the scheduled time. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) false, // draft (optional) '2025-02-13T22:01:00+0000' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) false, // draft (optional) '2025-02-13T22:01:00+0000' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: '2025-02-13T22:01:00+0000' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = false, # optional scheduled_at = '2025-02-13T22:01:00+0000' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: false, # optional scheduled_at: '2025-02-13T22:01:00+0000' # optional ) ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); Message message = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: false, // optional scheduledAt: "2025-02-13T22:01:00+0000"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client(); .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key Messaging messaging = Messaging(client); Message message result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: '2025-02-13T22:01:00+0000' // optional ); ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val messaging = Messaging(client) val message - await messaging.createSms( messageId = "<MESSAGE_ID>", content = "<CONTENT>", topics = listOf(), users = listOf(), targets = listOf(), draft = false, scheduledAt = "2025-02-13T22:01:00+0000" ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) false, // draft (optional) "2025-02-13T22:01:00+0000", // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: false, // optional scheduledAt: "2025-02-13T22:01:00+0000" // optional ) ``` {% /multicode %} --- ## SendGrid https://appwrite.io/docs/products/messaging/sendgrid SendGrid lets you send customized email messages to your users. These emails can be sent immediately or scheduled. You can send emails for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add SendGrid as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **Email**. {% only_dark %} ![Add a SMTP provider](/images/docs/messaging/providers/sendgrid/dark/add-sendgrid.png) {% /only_dark %} {% only_light %} ![Add a SMTP provider](/images/docs/messaging/providers/sendgrid/add-sendgrid.png) {% /only_light %} Give your provider a name > choose **SendGrid** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your SendGrid dashboard to connect your Appwrite project. {% only_dark %} ![Configure SMTP provider](/images/docs/messaging/providers/sendgrid/dark/configure-sendgrid.png) {% /only_dark %} {% only_light %} ![Configure SMTP provider](/images/docs/messaging/providers/sendgrid/configure-sendgrid.png) {% /only_light %} You will need to provide the following information from your **SendGrid dashboard**. {% table %} * Field name * --- * API key * Head to Settings -> API Keys -> Create API Key. --- * Sender email * The provider sends emails from this sender email. The sender email must either be an email under an [authenticated domain](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/how-to-set-up-domain-authentication) or a [verified sender identity](https://www.twilio.com/docs/sendgrid/ui/sending-email/sender-verification). --- * Sender name * The sender name that appears in the emails sent from this provider. --- * Reply-to email * The reply-to email that appears in the emails sent from this provider. The reply-to email must either be an email under an authenticated domain or a verified sender identity. --- * Reply-to name * The reply-to name that appears in the emails sent from this provider. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **Email**. {% only_dark %} ![Create email message](/images/docs/messaging/messages/dark/create-email-message.png) {% /only_dark %} {% only_light %} ![Create email message](/images/docs/messaging/messages/create-email-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; const message = await messaging.createEmail({ messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>' }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; const message = await messaging.createEmail({ messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>' }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('<API_KEY>') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>'); ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>') ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email(message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; using Appwrite.Enums; using Appwrite.Enums; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("<API_KEY>"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; Future result = await messaging.createEmail( messageId:'<MESSAGE_ID>' , subject:'<SUBJECT>' , content:'<CONTENT>' , ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Messaging val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key val messaging = Messaging(client) val response = messaging.createEmail( messageId = "<MESSAGE_ID>", subject = "<SUBJECT>", content = "<CONTENT>", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", "<SUBJECT>", "<CONTENT>", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>" ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send email messages](/docs/products/messaging/send-email-messages) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>') // Your secret API key ; const provider = await messaging.updateSendgridProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) '<API_KEY>', // apiKey (optional) false, // enabled (optional) '<FROM_NAME>', // fromName (optional) 'email@example.com', // fromEmail (optional) '<REPLY_TO_NAME>', // replyToName (optional) '<REPLY_TO_EMAIL>' // replyToEmail (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>') // Your secret API key ; const provider = await messaging.updateSendgridProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) '<API_KEY>', // apiKey (optional) false, // enabled (optional) '<FROM_NAME>', // fromName (optional) 'email@example.com', // fromEmail (optional) '<REPLY_TO_NAME>', // replyToName (optional) '<REPLY_TO_EMAIL>' // replyToEmail (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('<YOUR_API_KEY>') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateSendgridProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional apiKey: '<API_KEY>', // optional enabled: false, // optional fromName: '<FROM_NAME>', // optional fromEmail: 'email@example.com', // optional replyToName: '<REPLY_TO_NAME>', // optional replyToEmail: '<REPLY_TO_EMAIL>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key ) messaging = Messaging(client) result = messaging.update_sendgrid_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional api_key = '<API_KEY>', # optional enabled = False, # optional from_name = '<FROM_NAME>', # optional from_email = 'email@example.com', # optional reply_to_name = '<REPLY_TO_NAME>', # optional reply_to_email = '<REPLY_TO_EMAIL>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key messaging = Messaging.new(client) response = messaging.update_sendgrid_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional api_key: '<API_KEY>', # optional enabled: false, # optional from_name: '<FROM_NAME>', # optional from_email: 'email@example.com', # optional reply_to_name: '<REPLY_TO_NAME>', # optional reply_to_email: '<REPLY_TO_EMAIL>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("<YOUR_API_KEY>"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateSendgridProvider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional apiKey: "<API_KEY>" // optional enabled: false // optional fromName: "<FROM_NAME>" // optional fromEmail: "email@example.com" // optional replyToName: "<REPLY_TO_NAME>" // optional replyToEmail: "<REPLY_TO_EMAIL>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>') // Your secret API key ; Future result = messaging.updateSendgridProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional apiKey: '<API_KEY>', // optional enabled: false, // optional fromName: '<FROM_NAME>', // optional fromEmail: 'email@example.com', // optional replyToName: '<REPLY_TO_NAME>', // optional replyToEmail: '<REPLY_TO_EMAIL>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateSendgridProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) "<API_KEY>", // apiKey (optional) false, // enabled (optional) "<FROM_NAME>", // fromName (optional) "email@example.com", // fromEmail (optional) "<REPLY_TO_NAME>", // replyToName (optional) "<REPLY_TO_EMAIL>" // replyToEmail (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateSendgridProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) "<API_KEY>", // apiKey (optional) false, // enabled (optional) "<FROM_NAME>", // fromName (optional) "email@example.com", // fromEmail (optional) "<REPLY_TO_NAME>", // replyToName (optional) "<REPLY_TO_EMAIL>" // replyToEmail (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateSendgridProvider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional apiKey: "<API_KEY>", // optional enabled: xfalse, // optional fromName: "<FROM_NAME>", // optional fromEmail: "email@example.com", // optional replyToName: "<REPLY_TO_NAME>", // optional replyToEmail: "<REPLY_TO_EMAIL>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## SMTP https://appwrite.io/docs/products/messaging/smtp If you wish to use a third-party SMTP provider that Appwrite doesn't yet support or host your own SMTP server, you can setup a custom SMTP provider for your project. {% section #add-provider step=1 title="Add provider" %} To add a custom SMTP server as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **Email**. {% only_dark %} ![Add a SMTP provider](/images/docs/messaging/providers/smtp/dark/add-smtp.png) {% /only_dark %} {% only_light %} ![Add a SMTP provider](/images/docs/messaging/providers/smtp/add-smtp.png) {% /only_light %} Give your provider a name > choose **SMTP** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your SMTP dashboard to connect your Appwrite project. You will need to provide the following information from your **SMTP dashboard**. {% table %} * Field name * Description --- * Host * The server address of the SMTP provider. --- * Port * The port used for SMTP connections. --- * Username * Your SMTP provider account username. --- * Password * Your SMTP provider account password. --- * Encryption * The type of encryption used. One of SSL or TLS. --- * Auto TLS * Automatically uses TLS encryption if available. --- * Mailer * The SMTP server or provider. --- * Sender email * The provider sends emails from this sender email. The sender email needs to be an email under the configured domain. --- * Sender name * The sender name that appears in the emails sent from this provider. --- * Reply-to email * The reply-to email that appears in the emails sent from this provider. The reply-to email needs to be an email under the configured domain. --- * Reply-to name * The reply-to name that appears in the emails sent from this provider. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **Email**. {% only_dark %} ![Create email message](/images/docs/messaging/messages/dark/create-email-message.png) {% /only_dark %} {% only_light %} ![Create email message](/images/docs/messaging/messages/create-email-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; const message = await messaging.createEmail({ messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>' }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; const message = await messaging.createEmail({ messageId: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>' }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('<API_KEY>') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createEmail('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>'); ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key ) messaging = Messaging(client) result = messaging.create_email('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>') ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key messaging = Messaging.new(client) response = messaging.create_email(message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; using Appwrite.Enums; using Appwrite.Enums; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("<API_KEY>"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; Future result = await messaging.createEmail( messageId:'<MESSAGE_ID>' , subject:'<SUBJECT>' , content:'<CONTENT>' , ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Messaging val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key val messaging = Messaging(client) val response = messaging.createEmail( messageId = "<MESSAGE_ID>", subject = "<SUBJECT>", content = "<CONTENT>", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createEmail( "<MESSAGE_ID>", "<SUBJECT>", "<CONTENT>", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createEmail( messageId: "<MESSAGE_ID>", subject: "<SUBJECT>", content: "<CONTENT>" ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send email messages](/docs/products/messaging/send-push-notifications) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; // update provider messaging.updateSendgridProvider( '<PROVIDER_ID>', '<PROVIDER_NAME>', '<API_KEY>', '<DOMAIN>', '<IS_EU_REGION?>', '<SENDER_NAME>', '<SENDER_EMAIL>', '<REPLY_TO_NAME>', '<REPLY_TO_EMAIL>', '<ENABLED?>', ).then(function (response) { console.log(response); }, function (error) { console.log(error); }); // delete provider messaging.deleteProvider('<PROVIDER_ID>') .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); // update provider messaging.updateSendgridProvider( '<PROVIDER_ID>', '<PROVIDER_NAME>', '<API_KEY>', '<DOMAIN>', '<IS_EU_REGION?>', '<SENDER_NAME>', '<SENDER_EMAIL>', '<REPLY_TO_NAME>', '<REPLY_TO_EMAIL>', '<ENABLED?>', ).then(function (response) { console.log(response); }, function (error) { console.log(error); }); // delete provider messaging.deleteProvider('<PROVIDER_ID>') .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('<API_KEY>') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateSendgridProvider( '<PROVIDER_ID>', '<PROVIDER_NAME>', '<API_KEY>', '<DOMAIN>', '<IS_EU_REGION?>', '<SENDER_NAME>', '<SENDER_EMAIL>', '<REPLY_TO_NAME>', '<REPLY_TO_EMAIL>', '<ENABLED?>', ); ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key ) messaging = Messaging(client) result = messaging.update_sendgrid_provider( '<PROVIDER_ID>', '<PROVIDER_NAME>', '<API_KEY>', '<DOMAIN>', '<IS_EU_REGION?>', '<SENDER_NAME>', '<SENDER_EMAIL>', '<REPLY_TO_NAME>', '<REPLY_TO_EMAIL>', '<ENABLED?>', ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('<API_KEY>') # Your secret API key messaging = Messaging.new(client) response = messaging.update_sendgrid_provider( provider_id: "<PROVIDER_ID>", name: "<PROVIDER_NAME>", api_key: "<API_KEY>", domain: "<DOMAIN>", isEuRegion: "<IS_EU_REGION?>", from_name: "<SENDER_NAME>", from_email: "<SENDER_EMAIL>", reply_to_name: "<REPLY_TO_NAME>", reply_to_email: "<REPLY_TO_EMAIL>", enabled: "<ENABLED?>", ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; using Appwrite.Enums; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("<API_KEY>"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateSendgridProvider( providerId: "<PROVIDER_ID>", name: "<PROVIDER_NAME>", apiKey: "<API_KEY>", domain: "<DOMAIN>", isEuRegion: "<IS_EU_REGION?>", fromName: "<SENDER_NAME>", fromEmail: "<SENDER_EMAIL>", replyToName: "<REPLY_TO_NAME>", replyToEmail: "<REPLY_TO_EMAIL>", enabled: "<ENABLED?>", ); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('<API_KEY>') // Your secret API key ; Future result = messaging.updateSendgridProvider( providerId: "<PROVIDER_ID>", name: "<PROVIDER_NAME>", apiKey: "<API_KEY>", domain: "<DOMAIN>", isEuRegion: "<IS_EU_REGION?>", fromName: "<SENDER_NAME>", fromEmail: "<SENDER_EMAIL>", replyToName: "<REPLY_TO_NAME>", replyToEmail: "<REPLY_TO_EMAIL>", enabled: "<ENABLED?>", ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Messaging val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key val messaging = Messaging(client) val response = messaging.updateSendgridProvider( providerId = "<PROVIDER_ID>", name = "<PROVIDER_NAME>", apiKey = "<API_KEY>", domain = "<DOMAIN>", isEuRegion = "<IS_EU_REGION?>", fromName = "<SENDER_NAME>", fromEmail = "<SENDER_EMAIL>", replyToName = "<REPLY_TO_NAME>", replyToEmail = "<REPLY_TO_EMAIL>", enabled = "<ENABLED?>", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateSendgridProvider( "<PROVIDER_ID>", "<PROVIDER_NAME>", "<API_KEY>", "<DOMAIN>", "<IS_EU_REGION?>", "<SENDER_NAME>", "<SENDER_EMAIL>", "<REPLY_TO_NAME>", "<REPLY_TO_EMAIL>", "<ENABLED?>", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("<API_KEY>") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateSendgridProvider( providerId: "<PROVIDER_ID>", name: "<PROVIDER_NAME>", apiKey: "<API_KEY>", domain: "<DOMAIN>", isEuRegion: "<IS_EU_REGION?>", fromName: "<SENDER_NAME>", fromEmail: "<SENDER_EMAIL>", replyToName: "<REPLY_TO_NAME>", replyToEmail: "<REPLY_TO_EMAIL>", enabled: "<ENABLED?>", ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Targets https://appwrite.io/docs/products/messaging/targets Targets are different ways a user can be reached. For example, a user might have two emails, a phone number as well as a phone and a tablet with your app installed. This means, the user has five different targets that you can deliver messages to. {% only_dark %} ![Target overview](/images/docs/messaging/targets/dark/target-overview.png) {% /only_dark %} {% only_light %} ![Target overview](/images/docs/messaging/targets/target-overview.png) {% /only_light %} ### Topics and targets {% #topics-and-targets %} A user can have multiple targets, such as emails, phone numbers, and devices with your app installed. These targets can subscribe to a topic, so when messages are published to a topic, all subscribed targets receive the message. {% arrow_link href="/docs/products/messaging/topics" %} Learn more about topics {% /arrow_link %} ### Types of targets {% #types-of-targets %} There are three types of targets you can use to reach your targets. {% table %} * Target Type * Description --- * **Email** * Allows you to send emails to the user's email. --- * **SMS** * Allows you to send SMS messages to the user's phone. --- * **Push notification** * Allows you to send push notifications to the user's device. {% /table %} ### Add a target {% #add-a-target %} Before you can send messages, make sure you have the appropriate targets added for your user. #### Add email target {% #add-email-target%} Verified emails for users that signed up with [email password](/docs/products/auth/email-password), [magic URL](/docs/products/auth/magic-url), and [email OTP](/docs/products/auth/email-otp) login will already have an email target. #### Add SMS target {% #add-sms-target%} Verified phone numbers for users that signed up with [Phone OTP](/docs/products/auth/phone-sms) login will already have a phone target. #### Add push notification target {% #add-push-notification-target%} Push notifications require configuration on both the Appwrite platform and your client app's code. {% tabs %} {% tabsitem #fcm-ios title="iOS with FCM" %} 1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **iOS** app. 1. Register and download your `google-services.json` config file. 1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled. 1. Create a new key, note down the key ID and download your key. 1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here. 1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. 1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`. {% only_dark %}![Enable Push Notification in XCode](/images/docs/messaging/targets/dark/xcode-enable-pn.png){% /only_dark %}{% only_light %}![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.png){% /only_light %} {% /tabsitem %} {% tabsitem #fcm-android title="Android with FCM" %} 1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **Android** app. 1. Register and download your `google-services.json` config file. 1. Add `google-services.json` at the root of your project. 1. Add Google Services class path to your app-level Gradle dependencies block `"com.google.gms:google-services:4.4.0"`. 1. Add Google Services plugin to your app-level Gradle in the plugins block as `"com.google.gms.google-services"`. 1. Add notification handler service to `AndroidManifest.xml` inside the application tag, alongside other activities. Find an example of this service in the [Send push notification](/docs/products/messaging/send-push-notifications#add-targets) journey. ```xml <service android:name="<YOUR_NOTIFICATION_HANDLER_SERVICE>" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> ``` {% /tabsitem %} {% tabsitem #APNs-ios title="iOS with APNs" %} 1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled. 1. Create a new key, note down the key ID and download your key. 1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon="plus" size="m" /%} Capabilities > Search for **Push Notifications**. {% only_dark %} ![Enable Push Notification in XCode](/images/docs/messaging/targets/dark/xcode-enable-pn.png) {% /only_dark %} {% only_light %} ![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.png) {% /only_light %} {% /tabsitem %} {% /tabs %} --- ## Telesign https://appwrite.io/docs/products/messaging/telesign Telesign lets you send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add Telesign as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS**. {% only_dark %} ![Add a Telesign provider](/images/docs/messaging/providers/telesign/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a Telesign provider](/images/docs/messaging/providers/telesign/provider.png) {% /only_light %} Give your provider a name > choose **Telesign** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Telesign dashboard to connect your Appwrite project. You will need to provide the following information from your **Telesign dashboard**. {% table %} * Field name * --- * Customer ID * Head to **Telesign portal** > **Profile** > **Customer ID**. --- * API Key * Head to **Telesign portal** > **Profile** > **API Keys**. --- * Sender number * The number from which the SMS will be sent. You may need to first purchase a number from Telesign. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **SMS**. {% only_dark %} ![Create SMS message](/images/docs/messaging/messages/dark/create-sms-message.png) {% /only_dark %} {% only_light %} ![Create SMS message](/images/docs/messaging/messages/create-sms-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const messaging = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const messaging = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTelesignProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<USERNAME>', // username (optional) '<PASSWORD>', // password (optional) '<FROM>' // from (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTelesignProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<USERNAME>', // username (optional) '<PASSWORD>', // password (optional) '<FROM>' // from (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateTelesignProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional username: '<USERNAME>', // optional password: '<PASSWORD>', // optional from: '<FROM>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_telesign_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional enabled = False, # optional username = '<USERNAME>', # optional password = '<PASSWORD>', # optional from = '<FROM>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_telesign_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional enabled: false, # optional username: '<USERNAME>', # optional password: '<PASSWORD>', # optional from: '<FROM>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateTelesignProvider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional enabled: false // optional username: "<USERNAME>" // optional password: "<PASSWORD>" // optional from: "<FROM>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.updateTelesignProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional username: '<USERNAME>', // optional password: '<PASSWORD>', // optional from: '<FROM>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTelesignProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<USERNAME>", // username (optional) "<PASSWORD>", // password (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTelesignProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<USERNAME>", // username (optional) "<PASSWORD>", // password (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateTelesignProvider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional enabled: xfalse, // optional username: "<USERNAME>", // optional password: "<PASSWORD>", // optional from: "<FROM>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Textmagic https://appwrite.io/docs/products/messaging/textmagic Textmagic lets you send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add Textmagic as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS**. {% only_dark %} ![Add a Textmagic provider](/images/docs/messaging/providers/textmagic/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a Textmagic provider](/images/docs/messaging/providers/textmagic/provider.png) {% /only_light %} Give your provider a name > choose **Textmagic** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Textmagic dashboard to connect your Appwrite project. You will need to provide the following information from your **Textmagic dashboard**. {% table %} * Field name * --- * API key * Head to Textmagic dashboard > **Services** > **API** > **Add new API key**. --- * Username * Head to Textmagic dashboard > **My account** > **Username**. --- * Sender number * Head to Textmagic dashboard > **Services** > **Sender settings**. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **SMS**. {% only_dark %} ![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.png) {% /only_dark %} {% only_light %} ![Create an SMS message](/images/docs/messaging/messages/create-sms-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const messaging = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const messaging = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTextmagicProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<USERNAME>', // username (optional) '<API_KEY>', // apiKey (optional) '<FROM>' // from (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTextmagicProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<USERNAME>', // username (optional) '<API_KEY>', // apiKey (optional) '<FROM>' // from (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateTextmagicProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional username: '<USERNAME>', // optional apiKey: '<API_KEY>', // optional from: '<FROM>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_textmagic_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional enabled = False, # optional username = '<USERNAME>', # optional api_key = '<API_KEY>', # optional from = '<FROM>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_textmagic_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional enabled: false, # optional username: '<USERNAME>', # optional api_key: '<API_KEY>', # optional from: '<FROM>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateTextmagicProvider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional enabled: false // optional username: "<USERNAME>" // optional apiKey: "<API_KEY>" // optional from: "<FROM>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.updateTextmagicProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional username: '<USERNAME>', // optional apiKey: '<API_KEY>', // optional from: '<FROM>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTextmagicProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<USERNAME>", // username (optional) "<API_KEY>", // apiKey (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTextmagicProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<USERNAME>", // username (optional) "<API_KEY>", // apiKey (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateTextmagicProvider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional enabled: xfalse, // optional username: "<USERNAME>", // optional apiKey: "<API_KEY>", // optional from: "<FROM>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Topics https://appwrite.io/docs/products/messaging/topics In Appwrite Messaging, you can use topics to deliver messages to groups of users at once. {% only_dark %} ![Add a target](/images/docs/messaging/topics/dark/topics.png) {% /only_dark %} {% only_light %} ![Add a target](/images/docs/messaging/topics/topics.png) {% /only_light %} ### Topics and targets {% #topics-and-targets %} A user can have multiple targets, such as emails, phone numbers, and devices with your app installed. These targets can subscribe to a topic, so when messages are published to a topic, all subscribed targets receive the message. {% arrow_link href="/docs/products/messaging/targets" %} Learn more about targets {% /arrow_link %} ### Organizing topics {% #organizing-topics %} A topic should have semantic meaning. For example, a topic can represent a group of customers that receiving a common announcemennt or publishing public updates. It's important to keep privacy in mind when using topics. Prefer sending private information like chat messages by addressing individual targets attached to a user. Topics are optimized for delivering the same message to large groups of users. If you need to deliver messages to **all devices of the same user**, you can find a user's targets by calling `account.get()`. ### Create a topic {% #create-a-topic %} You can create topics {% tabs %} {% tabsitem #console title="Console" %} {% only_dark %} ![Add a topic](/images/docs/messaging/topics/dark/create-topics.png) {% /only_dark %} {% only_light %} ![Add a topic](/images/docs/messaging/topics/create-topics.png) {% /only_light %} Navigate to your Appwrite Console > **Messaging** > **Topics** > **Create topic**. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const topic = messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>', // name '<ROLES>' // permission roles for who can subscribe ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const topic = messaging.createTopic({ topicId: '<TOPIC_ID>', name: '<NAME>', subscribe: '<ROLES>' // permission roles for who can subscribe }); const topic = messaging.createTopic( '<TOPIC_ID>', // topicId '<NAME>', // name '<ROLES>' // permission roles for who can subscribe ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $topic = $messaging->createTopic( topicId: '<TOPIC_ID>', name: '<NAME>', subscribe: '<ROLES>' // permission roles for who can subscribe ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) topic = messaging.create_topic( topic_id = '<TOPIC_ID>', name = '<NAME>', subscribe = '<ROLES>' # permission roles for who can subscribe ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) topic = messaging.create_topic( topic_id: '<TOPIC_ID>', name: '<NAME>', subscribe: '<ROLES>' # permission roles for who can subscribe ) puts topic.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Topic topic = await messaging.CreateTopic( topicId: "<TOPIC_ID>", name: "<NAME>", subscribe: "<ROLES>") // permission roles for who can subscribe ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createTopic( topicId: '<TOPIC_ID>', name: '<NAME>', subscribe: '<ROLES>' // permission roles for who can subscribe ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>" // name "<ROLES>" // permission roles for who can subscribe new CoroutineCallback<>((topic, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(topic); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>" // name "<ROLES>" // permission roles for who can subscribe new CoroutineCallback<>((topic, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(topic); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let topic = try await messaging.createTopic( topicId: "<TOPIC_ID>", name: "<NAME>", subscribe: "<ROLES>" // permission roles for who can subscribe ) ``` {% /multicode %} {% /tabsitem %} {% tabsitem #cli title="CLI" %} {% partial file="cli-disclaimer.md" /%} You can create a topic using the CLI command `appwrite init topics` to initialize a topic. ```sh appwrite init topics ``` You can now push your topics with the following command: ```sh appwrite push topics ``` This will create your topic in the Console with all of your `appwrite.config.json` configurations. {% arrow_link href="/docs/tooling/command-line/topics#commands" %} Learn more about the CLI topics commands {% /arrow_link %} {% /tabsitem %} {% /tabs %} ### Permissions {% #permissions %} Before you can subscribe to a topic, a user needs the appropriate permission. You can set permission by navigating to **Messaging** > **Topics** > select a topic to configure > **Subscription access**. {% arrow_link href="/docs/advanced/platform/permissions#permission-roles" %} Learn more about permission roles {% /arrow_link %} ### Subscribe targets to a topic {% #subscribe-targets-to-topics %} {% tabs %} {% tabsitem #console title="Console" %} During development, you can subscribe targets to a topic for testing right in the Appwrite console. {% only_dark %} ![Add a topic](/images/docs/messaging/topics/dark/add-subscriber.png) {% /only_dark %} {% only_light %} ![Add a topic](/images/docs/messaging/topics/add-subscriber.png) {% /only_light %} Navigate to your Appwrite Console > **Messaging** > **Topics** > click on your topic > **Subscribers** > **Create topic** > **Add subscriber**. If you can't find the targets you'd like to add, see the [targets page](/docs/products/messaging/targets). {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token const messaging = new sdk.Messaging(client); const subscriber = await messaging.createSubscriber( '<TOPIC_ID>', // topicId '<SUBSCRIBER_ID>', // subscriberId '<TARGET_ID>' // targetId ); ``` ```deno import * as sdk from "npm:node-appwrite"; const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token const messaging = new sdk.Messaging(client); const subscriber = await messaging.createSubscriber({ topicId: '<TOPIC_ID>', subscriberId: '<SUBSCRIBER_ID>', targetId: '<TARGET_ID>' }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token $messaging = new Messaging($client); $subscriber = $messaging->createSubscriber( topicId: '[TOPIC_ID]', subscriberId: '[SUBSCRIBER_ID]', targetId: '[TARGET_ID]' topicId: '<TOPIC_ID>', subscriberId: '<SUBSCRIBER_ID>', targetId: '<TARGET_ID>' ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<PROJECT_ID>') # Your project ID client.set_jwt('eyJhbVCJ9.eyJ...') # Your secret JSON Web Token messaging = Messaging(client) subscriber = messaging.create_subscriber( topic_id = '<TOPIC_ID>', subscriber_id = '<SUBSCRIBER_ID>', target_id = '<TARGET_ID>' ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_jwt('eyJhbVCJ9.eyJ...') # Your secret JSON Web Token messaging = Messaging.new(client) subscriber = messaging.create_subscriber( topic_id: '<TOPIC_ID>', subscriber_id: '<SUBSCRIBER_ID>', target_id: '<TARGET_ID>' ) ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetJWT("eyJhbVCJ9.eyJ..."); // Your secret JSON Web Token Messaging messaging = new Messaging(client); Subscriber result = await messaging.CreateSubscriber( topicId: "<TOPIC_ID>", subscriberId: "<SUBSCRIBER_ID>", targetId: "<TARGET_ID>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token Messaging messaging = Messaging(client); Subscriber subscriber result = await messaging.createSubscriber( topicId: '<TOPIC_ID>', subscriberId: '<SUBSCRIBER_ID>', targetId: '<TARGET_ID>', ); ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setJWT("eyJhbVCJ9.eyJ...") // Your secret JSON Web Token val messaging = new Messaging(client) val subscriber = messaging.createSubscriber( topicId = "<TOPIC_ID>", subscriberId = "<SUBSCRIBER_ID>", targetId = "<TARGET_ID>" ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setJWT("eyJhbVCJ9.eyJ..."); // Your secret JSON Web Token Messaging messaging = new Messaging(client); messaging.createSubscriber( "<TOPIC_ID>", // topicId "<SUBSCRIBER_ID>", // subscriberId "<TARGET_ID>" // targetId new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setJWT("eyJhbVCJ9.eyJ...") // Your secret JSON Web Token let messaging = Messaging(client) let subscriber = try await messaging.createSubscriber( topicId: "<TOPIC_ID>", subscriberId: "<SUBSCRIBER_ID>", targetId: "<TARGET_ID>" ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} --- ## Twilio https://appwrite.io/docs/products/messaging/twilio Twilio lets you send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add Twilio as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS**. {% only_dark %} ![Add a Twilio provider](/images/docs/messaging/providers/twilio/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a Twilio provider](/images/docs/messaging/providers/twilio/provider.png) {% /only_light %} Give your provider a name > choose **Twilio** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Twilio dashboard to connect your Appwrite project. You will need to provide the following information from your **Twilio dashboard**. {% table %} * Field name * --- * Account SID * Head to Twilio console > **Account info** > **Account SID**. --- * Auth token * Head to Twilio console > **Account info** > **Auth Token**. --- * Sender number * You can access numbers by navigating to your Twilio console > **Develop** > **Phone Numbers** > **Manage** > **Active Numbers**. {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **SMS**. {% only_dark %} ![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.png) {% /only_dark %} {% only_light %} ![Create an SMS message](/images/docs/messaging/messages/create-sms-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() async { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = await messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTwilioProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<ACCOUNT_SID>', // accountSid (optional) '<AUTH_TOKEN>', // authToken (optional) '<FROM>' // from (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateTwilioProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<ACCOUNT_SID>', // accountSid (optional) '<AUTH_TOKEN>', // authToken (optional) '<FROM>' // from (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateTwilioProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional accountSid: '<ACCOUNT_SID>', // optional authToken: '<AUTH_TOKEN>', // optional from: '<FROM>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_twilio_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional enabled = False, # optional account_sid = '<ACCOUNT_SID>', # optional auth_token = '<AUTH_TOKEN>', # optional from = '<FROM>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_twilio_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional enabled: false, # optional account_sid: '<ACCOUNT_SID>', # optional auth_token: '<AUTH_TOKEN>', # optional from: '<FROM>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateTwilioProvider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional enabled: false // optional accountSid: "<ACCOUNT_SID>" // optional authToken: "<AUTH_TOKEN>" // optional from: "<FROM>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.updateTwilioProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional accountSid: '<ACCOUNT_SID>', // optional authToken: '<AUTH_TOKEN>', // optional from: '<FROM>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTwilioProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<ACCOUNT_SID>", // accountSid (optional) "<AUTH_TOKEN>", // authToken (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateTwilioProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<ACCOUNT_SID>", // accountSid (optional) "<AUTH_TOKEN>", // authToken (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateTwilioProvider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional enabled: xfalse, // optional accountSid: "<ACCOUNT_SID>", // optional authToken: "<AUTH_TOKEN>", // optional from: "<FROM>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Vonage https://appwrite.io/docs/products/messaging/vonage Vonage lets you send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. {% section #add-provider step=1 title="Add provider" %} To add Vonage as a provider, navigate to **Messaging** > **Providers** > {% icon icon="plus" size="m" /%} **Add provider** > **SMS**. {% only_dark %} ![Add a Vonage provider](/images/docs/messaging/providers/vonage/dark/provider.png) {% /only_dark %} {% only_light %} ![Add a Vonage provider](/images/docs/messaging/providers/vonage/provider.png) {% /only_light %} Give your provider a name > choose **Vonage** > click **Save and continue**. The provider will be saved to your project, but not enabled until you complete its configuration. {% /section %} {% section #configure-provider step=2 title="Configure provider" %} In the **Configure** step, you will need to provide details from your Vonage dashboard to connect your Appwrite project. You will need to provide the following information from your **Vonage dashboard**. {% table %} * Field name * --- * API key * Head to Vonage dashboard > **Build & manage** > **API settings** and copy the API key. --- * API secret * Head to Vonage dashboard > **Build & manage** > **API settings** and copy the API secret. --- * Sender number * You can access your numbers by navigating to Vonage dashboard > **Build & manage** > **Numbers** > **Your numbers**. --- {% /table %} After adding the following details, click **Save and continue** to enable the provider. {% /section %} {% section #test-provider step=3 title="Test provider" %} Before sending your first message, make sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to. {% tabs %} {% tabsitem #console title="Console" %} To send a test message, navigate to **Messaging** > **Messages** > {% icon icon="plus" size="m" /%} **Create message** > **SMS**. {% only_dark %} ![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.png) {% /only_dark %} {% only_light %} ![Create an SMS message](/images/docs/messaging/messages/create-sms-message.png) {% /only_light %} Add your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**. Verify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const message = await messaging.createSms( '<MESSAGE_ID>', // messageId '<CONTENT>', // content [], // topics (optional) [], // users (optional) [], // targets (optional) true, // draft (optional) '' // scheduledAt (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.create_sms( message_id = '<MESSAGE_ID>', content = '<CONTENT>', topics = [], # optional users = [], # optional targets = [], # optional draft = True, # optional scheduled_at = '' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.create_sms( message_id: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], # optional users: [], # optional targets: [], # optional draft: true, # optional scheduled_at: '' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Message result = await messaging.CreateSMS( messageId: "<MESSAGE_ID>", content: "<CONTENT>" topics: new List<string> {} // optional users: new List<string> {} // optional targets: new List<string> {} // optional draft: true // optional scheduledAt: ""); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.createSms( messageId: '<MESSAGE_ID>', content: '<CONTENT>', topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: '', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.createSms( "<MESSAGE_ID>", // messageId "<CONTENT>", // content listOf(), // topics (optional) listOf(), // users (optional) listOf(), // targets (optional) true, // draft (optional) "" // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let message = try await messaging.createSms( messageId: "<MESSAGE_ID>", content: "<CONTENT>", topics: [], // optional users: [], // optional targets: [], // optional draft: true, // optional scheduledAt: "" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider. {% /section %} {% section #manage-provider step=4 title="Manage provider" %} {% tabs %} {% tabsitem #console title="Console" %} You can update or delete a provider in the Appwrite Console. Navigate to **Messaging** > **Providers** > click your provider. In the settings, you can update a provider's configuration or delete the provider. {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateVonageProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<API_KEY>', // apiKey (optional) '<API_SECRET>', // apiSecret (optional) '<FROM>' // from (optional) ); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let messaging = new sdk.Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const provider = await messaging.updateVonageProvider( '<PROVIDER_ID>', // providerId '<NAME>', // name (optional) false, // enabled (optional) '<API_KEY>', // apiKey (optional) '<API_SECRET>', // apiSecret (optional) '<FROM>' // from (optional) ); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Messaging; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $messaging = new Messaging($client); $result = $messaging->updateVonageProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional apiKey: '<API_KEY>', // optional apiSecret: '<API_SECRET>', // optional from: '<FROM>' // optional ); ``` ```python from appwrite.client import Client from appwrite.services.messaging import Messaging client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) messaging = Messaging(client) result = messaging.update_vonage_provider( provider_id = '<PROVIDER_ID>', name = '<NAME>', # optional enabled = False, # optional api_key = '<API_KEY>', # optional api_secret = '<API_SECRET>', # optional from = '<FROM>' # optional ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key messaging = Messaging.new(client) response = messaging.update_vonage_provider( provider_id: '<PROVIDER_ID>', name: '<NAME>', # optional enabled: false, # optional api_key: '<API_KEY>', # optional api_secret: '<API_SECRET>', # optional from: '<FROM>' # optional ) puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var messaging = new Messaging(client); Provider result = await messaging.UpdateVonageProvider( providerId: "<PROVIDER_ID>" name: "<NAME>" // optional enabled: false // optional apiKey: "<API_KEY>" // optional apiSecret: "<API_SECRET>" // optional from: "<FROM>"); // optional ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; import 'package:dart_appwrite/enums.dart'; import 'package:dart_appwrite/models.dart'; void main() { // Init SDK Client client = Client(); Messaging messaging = Messaging(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = messaging.updateVonageProvider( providerId: '<PROVIDER_ID>', name: '<NAME>', // optional enabled: false, // optional apiKey: '<API_KEY>', // optional apiSecret: '<API_SECRET>', // optional from: '<FROM>', // optional ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateVonageProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<API_KEY>", // apiKey (optional) "<API_SECRET>", // apiSecret (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Messaging messaging = new Messaging(client); messaging.updateVonageProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) "<API_KEY>", // apiKey (optional) "<API_SECRET>", // apiSecret (optional) "<FROM>" // from (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let messaging = Messaging(client) let provider = try await messaging.updateVonageProvider( providerId: "<PROVIDER_ID>", name: "<NAME>", // optional enabled: xfalse, // optional apiKey: "<API_KEY>", // optional apiSecret: "<API_SECRET>", // optional from: "<FROM>" // optional ) ``` {% /multicode %} {% /tabsitem %} {% /tabs %} {% /section %} --- ## Network https://appwrite.io/docs/products/network Appwrite's network is designed to deliver low-latency, high-performance experiences for developers and end-users alike. It leverages a robust Content Delivery Network (CDN) with edge locations across multiple regions to ensure fast and reliable data delivery. With distributed infrastructure and multiple deployment regions, Appwrite enables developers to build globally scalable applications while maintaining data sovereignty. Its architecture integrates seamlessly with APIs, storage, and databases, optimizing both speed and availability. {% only_dark %} ![Network map](/images/docs/network/dark/all-maps.png) {% /only_dark %} {% only_light %} ![Network map](/images/docs/network/all-maps.png) {% /only_light %} ### Components {% #components %} The Appwrite Network is composed of multiple components that work together to deliver a seamless experience for developers and end-users. These components include: {% cards %} {% cards_item href="/docs/products/network/regions" title="Regions" %} Multi-region deployments for geo-redundancy, compliance and low-latency. {% /cards_item %} {% cards_item href="/docs/products/network/edges" title="Edges" %} Edge nodes for fast request processing and reduced round-trip times. {% /cards_item %} {% cards_item href="/docs/products/network/cdn" title="CDN" %} Global infrastructure for optimized routing, enabling faster and more consistent data delivery. {% /cards_item %} {% cards_item href="/docs/products/network/endpoints" title="Endpoints" %} Dedicated endpoints for region-specific, edge, and compute workloads. {% /cards_item %} {% /cards %} #### Region vs Edge {% #region-vs-edge %} In Appwrite, Regions are where all your core data and services live. This includes your databases, auth, functions, messaging, and storage. Regions are the source of truth, handling heavy workloads and ensuring your application runs reliably while keeping your data compliant with local regulations. Edges are about speed. They process requests closer to your users using smart geo-routing, reducing latency by handling compute tasks at the nearest edge location. Edges are perfect for serving cached content, executing lightweight computations, and optimizing user interactions. {% info title="Where to execute?" %} The Appwrite Network is designed for flexibility. You can choose to run your serverless compute workload in your project's home region by using the `<ID>.<REGION>.appwrite.run` endpoint, or on the edge using the `<ID>.appwrite.network` endpoint. Both your region and edge endpoints can be customized to use your own custom domain. {% /info %} ### Architecture {% #architecture %} Appwrite's network is designed to provide a balance between centralized compute and distributed delivery: - Regions: Core infrastructure and data resides in global regions, where all services like databases, auth, functions, messaging, and storage operate. These regions ensure data sovereignty, compliance, and high availability for critical workloads. - Edges: Distributed edge locations process requests closer to end-users, leveraging smart geo-routing to minimize latency. These edge handle tasks like caching, static content delivery, and lightweight compute to improve performance. - Private Routing: Data moves between edge nodes and regions through optimized, low-latency connections to ensure fast and reliable communication. This setup separates heavy backend processing in regions from latency-sensitive tasks at the edge, enabling efficient handling of global workloads with minimal performance trade-offs. {% info title="Self-Hosting?" %} Appwrite's self-hosted setup is designed and optimized for straightforward, single-region environments - making it an ideal choice for small to medium-scale workloads. All Cloud users have access to the Appwrite Network. If you need to scale your self-hosted deployment or require additional solutions, please [contact us](/contact-us/enterprise). {% /info %} ### Features {% #features %} {% cards %} {% cards_item href="/docs/products/network/dns" title="Domain Name System" %} Provides dedicated nameservers and DNS management for apex domains with SSL certification. {% /cards_item %} {% cards_item href="/docs/products/network/ddos" title="DDoS mitigation" %} Protects against distributed denial-of-service attacks, ensuring uninterrupted access. {% /cards_item %} {% cards_item href="/docs/products/network/tls" title="Transport Layer Security (TLS)" %} Encrypts data in transit for secure and private communication. {% /cards_item %} {% cards_item href="/docs/products/network/waf" title="Web Application Firewall (WAF)" %} Shields applications from common web vulnerabilities and attacks on the application layer. {% /cards_item %} {% cards_item href="/docs/products/network/compression" title="Compression" %} Reduces data size in transit to improve transfer speed and efficiency. {% /cards_item %} {% cards_item href="/docs/products/network/caching" title="Caching" %} Stores frequently accessed data for faster retrieval and lower latency. {% /cards_item %} {% /cards %} --- ## Caching https://appwrite.io/docs/products/network/caching Appwrite employs a multi-layered caching approach to enhance the performance of your applications. By utilizing caching at the **region**, **edge**, and **CDN** levels, Appwrite ensures faster response times, optimized resource usage, and efficient handling of dynamic workloads. ### Region-level {% #region-level %} At the region level, Appwrite provides smart in-memory caching for various resources: - **Documents**: Frequently accessed rows are cached in memory and automatically purged when updated, ensuring data consistency without manual intervention. - **Storage files**: Frequently accessed files are cached in memory to reduce disk reads and improve performance. - **Image transformations**: Processed images (e.g., resized or converted) are cached in memory for faster repeated requests, reducing processing overhead. Region-level caching is tightly integrated with Appwrite's APIs, optimizing performance while preserving data integrity. ### Edge-level {% #edge-level %} At the edge, Appwrite employs smart caching for specific use cases: - **Compute builds**: Caches build artifacts for faster deployments and reduced latency during function executions. - **Cold starts**: Pre-loads frequently accessed resources, reducing latency for new requests and improving application responsiveness. Edge-level caching complements region-level caching, ensuring optimal performance for globally distributed applications. ### Private caching {% #cdn-caching %} Appwrite's CDN layer includes **private caching**, a caching strategy designed to handle the dynamic and permission-sensitive nature of Appwrite's APIs and resources securely. **What is private caching?** In the HTTP context, private caching allows responses to be cached but ensures they are only served to the specific user or client that requested them. This is achieved using HTTP headers that control caching behavior. For example: - `Cache-Control: private, max-age=3600` Indicates that the response can be cached, but only in a private cache (e.g., the user's browser). - `Cache-Control: no-store` Ensures that no part of the response is cached, useful for highly sensitive or frequently changing data. - `Vary: Authorization` Signals that the cached response varies based on the `Authorization` header, ensuring permission-specific responses are cached and served appropriately. **Why use private caching?** Appwrite's APIs often deliver personalized or restricted content based on user roles and permissions. Private caching ensures: - **Security**: Sensitive resources are securely cached and only served to the correct user. - **Permission awareness**: API responses are tailored to each user's permissions, ensuring consistent behavior. - **Performance**: By caching user-specific responses, private caching reduces backend load while maintaining secure and accurate data delivery. This approach prevents the accidental exposure of user-specific or restricted data through shared caches while still enabling performance optimizations where possible. ### Caching rules {% #custom-caching %} Enterprise customers can collaborate with their Appwrite success manager to define custom caching rules tailored to their applications. This includes: - Setting custom caching durations for specific resources. - Defining exclusion rules for sensitive or frequently changing data. - Optimizing cache invalidation strategies for complex workflows. For more information on upgrading to the enterprise plan, [contact sales](https://appwrite.io/contact-us/enterprise). --- ## Content Delivery Network (CDN) https://appwrite.io/docs/products/network/cdn Appwrite's CDN (Content Delivery Network) is a globally distributed system designed to enhance the speed, reliability, and security of your application's content delivery. With points of presence (PoPs) in over 120 cities worldwide, the CDN ensures low latency and consistent performance for users, no matter their location. {% only_dark %} ![PoPs map](/images/docs/network/dark/pops-map.png) {% /only_dark %} {% only_light %} ![PoPs map](/images/docs/network/pops-map.png) {% /only_light %} ### Key features {% #key-features %} - Global coverage: Fast access to content for users across continents through over 120 PoPs worldwide. Available on all projects. - Reduced latency: By caching static content at edge nodes, the CDN minimizes the distance between the user and the requested data, significantly reducing latency. - Dynamic content: The CDN supports both static and dynamic content delivery, seamlessly integrating with backend services hosted in Appwrite regions. - Content optimization: Appwrite's CDN uses advanced compression algorithms to reduce data transfer sizes, further improving delivery times. - High availability: Distributed edge nodes and redundant routing ensure that content remains accessible even during regional outages or high traffic loads. {% info title="Self-Hosting?" %} Appwrite's self-hosted setup is optimized for local content delivery in single-region environments. Cloud users benefit from the global CDN with 120+ points of presence worldwide. If your self-hosted deployment requires distributed CDN capabilities, please [contact us](/contact-us/enterprise) to discuss custom solutions. {% /info %} ### Design {% #design %} - Caching strategy: Configurable cache policies for control over TTL and content invalidation. - Secure delivery: All content is transmitted over TLS for secure, encrypted connections. - Integration: The CDN works seamlessly with the Appwrite edges and backend regions, providing a unified experience for developers. By combining global caching, smart routing, and content optimization, the Appwrite CDN is built to handle the demands of modern, high-performance applications. --- ## Compression https://appwrite.io/docs/products/network/compression Appwrite is leveraging compression algorithms to both boost the performance of your app and to reduce and optimize bandwidth and storage costs for Appwrite developers. This page provides an in-depth explanation of the compression algorithms supported by Appwrite for API responses, image transformations, and storage buckets. ### API {% #api %} Appwrite supports two primary algorithms for text-based responses: **Brotli** and **Gzip**. These algorithms are integral for improving data transfer speeds across the HTTP based APIs, especially when dealing with textual content, which tends to be highly compressible. - **Brotli**: Chosen for its superior compression efficiency, especially for smaller files. Brotli performs best in HTTP/2 and HTTP/3 environments, where smaller payloads mean faster transfers and lower bandwidth consumption. Its efficiency also allows for quicker decompression on modern clients. - **Gzip**: Gzip remains supported for backward compatibility and for clients that do not yet fully support Brotli. Though Gzip has a lower compression ratio compared to Brotli, it is still a reliable fallback for older browsers and HTTP/1.1 connections. - **Zstd**: Zstd offers very high compression ratios and significantly faster decompression speeds, making it ideal for server-to-server communication and large data transfers. While its browser support is limited compared to Brotli and Gzip, Zstd excels in scenarios where performance and efficiency are critical for backend processes. #### Conditions {% #conditions %} Compression in Appwrite is triggered dynamically based on several conditions. This ensures that we only compress data when it is beneficial for performance and that we avoid unnecessary overhead for small payloads or non-textual data. 1. **MIME types**: Only text-based MIME types are eligible for compression. These include: - `text/plain` - `text/css` - `text/javascript` - `application/javascript` - `text/html` - `application/json` - `image/svg+xml` - `application/xml+rss` This selection is based on the nature of these content types being easily compressible, resulting in significant size reductions without loss of information. 2. **Response size**: Compression is applied when the size of the response exceeds **1KB**. This threshold has been selected based on testing to minimize the CPU overhead of compression for small payloads, where the gains in bandwidth reduction are negligible. 3. **Client-side support**: Clients indicate their support for specific compression algorithms via the `Accept-Encoding` HTTP header. Appwrite prioritizes compression based on the following client-provided values: - `br`: Indicates support for Brotli compression. - `zstd`: Indicates support for Zstandard compression. - `gzip`: Indicates support for Gzip compression. - `identity`: Indicates that no compression is supported or requested. #### Prioritizations {% #prioritizations %} Appwrite prioritizes Brotli over Gzip due to Brotli’s more efficient compression ratio, especially when dealing with text-based content like HTML, CSS, and JSON files. Brotli uses a sliding window dictionary that results in higher compression ratios at slower speeds, but in an HTTP/2 or HTTP/3 environment, the benefits outweigh the costs. Gzip is used as a fallback when Brotli is not supported by the client. | Algorithm {% width=120 %} | Ratio {% width=120 %} | Browsers | Notes | |-----------|-------------------|-----------------------|---------------------------------------------------| | Brotli | High | All modern browsers | Optimal for small text files; highly efficient. | | Gzip | Medium | Universal | Broad compatibility with older and modern clients.| | Zstd | Very High | Limited | High performance with faster decompression speeds, ideal for server-to-server communication. | | Identity | None | Universal | Used when no compression is applied or supported. | #### Enabling compression {% #enabling-compression-1 %} Compression is enabled by default for eligible API responses in Appwrite. You do not need to manually enable it; Appwrite dynamically selects the best algorithm based on the client’s `Accept-Encoding` headers and the MIME type of the response. ### Image transformations {% #image-transformations %} Appwrite's API supports the [compression of image files](/docs/products/storage/images) during manipulation and preview generation. The primary reason for compressing images is to minimize file sizes while maintaining visual quality, thus reducing both bandwidth usage and storage costs. Appwrite supports both legacy and modern image formats, including: - **PNG**, **JPEG**, **GIF**: These are traditional formats supported for compatibility reasons. PNG supports lossless compression, while JPEG and GIF are lossy but optimized for small sizes. - **WebP**: A modern format developed by Google, offering better compression rates than JPEG, PNG, or GIF while maintaining equivalent quality. - **AVIF**: The most modern image format supported by Appwrite, which offers even higher compression rates than WebP. AVIF is based on the AV1 video codec and is optimized for high-performance image rendering with minimal bandwidth use. #### Supported API endpoints {% #supported-api-endpoints %} Appwrite applies image compression exclusively through the Image Preview API. This ensures that any dynamic operations, such as generating previews, resizing images, or converting between formats, are optimized for performance and reduced file size. Images uploaded through the Storage API remain in their original format and quality without automatic compression to preserve your source of truth. #### Prioritization {% #prioritization %} Appwrite does not apply any image compression by default. Developers have full control over the output compression by specifying it in the query string when using the Image Preview API. This allows for precise customization to suit various use cases. Appwrite supports modern image formats like WebP and AVIF for their exceptional compression rates and compatibility with most browsers. While WebP is often the default choice for conversions, AVIF is recommended when seeking optimal performance and minimal file sizes. ```javascript import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFilePreview( 'photos', // bucket ID 'sunset.png', // file ID 1800, // width, will be resized using this value. 0, // height, ignored when 0 'center', // crop center '90', // slight compression 5, // border width 'CDCA30', // border color 15, // border radius 1, // full opacity 0, // no rotation 'FFFFFF', // background color 'webp' // output jpg format ); console.log(result.href); ``` | Algorithm {% width=120 %} | Formats | Ratio {% width=120 %} | Notes | | --------- | ----------------- | ----------------- | -------------------- | | WebP | PNG, JPEG, GIF | High | Great balance of compression efficiency and visual quality | | AVIF | PNG, JPEG, GIF | Best | Highest compression rate for modern use cases | | JPEG | JPEG | Medium | Legacy support for lossy compression | | PNG | PNG | Lossless | Necessary for lossless compression requirements | #### Enabling compression {% #enabling-compression-2 %} You can enable image compression through the **Image Preview API** by specifying the desired output format in API calls. For example, requesting a WebP or AVIF conversion automatically triggers Appwrite's compression algorithms to optimize the image size. --- ## DDoS mitigation https://appwrite.io/docs/products/network/ddos Distributed Denial-of-Service (DDoS) attacks are one of the most common threats to online applications, aimed at overwhelming servers with malicious traffic to disrupt services. Appwrite provides robust, always-on DDoS protection across all Appwrite Cloud plans to ensure the reliability and security of your applications. Appwrite's network is designed to detect and mitigate malicious traffic before it reaches your application. Using a combination of automated filtering and intelligent traffic analysis, our DDoS protection: - Identifies and blocks large-scale attack patterns in real-time. - Ensures legitimate traffic continues to flow uninterrupted. - Prevents application downtime and minimizes performance impacts. ### Design {% #design %} Appwrite's DDoS protection operates across multiple OSI layers to provide comprehensive coverage: - **Network Layer (Layer 3)**: Detects and mitigates large-scale attacks such as ICMP floods and UDP amplification. - **Transport Layer (Layer 4)**: Protects against attacks like SYN floods and TCP-based exploits by identifying anomalous traffic patterns. - **Application Layer (Layer 7)**: Blocks high-level attacks, such as HTTP floods, by filtering malicious requests while allowing legitimate user traffic. ### Benefits {% #benefits %} - **Cost control**: Malicious traffic blocked by DDoS protection does not count towards your bandwidth or request usage, saving you from unnecessary charges. - **Reliability**: Keeps your application online and responsive, even during attempted attacks. - **Zero configuration**: DDoS protection is fully managed by Appwrite and requires no manual setup or maintenance. Protection is enabled by default on all Cloud plans. ### Availability {% #availability %} DDoS mitigation is automatically enabled by default for all Appwrite Cloud plans, ensuring every application hosted on Appwrite benefits from this safeguard without additional costs. This includes: This protection is integrated directly into Appwrite's console, edge and region infrastructure, providing seamless coverage without requiring additional setup from developers. --- ## Appwrite DNS service https://appwrite.io/docs/products/network/dns Appwrite provides a dedicated DNS (Domain Name System) service through its `appwrite.zone` nameservers to help you manage domain records for your applications. This service is ideal for apex domains (root domains) that cannot use CNAME records due to DNS protocol limitations. The DNS service enables you to configure custom domains for Sites, Functions, and APIs while providing automatic SSL certificate management and high availability. Whether you need to set up subdomains or apex domains, Appwrite's DNS service offers a complete solution. ### Benefits {% #benefits %} - **Support for apex domains**: Use root domains like `example.com` directly without workarounds - **Automatic SSL certificate management**: All domains get valid SSL certificates automatically - **Integrated management**: Simplified configuration for Sites, Functions, and APIs - **High availability**: Built on reliable infrastructure with global distribution ### Managing DNS records {% #managing-dns-records %} You can manage DNS records for your domains in two places within the Appwrite Console: #### Organization-level DNS management To manage DNS records at the organization level: 1. Navigate to your organization in the Appwrite Console 2. Select the **Domains** tab 3. Here you can view and manage all your DNS records across different projects This is the central place to manage domain records when you're using Appwrite's DNS service by configuring NS records to point to `ns1.appwrite.zone` and `ns2.appwrite.zone`. #### Service-specific domain management You can also manage domains directly from specific services: - **Sites**: Navigate to a site and select the **Domains** tab - **Functions**: Navigate to a function and select the **Domains** tab - **API Endpoints**: Configure custom domains in your project settings under **Settings** > **Custom domains** ### Record types {% #record-types %} Appwrite DNS supports various DNS record types to meet your domain configuration needs: | Record Type | Description | |-------------|-------------| | A | Maps a domain to an IPv4 address | | AAAA | Maps a domain to an IPv6 address | | CNAME | Maps a domain to another domain (alias) | | MX | Specifies mail servers for the domain | | TXT | Stores text information (often used for verification) | | NS | Specifies the nameservers for the domain | | SRV | Specifies services available for a domain (used for Voice over IP, instant messaging, etc.) | | CAA | Specifies which certificate authorities (CAs) are authorized to issue certificates for a domain | | HTTPS | Provides configuration for HTTPS connections | | ALIAS | Similar to CNAME but can be used at the zone apex | ### Setting up apex domains {% #setting-up-apex-domains %} To use an apex domain (e.g., `example.com`) with Appwrite: 1. Navigate to your domain registrar's DNS settings 2. Find the NS (nameserver) record settings 3. Replace the existing nameservers with `ns1.appwrite.zone` and `ns2.appwrite.zone` 4. Wait for DNS propagation (may take up to 48 hours) 5. Return to Appwrite Console to verify and configure domain settings {% info title="DNS delegation" %} When you change your NS records to `ns1.appwrite.zone` and `ns2.appwrite.zone`, you're delegating complete DNS management to Appwrite. This means all existing DNS records (like email MX records) will need to be recreated in Appwrite's DNS configuration. {% /info %} ### Adding records in Appwrite {% #adding-records %} After delegating your domain to Appwrite's DNS servers, you can add and manage records: 1. Navigate to your organization's **Domains** tab in the Appwrite Console 2. Find your domain and click **Manage Records** 3. Click **Add Record** and select the record type 4. Fill in the required information based on the record type 5. Save the record {% info title="Record propagation" %} New DNS records may take time to propagate across the internet. This process typically takes minutes but can sometimes take longer depending on various factors like DNS cache settings. {% /info %} --- ## Edges https://appwrite.io/docs/products/network/edges Appwrite edges are strategically distributed locations designed to process requests closer to your users. These edge nodes handle latency-sensitive operations, such as caching, routing, and quick computations, to deliver faster, more efficient interactions while reducing the load on your application's core infrastructure. {% only_dark %} ![Edges map](/images/docs/network/dark/edges-map.png) {% /only_dark %} {% only_light %} ![Edges map](/images/docs/network/edges-map.png) {% /only_light %} {% info title="We're expanding!" %} Currently, Appwrite's edge network includes 6 locations. We are actively working to expand the number of edge locations globally. New locations will be strategically prioritized to ensure the best possible global coverage and performance for all users. {% /info %} ### List {% #list %} Appwrite edges are globally distributed, ensuring low-latency interactions for users around the world. Here's a list of locations with active or upcoming edge support: | Location | Code{% width=120 %} | Status {% width=120 %} | |---------------------|--------|-----------------| | Frankfurt | FRA | Available | | New York | NYC | Available | | Sydney | SYD | Available | | San Francisco | SFO | Available | | Singapore | SGP | Available | | Toronto | TOR | Available | | Bangalore | BLR | 2025 | | Amsterdam | AMS | 2025 | | London | LON | 2025 | Edges complement Appwrite regions, working together to provide fast, reliable, and scalable application performance. ### Routing {% #routing %} Appwrite's edges handle geo-aware routing to balance traffic across the network efficiently. When a user makes a request, it is directed to the nearest available edge node based on their geographic location. This ensures optimal performance by minimizing latency and distributing traffic evenly across edge nodes. Geo-aware routing helps handle high traffic loads by intelligently directing requests to the best-performing edge, reducing congestion and maintaining consistent response times. By leveraging this approach, Appwrite ensures that your application delivers fast and reliable experiences to users worldwide. {% info title="Edge vs Region" %} Use an edge when compute needs to happen close to your users, like serving static content, doing local computation or data processing. Use a region when compute needs to happen closer to your data, such as frequent access to your Appwrite database or storage. {% /info %} ### Design {% #design %} Edges are optimized to reduce latency and improve user experience by processing operations closer to the source of requests. Key aspects include: - Geo-routing: Smart routing ensures user requests are served by the optimal edge node. - Caching: Frequently accessed data is cached at edges to reduce round-trips to core regions. - Lightweight compute: Handles quick, resource-efficient computations for real-time tasks. - Optimization: The edge network planned locations are strategically designed with fewer, high-capacity edges to maximize cache efficiency and achieve higher cache-hit ratios. Edges enhance Appwrite's ability to deliver fast, reliable experiences by optimizing interactions and reducing latency for end-users. Together with regions, they create a robust infrastructure designed for modern, globally distributed applications. {% info title="Self-Hosting?" %} Appwrite's self-hosted deployments operate in a single region by default. The geo-distributed edge network with its routing features is available to Cloud users. For edge infrastructure in self-hosted environments or multi-region setups, please [contact us](/contact-us/enterprise) to explore enterprise options. {% /info %} --- ## Endpoints https://appwrite.io/docs/products/network/endpoints Appwrite offers multiple endpoints to access its services, each designed to optimize specific aspects of performance, routing, and compute. Understanding these endpoints helps you determine the most efficient way to interact with your Appwrite project. ### Edge {% #edge %} The **`appwrite.network`** domain provides geo-balanced endpoints that route traffic to the nearest edge node based on the user's geographic location. {% info title="Availability" %} The `appwrite.network` endpoints will be available in Q2 2025. Currently, you can use the `appwrite.run` domains to run functions in your region of choice. {% /info %} The edge network endpoints are designed for: - **Latency-sensitive operations**: Quickly serving cached content, routing requests, or performing lightweight edge computations. - **Global traffic distribution**: Automatically balancing traffic across the edge network for consistent performance. Example: - `https://<ID>.appwrite.network` Use this endpoint when optimizing for low-latency and global availability is critical for your functions. ### Region {% #region %} The **`<REGION>.cloud.appwrite.io`** domain directs traffic specifically to the region hosting your Appwrite project's services. This endpoint ensures that requests are processed close to your core data and infrastructure, making it ideal for: - **Data-intensive operations**: Frequent access to databases, storage, authentication, and other region-hosted services. - **Regulatory compliance**: Ensuring data residency requirements are met by targeting specific regions. Example: - `https://fra.cloud.appwrite.io` - `https://nyc.cloud.appwrite.io` Use this endpoint when direct access to region-specific infrastructure is required, this is the endpoint you will use to access your Appwrite API or if you want to execute functions directly from your Appwrite SDK. ### Compute {% #compute %} The **`<ID>.<REGION>.appwrite.run`** domain is designed for running server-side functions and compute-heavy tasks directly in the region where your services are hosted. It supports custom domains for seamless integration into your workflows. The `appwrite.run` subdomains are auto-generated for each function you create. This endpoint is best suited for: - **Compute-Intensive Tasks**: Executing server-side functions, handling APIs, or processing asynchronous jobs. - **Custom Domain Support**: Enabling custom domains for specific function endpoints. Examples: - `https://fra.appwrite.run` - `https://[custom-domain]` Use this endpoint for scenarios where compute needs to happen close to your data or for deploying APIs under your own domain. ### Summary {% #summary %} | Endpoint | Use Case | |--------------------------------------|------------------------------------------------------| | `https://<ID>.appwrite.network` | Geo-balanced edges for low-latency operations | | `https://<REGION>.cloud.appwrite.io` | Direct access to region services | | `https://<ID>.<REGION>.appwrite.run` | Region-based compute and function execution | Knowing how these endpoints work helps you choose the right one for your needs, ensuring better performance and alignment with your application's compliance requirements. --- ## Regions https://appwrite.io/docs/products/network/regions Appwrite regions are geographic locations where all your application's core infrastructure is deployed. Each region operates as an independent, highly available cluster, managing the storage, processing, and serving of your data and Appwrite services. {% only_dark %} ![Regions map](/images/docs/network/dark/regions-map.png) {% /only_dark %} {% only_light %} ![Regions map](/images/docs/network/regions-map.png) {% /only_light %} ### List {% #list %} Appwrite is currently available in the following list of regions: | Region {% width=120 %} | Code{% width=120 %} | Endpoint | Status{% width=120 %} | |---------------------|--------|-----------------------------------------|-----------------| | Frankfurt | FRA | `https://fra.cloud.appwrite.io/v1` | Available | | New York | NYC | `https://nyc.cloud.appwrite.io/v1` | Available | | Sydney | SYD | `https://syd.cloud.appwrite.io/v1` | Available | | San Francisco | SFO | `https://sfo.cloud.appwrite.io/v1` | Available | | Singapore | SGP | `https://sgp.cloud.appwrite.io/v1` | Available | | Toronto | TOR | `https://tor.cloud.appwrite.io/v1` | Available | | Bangalore | BLR | `coming soon` | TBD | | Amsterdam | AMS | `coming soon` | TBD | | London | LON | `coming soon` | TBD | Regions are designed to be entirely independent unless explicitly connected, which provides control over data replication and compliance. We're constantly working to add new regions to our network to provide developers with more options for deploying their applications. ### Choosing a region {% #choosing-a-region %} Selecting a region impacts both your application’s performance and its compliance with local regulations. Regions are isolated, so your data and services remain contained within the selected location. When choosing a region, consider proximity to your primary user base to reduce latency and improve response times. Additionally, ensure the region aligns with legal requirements for data residency and sovereignty specific to your application's domain. {% info title="Region vs Edge" %} Use a region when compute needs to happen close to your data, such as frequent access to your Appwrite database or storage. Use an edge when compute needs to happen closer to your users, like serving static content, performing local computations, or handling data processing at the edge. {% /info %} ### Design {% #design %} Regions are isolated environments designed for predictable performance and data security. Key aspects include: - Data storage: All data remains within the region and adheres to local data residency laws. - Fault isolation: Each region is self-contained, so failures in one region do not impact others. - Scalability: Resources within a region scale dynamically to meet application demands. - Networking: Regions connect via secure, low-latency private networks. - High availability: Redundant power, networking, and hardware configurations in data centers. - Cache efficiency: Fewer, data-dense regions boost hit probability, ensuring popular content is readily available. Regions provide the foundation for running scalable, reliable applications with full control over data locality and compliance. --- ## Transport Layer Security (TLS) https://appwrite.io/docs/products/network/tls Transport Layer Security (TLS) is a critical feature of the Appwrite Network, ensuring that all data exchanged between clients and servers is encrypted and secure. By using TLS, Appwrite protects sensitive information from interception, tampering, and unauthorized access during transit. TLS operates at the **transport layer** of the OSI model (Layer 4), encrypting all data before it is transmitted over the network. This includes securing HTTP traffic via HTTPS. When a client connects to Appwrite services, a TLS handshake is performed to establish a secure connection. This process ensures: - **Encryption**: Data is encrypted to prevent unauthorized access during transmission. - **Integrity**: Ensures that data cannot be tampered with or altered. - **Authentication**: Verifies the identity of the server to protect against impersonation or spoofing. ### Key features {% #key-features %} 1. **Modern protocols** Appwrite supports TLS 1.2 and TLS 1.3, offering the latest in encryption standards and performance optimization. 2. **Automatic certificates** TLS certificates are automatically managed and renewed, ensuring that your applications always run on secure connections without manual intervention. 3. **Strong ciphers** Only strong, industry-standard cipher suites are used to ensure robust encryption. 4. **End-to-end security** TLS secures every connection in Appwrite's network, including communication between edge nodes and regions, protecting your data at every step. ### Getting started {% #getting-started %} TLS is enabled by default on all Appwrite endpoints, requiring no additional configuration from developers. Simply use HTTPS when interacting with Appwrite services, and your data will be secured automatically. --- ## Web application firewall (WAF) https://appwrite.io/docs/products/network/waf The Web Application Firewall (WAF) is a critical feature of the Appwrite Network, designed to protect applications from common web vulnerabilities and attacks. Available exclusively to enterprise customers, WAF can be configured through your Appwrite success manager to meet the specific security needs of your application. {% info title="Availability" %} The WAF feature is available exclusively to enterprise customers as part of the Appwrite enterprise offering. Setup and configuration are managed through your dedicated Appwrite success manager, who ensures that the WAF aligns with your application's requirements and evolves with emerging security threats. {% /info %} The WAF functions as a protective barrier at the application layer (Layer 7) of the OSI model, where it inspects and filters HTTP/HTTPS traffic in real-time. It is specifically designed to safeguard your application by analyzing request headers, payloads, and query strings to identify malicious patterns or anomalies. The WAF intercepts traffic before it reaches your application, blocking threats such as: - SQL injection: Malicious input targeting database queries is identified and neutralized by inspecting payloads and request parameters. - Cross-site scripting (XSS): WAF detects and blocks scripts attempting to manipulate or steal client-side data through injection into the DOM or other contexts. - Cross-site request forgery (CSRF): By analyzing session headers and request origins, the WAF ensures only legitimate actions are processed. - DDoS attacks: Although primarily focused on Layer 7 attacks, the WAF works in tandem with other mitigation systems to handle floods of application-level requests. ### Design {% #design %} 1. **Traffic inspection** The WAF analyzes all incoming HTTP/HTTPS requests in real-time, matching them against a dynamic set of security rules. 2. **Threat mitigation** Suspicious requests are blocked before they reach your application, ensuring uninterrupted service and secure user interactions. 3. **Custom rules** Enterprise customers can work with their success manager to define custom rules tailored to their specific application requirements. ### Key features {% #key-features %} - Real-time protection: Continuous monitoring and blocking of malicious traffic. - Customizable rulesets: Configure WAF policies to align with your application's unique architecture. - Compliance support: Meet security standards and regulatory requirements by safeguarding sensitive data. - Seamless integration: Fully integrated into the Appwrite network, ensuring optimal performance and low-latency security enforcement. ### Getting started {% #getting-started %} If you're an enterprise customer, reach out to your success manager to enable WAF for your applications. They'll guide you through the setup process, help configure custom rules, and ensure your application is fully protected. For more information on upgrading to the enterprise plan, [contact sales](https://appwrite.io/contact-us/enterprise). #### Configuration {% #configuration %} Appwrite's WAF is designed to adapt to the specific needs of your applications. Tailored rules and actions can be implemented to address evolving security threats and traffic patterns effectively. ##### Rules {% #rules %} The WAF supports the creation of fine-grained rules using logical expressions. These rules define how incoming traffic is inspected and handled. 1. **Expression-based rules** Use logical expressions to create granular filters for incoming requests. These expressions operate on HTTP request fields, headers, cookies, and metadata. Examples of configurable expressions: - **URL matching**: Block or allow traffic based on URL patterns (e.g., `/v1/functions/*`). - **Request method**: Filter traffic based on HTTP methods (e.g., `GET`, `POST`, `PUT`). - **Header inspection**: Inspect headers like `User-Agent`, `Authorization`, or `X-Forwarded-For`. - **Cookie validation**: Check for specific cookies or validate their values to identify bot or malicious traffic. - **Rate limiting**: Match requests based on IP or session-level rate thresholds. - **Geolocation rules**: Allow or block traffic based on the geographic location of the user. 2. **Regular expressions (Regex)** Use regex patterns for more advanced filtering, such as matching complex URL structures, payloads, or specific query parameters. 3. **IP Filtering** Define rules to block, allow, or challenge traffic from specific IP ranges or CIDR blocks. ##### Actions {% #actions %} Based on the defined rules, the WAF can perform various actions to handle traffic appropriately. 1. **Allow** Allows the request to pass through to the backend without modification. 2. **Block** Rejects the request entirely, responding with an appropriate HTTP status code (e.g., `403 Forbidden`). 3. **Challenge** Issues a challenge (e.g., CAPTCHA) to validate whether the request originates from a legitimate user. 4. **Redirect** Redirects the request to a specified URL. Useful for handling deprecated endpoints or re-routing unauthorized traffic. 5. **Log** Records the request in detailed logs for further analysis without altering the request flow. 6. **Rate limiting** Throttles traffic from specific IPs or sessions that exceed defined thresholds, reducing the risk of abuse or overload. 7. **Modify request** Dynamically alters request components, such as headers, query parameters, or cookies. ##### Examples {% #examples %} ###### Blocking malicious bots - **Expression**: Match requests with suspicious `User-Agent` strings or invalid headers. - **Action**: Block or issue a CAPTCHA challenge to confirm legitimate traffic. ###### Preventing SQL injection - **Expression**: Match payloads or query strings containing SQL keywords like `SELECT`, `DROP`, or `--`. - **Action**: Block the request and log it for analysis. ###### Rate limiting by endpoint - **Expression**: Limit requests to sensitive endpoints (e.g., `/login`) to prevent abuse. - **Action**: Throttle or temporarily block repeated requests from the same IP. ###### Geo-based blocking - **Expression**: Match requests originating from specific regions or countries. - **Action**: Block or redirect users to a region-specific notice page. ###### Header enforcement - **Expression**: Check for required headers (e.g., `Authorization`) on API requests. - **Action**: Block requests missing critical headers. --- ## Sites https://appwrite.io/docs/products/sites Appwrite Sites empowers developers to host and manage web applications seamlessly within the Appwrite ecosystem. Appwrite Sites provides a fast, scalable, and secure way to deploy web apps directly from source control, allowing for quick iterations and live updates. Each site has a dedicated URL, runs within its own isolated container, and can be configured with custom domains and environment variables. Appwrite Sites leverages the [Appwrite Network](/docs/products/network) infrastructure to enhance your sites' performance and reliability. Your deployed sites automatically benefit from global content distribution across strategic edge locations, reducing latency and improving load times, while also gaining advanced security features including DDoS protection, Web Application Firewall (WAF), and TLS encryption. ### Getting started Appwrite Sites lets you host any web application with ease. However, to make this process even simpler, you can begin by exploring starter kits developed by the Appwrite team or leverage a pre-configured template with built-in integrations to implement essential features. {% only_dark %} ![Create first site](/images/docs/sites/dark/create-first-site.png) {% /only_dark %} {% only_light %} ![Create first site](/images/docs/sites/create-first-site.png) {% /only_light %} Try out one of our most popular framework quick-starts: {% cards %} {% cards_item href="/docs/products/sites/quick-start/nextjs" title="Next.js" icon="icon-nextjs" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/nuxt" title="Nuxt" icon="icon-nuxt" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/sveltekit" title="SvelteKit" icon="icon-svelte" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/astro" title="Astro" icon="icon-astro" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/vue" title="Vue" icon="web-icon-vue" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/tanstack-start" title="TanStack Start" icon="web-icon-tanstack" %} {% /cards_item %} {% /cards %} Or, [setup your first site using your favorite framework >](/docs/products/sites/quick-start) ### Migrating from other platforms? Looking to migrate existing web applications from other hosting platforms to Appwrite Sites? Find detailed migration guides for various platforms: {% cards %} {% cards_item href="/docs/products/sites/migrations/vercel" title="Migrating from Vercel" icon="icon-vercel" %} {% /cards_item %} {% /cards %} --- ## Deploy from CLI https://appwrite.io/docs/products/sites/deploy-from-cli Appwrite Sites allows you to host and deploy websites directly within the Appwrite platform. Each site can have many deployments, which can be thought of as versions of the web application. While we recommend you create deployments through [automatic Git deployments](/docs/products/sites/deploy-from-git), you can also create deployments via the Appwrite CLI. ### CLI {% #cli %} {% partial file="cli-sites.md" /%} #### Configure CLI deployments {% #configure-cli-deployments %} If you need to target a different project, API endpoint, change the path or entry point of your site, or update any of the other configuration options, you can do so by editing the `appwrite.config.json` file. {% arrow_link href="/docs/tooling/command-line/sites#appwritejson" %} Learn more about appwrite.config.json {% /arrow_link %} ### Debugging - If you updated your site's configuration but the deployment is not working as expected, you may need to first redeploy your site before the changes take effect. --- ## Deploy from Git https://appwrite.io/docs/products/sites/deploy-from-git Appwrite Sites allows you to host and deploy websites directly within the Appwrite platform. Each site can have many deployments, which can be thought of as versions of the web application. With Appwrite Sites, you can seamlessly deploy updates from Git repositories, enabling you to track changes to your web app as part of your development workflow. This versioning approach ensures that your site stays up-to-date and your deployment process is fully integrated with your source control, streamlining collaboration and updates. ### Create deployment The recommended way to manage your Appwrite Sites deployments is to use a version control system like Git. This offers simple versioning and collaboration that will easily fit into the rest of your development workflow. You can only use Git deployment for Appwrite Sites connected to Git. [Create a new site with GitHub](/docs/products/sites/quick-start) or connect your existing site to a GitHub repository in your site’s **Settings** > **Git repository** > **Connect Git**. {% only_dark %} ![Git repository](/images/docs/sites/dark/git-repo.png) {% /only_dark %} {% only_light %} ![Git repository](/images/docs/sites/git-repo.png) {% /only_light %} 1. Using Git, checkout to the branch you configured as the production branch when creating the Appwrite Site. 2. Create a new commit. 3. Push the commit. 4. A new deployment will be automatically created, built, and activated. #### Commits to the production branch When you push a commit to the production branch, usually `main`, a new deployment is created, built, and activated. This means the new deployments **immediately replace the current active deployment** and can be used on your site’s primary domain. #### Commits to other branches When you push a commit to a branch other than the production branch, a new deployment is created, but it is not activated. A [preview link](/docs/products/sites/previews) is generated to test this deployment; however, only authorized users, i.e., members of your Appwrite organization, can access this link. ### Git configuration If you need to update your Git configuration, navigate to **Sites** > your site > **Settings** > **Git repository**. #### Install command The install command of your site allow you to install your site’s dependencies. You can specify custom install scripts such as ones that let you configure your `npm` command with options or use an alternative package manager such as `pnpm` or `yarn`. #### Build command The build command of your site allow you to create a build of the site ready for output. You can specify custom build scripts, customize your `npm` command options, or use alternative package managers. #### Output directory The output directory will contain the files generated by your site's build command. The contents of this directory will be available to view on your site's public URL. ### Debugging - If you updated your site's configuration but the deployment is not working as expected, you may need to first redeploy your site before the changes take effect. - If you're missing some code files at build time, verify your **build command** and ensure that the build output is included in the Git configuration's **output directory**. Only files in the output directory folder will be available after deployment. - If you're self-hosting Appwrite, you will need to configure some [environment variables](https://appwrite.io/docs/advanced/self-hosting/functions) to enable Git deployments. --- ## Deploy manually https://appwrite.io/docs/products/sites/deploy-manually Appwrite Sites allows you to host and deploy websites directly within the Appwrite platform. Each site can have many deployments, which can be thought of as versions of the web application. While we recommend you create deployments through [automatic Git deployments](/docs/products/sites/deploy-from-git), you can also create deployments manually by uploading the source code to the Appwrite Console. ### Manual Deployment You can upload your sites to be deployed using the Appwrite Console. The example below shows a skeleton SvelteKit app. ```bash . ├ src/ ├ static/ ├ package.json ├ svelte.config.js ├ tsconfig.json └ vite.config.js ``` First, create a build using the `npm run build` command (or an alternative package manager link `yarn` or `pnpm`). Then navigate inside the folder that contains your build output (for example, `./build` in SvelteKit) and package your code files into the `.tar.gz` format: ```bash tar --exclude code.tar.gz -czf code.tar.gz . ``` Next, navigate to your Appwrite Console and upload the site. 1. Navigate to the site you want to deploy. 2. Head to the **Deployments** tab. 3. Click on the **Create deployment** button. 4. Select the **Manual** option. {% only_dark %} ![Manual deployment](/images/docs/sites/dark/manual-deployment.png) {% /only_dark %} {% only_light %} ![Manual deployment](/images/docs/sites/manual-deployment.png) {% /only_light %} 5. Upload `code.tar.gz`. 6. Select **Activate deployment after build**. 7. Click on the **Create** button. ### Debugging - If you updated your site's configuration but the deployment is not working as expected, you may need to first redeploy your site before the changes take effect. --- ## Deployments https://appwrite.io/docs/products/sites/deployments Each site can have many deployments, which can be thought of as versions of the web application. Sites can be created and deployed using different methods to meet your unique development habits. ### Deployment status Throughout the life cycle of a deployment, it can have any of the following status: | Status | Description | | --- | --- | | `active` | The deployment is built and currently activated and ready to be accessed. A site can have one active deployment and the deployment must be active before being executed. | | `ready` | A deployment is built, but is not activated. Any `ready` deployment can be activated to replace the current active deployment. A ready deployment can also be [previewed](#preview-deployments) by authorized members of your Appwrite organization before activation. | | `building` | A deployment is being built. Check the [deployment logs](#deployment-logs) for more info. | | `processing` | The creation of a site deployment has begun and has not finished. | | `waiting` | The deployment is queued but has not been picked up for processing. | | `failed` | A deployment was not successful. Check the [deployment logs](#deployment-logs) for more info for debugging. | ### Deployment logs When you build a deployment, the logs generated will be saved for debugging purposes. You can find these build logs by navigating to the **Deployments** tab of your site, clicking the three-dots menu beside a deployment, and clicking **Logs**. {% only_dark %} ![Deployment logs](/images/docs/sites/dark/deployment-logs.png) {% /only_dark %} {% only_light %} ![Deployment logs](/images/docs/sites/deployment-logs.png) {% /only_light %} ### Create deployment To manually trigger a deployment of your app from the Appwrite Console, you can head to the **Deployments** tab of your site, click on the **Create deployment** button, and select one of the following: - **Git**: Lets you select a branch on your connected Git repo and whether you would like to activate the build post-deployment - **CLI**: Lets you run a [CLI command](/docs/products/sites/deploy-from-cli#cli) in your site's directory - **Manual**: Lets you upload a [.tar.gz file](/docs/products/sites/deploy-manually#manual-deployment) containing your site's build output {% only_dark %} ![Create deployment](/images/docs/sites/dark/create-deployment.png) {% /only_dark %} {% only_light %} ![Create deployment](/images/docs/sites/create-deployment.png) {% /only_light %} ### Cancel deployment If a site is being deployed and you wish to stop this deployment, you can head to the **Deployments** tab of your site, click on the three-dots menu, and click on the **Cancel** button. {% only_dark %} ![Cancel deployment](/images/docs/sites/dark/cancel-deployment.png) {% /only_dark %} {% only_light %} ![Cancel deployment](/images/docs/sites/cancel-deployment.png) {% /only_light %} ### Update deployment Some site settings require redeploying your site to be reflected in your active deployment. When you update a site by changing its **Git repository**, **Build settings**, and **Environment variables**, you must redeploy your site before those changes take effect. ### Redeploy After updating the configuration, redeploy your site for changes to take effect. You can also redeploy to retry failed builds. 1. Navigate to your site on Appwrite Console. 2. Under the **Deployments** tab, find the status of the current active deployment. 3. Redeploy by clicking the triple-dots beside a deployment and hitting the **Redeploy** button. {% only_dark %} ![Redeploy](/images/docs/sites/dark/redeploy.png) {% /only_dark %} {% only_light %} ![Redeploy](/images/docs/sites/redeploy.png) {% /only_light %} Redeployment behavior varies depending on how the initial deployment was created. {% info title="Benefits for Pro+ users" %} Users subscribed to the Appwrite Pro plan or above receive certain special benefits: - [Express builds](/changelog/entry/2024-08-10) for quicker deployments, resulting in reduced wait times and smoother workflows - Customizable [runtime specifications](/blog/post/introducing-new-compute-capabilities-appwrite-functions), allowing for tailored performance and resource allocation {% /info %} --- ## Develop Appwrite Sites https://appwrite.io/docs/products/sites/develop Appwrite allows you to host both statically-generated and server-rendered websites. [Static sites](/docs/products/sites/rendering/static) are websites that are pre-built and served as-is to clients. They do not execute server-side code on each request. They are ideal for use-cases such as [Single Page Applications (SPAs)](/docs/products/sites/rendering/static#running-spas-on-appwrite-sites), documentation sites, personal blogs, and portfolio websites. [Server-side rendered (SSR) sites](/docs/products/sites/rendering/ssr) generate content dynamically on the server and send fully rendered pages for each request. They are ideal for use-cases with substantial dynamic content or server-side processing such as e-commerce platforms, social media applications, content management systems (CMS), and real-time collaboration tools. You can configure your preferred rendering strategy through the following steps: 1. Navigate to your site on Appwrite Console. 2. Head to the **Settings** tab > **Build settings** section. 3. Select the SSR or the Static checkbox. 4. Confirm that the appropriate install command, build command, and output directory are set. 5. *(For SPAs)* Add a fallback file. 6. Click on the **Update** button and redeploy your site. {% only_dark %} ![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-ssr.png) {% /only_dark %} {% only_light %} ![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.png) {% /only_light %} ### **Accessing environment variables** Appwrite Sites lets you set static environment variables to pass constants and secrets such as API keys, connection strings, etc., to your sites. To set static environment variables, follow these steps: 1. Navigate to your site on Appwrite Console. 2. Head to the **Settings** tab > **Environment variables** section. 3. Click on the **Create variable** button and add the key and value of the environment variable. 4. Select the **Secret** checkbox if you want to prevent any team member from reading the value of the environment variable after creation. 5. Click on the **Create** button and redeploy the site. {% only_dark %} ![Environment variables](/images/docs/sites/dark/env-variables.png) {% /only_dark %} {% only_light %} ![Environment variables](/images/docs/sites/env-variables.png) {% /only_light %} You can also configure global variables that apply to all your sites in the **Settings** of your project. {% info title="Secret environment variables" %} Appwrite now allows you create secret environment variables, which are hidden from both the Appwrite Console and API. Once a variable is marked as secret, this action cannot be reversed. You can mark an environment variable as secret either at the time of creation or after it is created. {% /info %} #### Appwrite-specific environment variables Appwrite passes the following environment variables into every deployed site by default. {% partial file="sites-env-vars.md" /%} ### Timeouts Each request made to a path on an Appwrite Site has a set time limit, after which the request will timeout. Here are the steps to configure those timeout period: 1. Navigate to your site on Appwrite Console. 2. Head to the **Settings** tab > **Timeout** section. 3. Add an appropriate time limit (in seconds). {% only_dark %} ![Timeout](/images/docs/sites/dark/timeout.png) {% /only_dark %} {% only_light %} ![Timeout](/images/docs/sites/timeout.png) {% /only_light %} The default timeout is set at `15 seconds` and the maximum value possible is `30 seconds`. ### Project dependencies To install your dependencies before your site is built, you should add the relevant install command to your site’s build settings. Here are the steps to add the install command: 1. Navigate to your site on Appwrite Console. 2. Head to the **Settings** tab > **Build settings** section. 3. Confirm that the appropriate install command is set. {% only_dark %} ![Install command](/images/docs/sites/dark/build-settings-install-command.png) {% /only_dark %} {% only_light %} ![Install command](/images/docs/sites/build-settings-install-command.png) {% /only_light %} Make sure to include dependency files like `package.json` in your site's configured root directory. Do not include the dependency folders like `node_modules` in your site’s root directory. The dependencies installed for your local OS may not work in the site's environment. Your site's dependencies should be managed by the package manager of each language. We include the following package managers and setup commands by default. {% table %} -   {% width=48 %} - Framework - Default package manager - Install command - Build command - Output directory --- - {% icon icon="nextjs" size="m" /%} - **Next.js** - `npm` - `npm install` - `npm run build` - `./.next` --- - {% icon icon="nuxt" size="m" /%} - **Nuxt** - `npm` - `npm install` or `yarn install` - `npm run build` or `yarn build` - `./.output` --- - {% icon icon="svelte" size="m" /%} - **SvelteKit** - `npm` - `npm install` - `npm run build` - `./build` --- - {% icon icon="angular" size="m" /%} - **Angular** - `npm` - `npm install` - `npm run build` - `./dist/angular/browser` --- - {% icon icon="astro" size="m" /%} - **Astro** - `npm` - `npm install` - `npm run build` - `./dist` --- - {% only_dark %}{% icon_image src="/images/platforms/dark/remix.svg" alt="Remix logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/light/remix.svg" alt="Remix logo" size="m" /%}{% /only_light %} - **Remix** - `npm` - `npm install` - `npm run build` - `./build` --- - {% only_dark %}{% icon_image src="/images/platforms/dark/tanstack.svg" alt="TanStack Start logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/light/tanstack.svg" alt="TanStack Start logo" size="m" /%}{% /only_light %} - **TanStack Start** - `npm` - `npm install` - `npm run build` - `./dist` --- - {% icon icon="flutter" size="m" /%} - **Flutter** - `pub` - `flutter pub get` - `flutter build web --release -t lib/main.dart` - `./build/web` --- - {% icon icon="react-native" size="m" /%} - **React Native** - `npm` - `npm install` - `npm run build` - `./dist` --- - {% icon icon="js" size="m" /%} - **Other JavaScript** - `npm` - (Empty) - (Empty) - `./` {% /table %} --- ## Domains https://appwrite.io/docs/products/sites/domains Each deployed site can have its own domain, which can be Appwrite-generated or custom. You can use this domain to consume web apps deployed on Appwrite Sites. Appwrite generates TLS certificates to enforce HTTPS on all Appwrite Sites domains. These domains are safe to use and access in production. [Learn about Sites development >](/docs/products/sites/develop) ### Generated domains Each site automatically receives a unique Appwrite-generated domain that's ready to use immediately. 1. In the Appwrite Console's sidebar, click **Sites**. 2. Under the **Domains** tab, you'll find the domain generated by Appwrite. The domain usually has this format: ```bash https://64d4d22db370ae41a32e.appwrite.network ``` #### Branch and Commit URLs Additional to the site URL, Appwrite also generates a URL for the branch and commit that your site has been deployed from. The branch URL will remain consistent for all deployments made for code pushed to a specific branch, whereas the commit URL will be updated every time a new deployment is made via the Git integration (i.e. when code is pushed to your repo). {% only_dark %} ![Branch and commit URLs](/images/docs/sites/dark/branch-commit-urls.png) {% /only_dark %} {% only_light %} ![Branch and commit URLs](/images/docs/sites/branch-commit-urls.png) {% /only_light %} To find the branch and commit URLs of any deployment, follow these steps: 1. Navigate to your site on Appwrite Console. 2. Head to the **Deployments** tab and click on any deployment. 3. In the **Domains** section, click on the **+2** next to the mentioned domain. ### Add a custom domain You can add your own domain to your Appwrite site to provide a branded experience for your users. There are two ways to add a custom domain, depending on whether you're using a subdomain or an apex domain. #### Add an apex domain with NS records Apex domains (also known as root domains) are domains without a subdomain prefix, like `example.com` instead of `www.example.com`. Unlike subdomains, apex domains cannot use CNAME records due to DNS protocol limitations. To add an apex domain: 1. Navigate to your site in the Appwrite Console. 2. Head to the **Domains** tab and click on **Add domain**. 3. Enter your apex domain (e.g., `example.com`). 4. Select the appropriate domain rule type (Active deployment, Git branch, or Redirect) and configure its settings. See the [Domain rule types](#domain-rule-types) section for details. 5. Appwrite will provide NS record information. 6. Go to your domain registrar and update the NS records for your domain to point to `ns1.appwrite.zone` and `ns2.appwrite.zone`. 7. Return to the Appwrite Console and wait for the verification process to complete. DNS changes can take up to 48 hours to fully propagate across the internet. During this time, your domain might not be accessible or might show inconsistent behavior. ##### Why Appwrite uses NS records instead of A records By [DNS standards (RFC)](https://datatracker.ietf.org/doc/html/rfc1035), apex domains cannot use CNAME records, only A or AAAA records, which require pointing to fixed IP addresses. Using A records would lock Appwrite into specific IP addresses, limiting our ability to optimize routing, scale our infrastructure, or make changes as needed. To avoid this constraint and maintain flexibility, Appwrite offers DNS delegation through NS records so we can manage routing on your behalf. Some DNS providers support CNAME-like behavior at the apex through custom setups. If your provider allows it, you can point your apex domain to `appwrite.network` without using Appwrite's nameservers. Using Appwrite's DNS servers for your apex domain provides several benefits: - Proper SSL certificate management - Automatic DNS configuration - Secure and reliable DNS resolution When you change your domain's NS records, you're delegating DNS management to Appwrite. This means any existing DNS records (like MX records for email) will need to be recreated in Appwrite's DNS configuration. {% arrow_link href="/docs/products/network/dns" %} Learn more about Appwrite DNS server {% /arrow_link %} #### Add a subdomain with CNAME Subdomains (like `www.example.com` or `app.example.com`) are set up using CNAME records, which point to Appwrite's hostname. To add a subdomain: 1. Navigate to your site in the Appwrite Console. 2. Head to the **Domains** tab and click on **Add domain**. 3. Input your subdomain (e.g., `www.example.com`). 4. Select the appropriate domain rule type (Active deployment, Git branch, or Redirect) and configure its settings. See the [Domain rule types](#domain-rule-types) section for details. 5. Copy the specified **CNAME** record and add it to your domain registrar. 6. Return to the Site settings and wait for verification status. {% only_dark %} ![Add domain](/images/docs/sites/dark/add-domain.png) {% /only_dark %} {% only_light %} ![Add domain](/images/docs/sites/add-domain.png) {% /only_light %} DNS records can take up to 48 hours to propagate. Once verified, the domain is ready to use. #### Domain rule types When adding a custom domain to your Appwrite site, you'll need to select one of the following rule types that determine how your domain will behave: ##### Active deployment Points your domain to the latest deployed version of your site. This is the most common option for production domains. - When selected, your domain will always serve the most recent successful deployment - Any new deployments will automatically be available on this domain ##### Git branch Points your domain to a specific branch in your repository. This is useful for testing or staging environments. - When selected, you'll need to choose a specific branch from your connected repository - Your domain will always serve the latest successful deployment from that branch - This allows you to have different domains for different branches (e.g., staging.example.com for your staging branch) ##### Redirect Forwards all traffic from your domain to another URL. This is useful for domain migrations or creating shortcuts. - When selected, you'll need to specify the destination URL - You can choose from various HTTP status codes for the redirect: - 301 Moved permanently - 302 Found - 303 See other - 307 Temporary redirect - 308 Permanent redirect {% info title="Path and query parameters in redirects" %} When you redirect an added domain to another URL, any additional path and queries will be ignored. For example, if a domain `example.com` is set to redirect to `appwrite.io`, `example.com/docs?id=123` will also redirect to `appwrite.io`. {% /info %} --- ## Frameworks https://appwrite.io/docs/products/sites/frameworks Appwrite Sites allows web apps developed with a variety of frameworks to be hosted and served to your users. Appwrite Sites allows web apps developed with a variety of frameworks to be hosted and served to your users. When we say a framework is "supported," it means Appwrite can automatically detect, build, and optimize deployments for that framework with minimal configuration from you. ### Zero-configuration approach Appwrite Sites uses a zero-config approach to make deployments as frictionless as possible. When you deploy a project, Appwrite: 1. Automatically detects your framework based on your package dependencies and configuration files (like `next.config.js`, `nuxt.config.js`, etc.) 2. Selects one of the [SSR](/docs/products/sites/rendering/ssr) or [Static](/docs/products/sites/rendering/static) rendering strategies 3. Sets up the appropriate install command, build command, and output directory This means you can focus on building your application while Appwrite handles the deployment complexities. ### Supported frameworks {% table %} -   {% width=48 %} - Framework - Rendering strategy --- - {% icon icon="nextjs" size="m" /%} - [**Next.js**](/docs/products/sites/quick-start/nextjs) - `SSR` `Static` --- - {% icon icon="nuxt" size="m" /%} - [**Nuxt**](/docs/products/sites/quick-start/nuxt) - `SSR` `Static` --- - {% icon icon="svelte" size="m" /%} - [**SvelteKit**](/docs/products/sites/quick-start/sveltekit) - `SSR` `Static` --- - {% icon icon="angular" size="m" /%} - [**Angular**](/docs/products/sites/quick-start/angular) - `SSR` `Static` --- - {% icon icon="astro" size="m" /%} - [**Astro**](/docs/products/sites/quick-start/astro) - `SSR` `Static` --- - {% only_dark %}{% icon_image src="/images/platforms/dark/remix.svg" alt="Remix logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/light/remix.svg" alt="Remix logo" size="m" /%}{% /only_light %} - [**Remix**](/docs/products/sites/quick-start/remix) - `SSR` `Static` --- - {% only_dark %}{% icon_image src="/images/platforms/dark/tanstack.svg" alt="TanStack Start logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/light/tanstack.svg" alt="TanStack Start logo" size="m" /%}{% /only_light %} - [**TanStack Start**](/docs/products/sites/quick-start/tanstack-start) - `SSR` `Static` --- - {% icon icon="flutter" size="m" /%} - [**Flutter Web**](/docs/products/sites/quick-start/flutter) - `Static` --- - {% icon icon="react-native" size="m" /%} - [**React Native**](/docs/products/sites/quick-start/react-native) - `Static` --- - {% icon icon="js" size="m" /%} - [**Other JavaScript**](/docs/products/sites/quick-start/vanilla) - `Static` --- {% /table %} #### Using unsupported frameworks Even with "unsupported" frameworks, Appwrite will attempt to detect the correct build configuration. If your preferred framework isn't officially supported, you can still deploy it to Appwrite Sites using: 1. **Manual configuration**: You can specify a custom install command, build command, and output directory in your build setting by selecting the **Other** framework option. 2. **Static builds**: Most JavaScript frameworks (and some non-JavaScript ones) can generate static builds that work with Appwrite Sites. --- ## Instant rollbacks https://appwrite.io/docs/products/sites/instant-rollbacks If a site needs to be reverted to a previously functional state for any reason (runtime errors, security flaw, etc.), you can roll your site back to an existing ready deployment. Instant rollbacks don't delete, modify, or re-deploy your code. Instead, they simply change which deployment is being served to visitors. This makes rollbacks near-instantaneous, with zero downtime. ### Use instant rollbacks To use the instant rollback feature, follow these steps: 1. Navigate to your site on Appwrite Console. 2. Under the **Overview** tab, click on the **Instant Rollback** button. 3. Once the modal opens, click on the **Rollback** button. {% only_dark %} ![Instant rollback](/images/docs/sites/dark/instant-rollback.png) {% /only_dark %} {% only_light %} ![Instant rollback](/images/docs/sites/instant-rollback.png) {% /only_light %} This will allow you to select a previously active, ready deployment to promote to currently active state. --- ## Logs https://appwrite.io/docs/products/sites/logs Each time a URL path on an Appwrite Site is requested, a log is created. Each log has a unique ID. You can find site logs logged in the **Logs** tab. ### Logs table In your site's **Logs** tab, you will see a table of your recent logs. The following information is shown in this table: | Column | Description | | --- | --- | | Log ID | Unique identifier for each log | | Status code | The HTTP status of the request | | Created | Timestamp of when the log was created | | Method | The HTTP method used to create the request | | Path | The URL path the request was made to | | Duration | The time taken for the request | ### Log details When you click on a log, you will be shown a set of log details. {% only_dark %} ![Log details](/images/docs/sites/dark/log-details.png) {% /only_dark %} {% only_light %} ![Log details](/images/docs/sites/log-details.png) {% /only_light %} You can find both request and response information, including parameters and headers. {% info title="Response logs for SSR apps" %} If your app uses **SSR hosting** on Appwrite Sites, you can also observe `console.log` and `console.error` outputs in the response logs. {% /info %} ### Disable logs You can optionally disable logging for your site, which will exclude `console.log` and `console.error` outputs from the response logs and make site responses slightly faster. Here are the steps to disable logs: 1. Navigate to your site on Appwrite Console. 2. Under the **Settings** tab, find the **Logging** section. 3. Disable logs and click on the **Update** button. {% only_dark %} ![Logging settings](/images/docs/sites/dark/logging-settings.png) {% /only_dark %} {% only_light %} ![Logging settings](/images/docs/sites/logging-settings.png) {% /only_light %} ### Log retention Logs are not retained forever in order to be compliant with GDPR and other data privacy standards. Free plan organizations will retain logs for 24 hours, Pro plan organizations will retain logs for 7 days. If you need longer log retention, you can log to an Appwrite table. Remember to configure proper permissions and implement Appwrite Functions or other scheduled tasks to expire and clean up logs. --- ## Migrating from Vercel to Appwrite Sites https://appwrite.io/docs/products/sites/migrations/vercel This guide walks you through migrating from Vercel to Appwrite Sites, covering project setup, configuration, routing, and serverless functionality. ### Prerequisites Before starting your migration: - Have access to your Vercel project dashboard - Ensure you can modify your domain's DNS settings - Prepare your source code repository ### Platform differences Understanding the key differences between Vercel and Appwrite Sites will help you plan your migration effectively. {% table %} - Feature - Vercel - Appwrite Sites --- - DNS configuration - Uses A records for apex domains - Uses NS records for apex domains --- - Configuration approach - Platform-level configuration via vercel.json - Framework-native configuration with SSR support --- - Redirects - Platform-level path redirects via vercel.json - Domain-level redirects and framework-level path redirects {% /table %} ### Migration process Follow these steps to move your application from Vercel to Appwrite Sites. {% section id="create-project" step=1 title="Create an Appwrite project" %} Start by setting up a new project in Appwrite: 1. Sign in to the [Appwrite Console](https://cloud.appwrite.io) 2. Click **Create Project** 3. Enter a name for your project and click **Create** Your new project will serve as the container for your migrated site and any other Appwrite services you might need. {% /section %} {% section id="connect-repo" step=2 title="Connect your repository" %} Next, create a new site by connecting your existing repository: 1. In your Appwrite project, select **Sites** from the sidebar 2. Click **Create Site** 3. Select **Connect a repository** 4. Authenticate with GitHub 5. Select the repository containing your Vercel project 6. Choose your production branch (typically `main`) Appwrite will auto-detect your framework. Verify this is correct or select manually from the dropdown menu. {% /section %} ### Configure your domain One of the differences between Vercel and Appwrite is how they handle domain configuration for apex domains. Vercel uses A records for apex domains, while Appwrite uses nameserver (NS) records. This means you'll need to delegate DNS management to Appwrite. ### Migrating an apex domain {% section id="prepare-vercel-domain" step=1 title="Prepare your Vercel domain" %} Before migrating, row your current Vercel DNS configuration: 1. In Vercel, go to project settings > Domains 2. Note any custom DNS records you've configured (MX, TXT, etc.) 3. Don't remove the domain from Vercel until Appwrite is fully configured {% /section %} {% section id="add-apex-domain" step=2 title="Add domain to Appwrite Sites" %} Add your apex domain to your Appwrite site: 1. Navigate to your site > **Domains** tab 2. Click **Add domain** 3. Enter your apex domain (e.g., `example.com`) 4. Select **Active deployment** as the domain rule type 5. Note the NS records Appwrite provides: - `ns1.appwrite.zone` - `ns2.appwrite.zone` {% /section %} {% section id="update-nameservers" step=3 title="Update nameservers at your registrar" %} At your domain registrar: 1. Update your domain's nameservers to point to Appwrite's nameservers 2. If you had custom DNS records in Vercel (like MX records for email), you'll need to recreate these in Appwrite's DNS configuration {% info title="Important DNS change note" %} Changing nameservers delegates your entire domain's DNS management to Appwrite. DNS changes can take up to 48 hours to fully propagate. {% /info %} {% /section %} ### Migrating a subdomain For subdomains, both Vercel and Appwrite use CNAME records, making the migration process simpler. {% section id="add-subdomain" step=1 title="Add subdomain to Appwrite Sites" %} 1. Navigate to your site > **Domains** tab 2. Click **Add domain** 3. Enter your subdomain (e.g., `www.example.com`) 4. Select **Active deployment** as the domain rule type 5. Copy the CNAME value provided by Appwrite {% /section %} {% section id="update-dns-records" step=2 title="Update DNS records" %} At your domain registrar or DNS provider: 1. Create or update the CNAME record for your subdomain 2. Point it to the value provided by Appwrite 3. Wait for DNS propagation and verification {% /section %} ### Domain rule types When adding a domain in Appwrite, you can choose from three rule types: Active deployment, Git branch, or Redirect. {% arrow_link href="/docs/products/sites/domains#domain-rule-types" %} Learn more about domain rule types {% /arrow_link %} ### Configure build settings After setting up your project and domain, you'll need to configure your build settings to match your Vercel configuration. {% section id="build-settings" step=1 title="Set up build configuration" %} Navigate to your site > **Settings** > **Build settings** and configure the following: - **Framework:** Appwrite will attempt to automatically detect your framework. Verify and ensure it is the same framework as in Vercel. - **Install command:** Enter the same install command from Vercel - **Build command:** Enter the same build command from Vercel - **Output directory:** Enter the same output directory from Vercel - **Root directory:** If your app is in a monorepo subdirectory, specify the path - **Rendering:** Select the appropriate rendering mode (Static or SSR) {% only_dark %} ![Build settings](/images/docs/sites/dark/build-settings-install-command.png) {% /only_dark %} {% only_light %} ![Build settings](/images/docs/sites/build-settings-install-command.png) {% /only_light %} {% /section %} ### Framework defaults Appwrite automatically detects and applies default settings for popular frameworks like Next.js, Nuxt, SvelteKit, Angular, and Astro. {% arrow_link href="/docs/products/sites/develop#project-dependencies" %} Learn more about framework defaults and project dependencies {% /arrow_link %} ### Manage environment variables Environment variables require special attention during migration, as they control how your application behaves in different environments. {% section id="gather-env-vars" step=1 title="Gather variables from Vercel" %} Before migrating, row all your Vercel environment variables: 1. In Vercel, go to your project settings > **Environment Variables** 2. Row all variables, noting which are for development, preview, or production 3. Identify any system variables your application relies on {% info title="System variables" %} Vercel automatically provides system variables like `VERCEL_URL`. You'll need to adapt your code to use Appwrite's equivalent system variables. {% /info %} {% /section %} {% section id="set-env-vars" step=2 title="Set variables in Appwrite" %} 1. Navigate to your site > **Settings** > **Environment variables** 2. Click the **plus (+)** icon to add a new variable 3. Enter the key and value for each variable. You can optionally import a `.env` file. 4. Toggle **Secret** for sensitive variables that should be hidden {% only_dark %} ![Environment variables](/images/docs/sites/dark/env-variables.png) {% /only_dark %} {% only_light %} ![Environment variables](/images/docs/sites/env-variables.png) {% /only_light %} {% /section %} ### Appwrite system variables Appwrite automatically injects these variables into your site: {% partial file="sites-env-vars.md" /%} ### Handle redirects and rewrites One key difference between Vercel and Appwrite is how they handle redirects and rewrites. {% info title="Key difference" %} Vercel offers platform-level path redirects configured in `vercel.json`, while Appwrite provides domain-level redirects through the Domains tab and supports framework-level path redirects through your application code. {% /info %} {% section id="domain-redirects" step=1 title="Domain-level redirects" %} Appwrite's domain-level redirects are configured in the Domains tab of your site: 1. Navigate to your site > **Domains** tab 2. Click **Add domain** or select an existing domain 3. Choose **Redirect** as the domain rule type 4. Enter the destination URL and select an appropriate HTTP status code (301, 302, 303, 307, or 308) Domain redirects in Appwrite do not preserve path or query parameters. For example, if you redirect `example.com` to `appwrite.io`, then `example.com/docs?id=123` will redirect to `appwrite.io` (not `appwrite.io/docs?id=123`). {% /section %} {% section id="framework-redirects" step=2 title="Framework-level redirects" %} For path-based redirects, use your framework's built-in functionality: ### Next.js ```javascript // next.config.js module.exports = { async redirects() { return [ { source: '/old-path', destination: '/new-path', permanent: true, // 308 status code }, ]; }, }; ``` ### SvelteKit ```javascript // src/routes/+layout.server.js export function load({ url }) { if (url.pathname === '/old-path') { return { status: 301, redirect: '/new-path' }; } } ``` ### Nuxt ```javascript // nuxt.config.js export default { router: { extendRoutes(routes, resolve) { routes.push({ path: '/old-path', redirect: { to: '/new-path', statusCode: 301 } }); } } } ``` {% /section %} ### Migrate serverless functions There are two approaches to serverless functions when migrating from Vercel to Appwrite: {% info title="Framework API routes vs. standalone functions" %} When using frameworks like Next.js, Nuxt, or SvelteKit with SSR enabled, your API routes will work natively within Appwrite Sites, just as they do in Vercel. For standalone serverless functions or more complex use cases, you can use [Appwrite Functions](/docs/functions). {% /info %} {% section id="framework-api-routes" step=1 title="Framework API routes" %} For frameworks with built-in API routes: 1. **Next.js**: API routes in `/pages/api` or route handlers in `/app/api` work natively 2. **Nuxt**: Server API endpoints work as expected 3. **SvelteKit**: Server routes and API endpoints function normally No migration is needed for these framework-native API routes - they'll work automatically when you deploy your site with SSR enabled. {% /section %} {% section id="standalone-functions" step=2 title="Standalone Appwrite Functions" %} For standalone serverless functions or more complex use cases: 1. In your Appwrite project, go to **Functions** 2. Click **Create Function** 3. Select a runtime that matches your needs (Node.js, Python, PHP, Ruby, etc.) 4. Create your function code 5. Deploy your function {% /section %} {% arrow_link href="/docs/products/functions/quick-start" %} Learn how to create and deploy functions {% /arrow_link %} {% section id="update-endpoints" step=3 title="API endpoints usage" %} ### Framework API routes When using framework API routes within Appwrite Sites (with SSR enabled), your endpoints remain the same: - Vercel: `/api/hello` - Appwrite Sites: `/api/hello` (no change needed) Your existing API routes code will work without modification: ```javascript // /api/hello.js in Next.js (works the same in both Vercel and Appwrite Sites) export default function handler(req, res) { res.status(200).json({ message: 'Hello!' }); } ``` ### Standalone Appwrite Functions If you're using standalone Appwrite Functions (outside your site's codebase), you'll need to update your frontend code to use the Appwrite Functions API: ```javascript // Calling an Appwrite Function from your frontend import { Client, Functions } from 'appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('your-project-id'); const functions = new Functions(client); const response = await functions.createExecution({ functionId: 'your-function-id' }); ``` {% /section %} ### Handle middleware While Vercel offers platform-level Edge Middleware configured through `vercel.json`, Appwrite Sites fully supports framework-native middleware when using SSR. This means your existing middleware code will continue to work without modification. {% section id="framework-middleware" step=1 title="Framework-native middleware" %} ### Next.js ```javascript // middleware.js export function middleware(request) { // Your middleware logic } export const config = { matcher: '/path/:path*', }; ``` ### SvelteKit ```javascript // src/hooks.server.js export async function handle({ event, resolve }) { // Your middleware logic before response const response = await resolve(event); // Your middleware logic after response return response; } ``` ### Nuxt ```javascript // server/middleware/example.js export default defineEventHandler((event) => { // Your middleware logic }); ``` {% /section %} ### Next steps After completing your migration from Vercel to Appwrite Sites, we recommend: 1. **Test thoroughly** - Verify all routes, functionality, and environment-specific features 2. **Monitor performance** - Check that your site performs as expected on Appwrite 3. **Set up CI/CD** - Appwrite already provides git integration and deployment workflows, but you can also use GitHub Actions or any other CI/CD tool to automate your deployments. 4. **Explore Appwrite services** - Consider integrating with other Appwrite services like [Authentication](/docs/products/auth), [Databases](/docs/products/databases), and [Storage](/docs/products/storage) ### Conclusion This guide has outlined the key steps for migrating from Vercel to Appwrite Sites. You'll find that Git integration and deployment workflows remain largely familiar, making these aspects of migration more approachable for most projects. While domain configuration and platform-specific features like middleware require some adaptation, the framework-native approaches detailed in this guide help ensure a smooth transition. For additional help, refer to the [Sites documentation](/docs/products/sites) or reach out to the Appwrite community on [Discord](https://appwrite.io/discord). --- ## Previews https://appwrite.io/docs/products/sites/previews If you create a new Pull Request on the GitHub repo for your site, Appwrite Sites will create a preview deployment that you can view and test before promoting to production. ### Visit preview deployments To access a preview deployment, follow these steps: 1. Navigate to your site on Appwrite Console. 2. Under the **Deployments** tab, click on a ready deployment. 3. Click on the **Visit** button. This preview URL is also visible under the **Domains** section. {% only_dark %} ![Preview deployment](/images/docs/sites/dark/preview-deployment.png) {% /only_dark %} {% only_light %} ![Preview deployment](/images/docs/sites/preview-deployment.png) {% /only_light %} Appwrite Sites will then verify whether you are authorized to access the Appwrite project in which the site is deployed and allow you to access this preview accordingly. #### Deployments for GitHub Aside from the preview URL, Appwrite also generates a [URL for the branch and commit](/docs/products/sites/domains#branch-and-commit-urls) that your site has been deployed from. To access these URLs, you can head to the **Deployments** tab of your site, head to any active deployment created using the Git integration, and click on the **+2** next to the mentioned domain in the **Domains** section. You can also directly access the branch and commit that these deployments are created from, by clicking on **GitHub** under the **Source** section. {% only_dark %} ![GitHub as source](/images/docs/sites/dark/github-source.png) {% /only_dark %} {% only_light %} ![GitHub as source](/images/docs/sites/github-source.png) {% /only_light %} --- ## Start with Sites https://appwrite.io/docs/products/sites/quick-start You can create and execute your first Appwrite Site in minutes. ### Create site Before deploying your web app with Git, create a new Site attached to your GitHub repository. {% only_dark %} ![Create site wizard](/images/docs/sites/dark/create-site-wizard.png) {% /only_dark %} {% only_light %} ![Create site wizard](/images/docs/sites/create-site-wizard.png) {% /only_light %} 1. In the Appwrite Console's sidebar, click **Sites**. 2. Click on the **Create site** button. 3. After clicking on **Connect Git repository**, select your repository. 4. After connecting to GitHub, (optionally) add a name and site ID. 5. Verify that the correct framework is selected. 6. Confirm the install command, build command, and output directory in the build settings. Visit your preferred [framework quick-start](#framework-quick-starts) to learn more. 7. Add any environment variables required by the site. 6. The site will be created, and a build will begin. Once the build is completed, you'll have created your first site. You can use your site's **domain** to access the deployment. ### Framework quick-starts Learn how to quickly setup a web app developed using any of the following frameworks and deploy it on Appwrite Sites. {% cards %} {% cards_item href="/docs/products/sites/quick-start/nextjs" title="Next.js" icon="icon-nextjs" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/nuxt" title="Nuxt" icon="icon-nuxt" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/sveltekit" title="SvelteKit" icon="icon-svelte" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/angular" title="Angular" icon="icon-angular" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/remix" title="Remix" icon="web-icon-remix" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/astro" title="Astro" icon="icon-astro" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/react" title="React" icon="icon-react" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/vue" title="Vue.js" icon="web-icon-vue" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/flutter" title="Flutter" icon="icon-flutter" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/react-native" title="React Native" icon="icon-react-native" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/vanilla" title="Vanilla JS" icon="icon-js" %} {% /cards_item %} {% cards_item href="/docs/products/sites/quick-start/tanstack-start" title="TanStack Start" icon="web-icon-tanstack" %} {% /cards_item %} {% /cards %} ### Explore Use your first site as a springboard to explore the flexible and powerful features of Appwrite Sites. {% cards %} {% cards_item href="/docs/products/sites/templates" title="Templates" %} Get a template site up and running with a single click. {% /cards_item %} {% cards_item href="/docs/products/sites/develop" title="Develop" %} Learn about developing your own Appwrite Site. {% /cards_item %} {% cards_item href="/docs/products/sites/deploy-from-git" title="Deploy" %} Configure and deploy your Appwrite Site from Git. {% /cards_item %} {% cards_item href="/docs/products/sites/frameworks" title="Frameworks" %} Learn which frameworks are supported by Appwrite Sites. {% /cards_item %} {% /cards %} --- ## Deploy an Angular app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/angular {% section #step-1 step=1 title="Create Angular app" %} First, you must either create an Angular app or setup the [Angular starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm install -g @angular/cli@17 ng new my-app ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Angular** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Angular are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist/angular/browser` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Astro app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/astro {% section #step-1 step=1 title="Create Astro app" %} First, you must either create an Astro app or setup the [Astro starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm create astro@latest ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Astro** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Astro are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Flutter Web app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/flutter {% section #step-1 step=1 title="Create Flutter Web app" %} First, you must either create a Flutter Web app or setup the [Flutter Web starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash flutter create my_app ``` In case you have an existing Flutter app and want to add web support to it, you must run the following command in your project directory: ```bash flutter create . --platforms web ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Flutter Web** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Flutter Web are: - **Install command:** `flutter pub get` - **Build command:** `flutter build web --release -t lib/main.dart` - **Output directory:** `./build/web` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Next.js app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/nextjs {% section #step-1 step=1 title="Create Next.js app" %} {% info title="Full Next.js support available" %} Appwrite Sites fully supports Next.js out of the box. Unlike other non-Vercel hosting services, the Appwrite Edge runs in a container-based environment for Node.js (and soon Bun as well), managed by a control plane that automatically scales your app as needed. This means all Next.js features work without any extra configuration or the OpenNext adapter. {% /info %} First, you must either create a Next.js app or setup the [Next.js starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npx create-next-app@latest ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Next.js** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Next.js are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./.next` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Nuxt app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/nuxt {% section #step-1 step=1 title="Create Nuxt app" %} First, you must either create a Nuxt app or setup the [Nuxt starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm create nuxt my-app ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Nuxt** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Nuxt are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./.output` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a React app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/react {% section #step-1 step=1 title="Create React app" %} First, you must either create a React app or setup the [React starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm create vite@latest my-app -- --template react ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **React** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for React are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a React Native app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/react-native {% section #step-1 step=1 title="Create React Native app" %} First, you must either create a React Native app or setup the [React Native starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npx create-expo-app my-app ``` Once the app is created, navigate to the project directory, open the `package.json` file and add the following line under `scripts`: ```json "build": "expo export --platform web" ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **React Native** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for React Native are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Remix app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/remix {% section #step-1 step=1 title="Create Remix app" %} First, you must either create a Remix app or setup the [Remix starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npx create-remix@latest ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Remix** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Remix are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./build` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a SvelteKit app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/sveltekit {% section #step-1 step=1 title="Create SvelteKit app" %} First, you must either create a SvelteKit app or setup the [SvelteKit starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npx sv create ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **SvelteKit** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for SvelteKit are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./build` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a TanStack Start app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/tanstack-start {% section #step-1 step=1 title="Create TanStack Start app" %} First, you must either create a TanStack Start app or setup the [TanStack Start starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm create @tanstack/start@latest ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **TanStack Start** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for TanStack Start are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist` (if you're using Nitro v2 or v3, this should be `./.output`) 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Vanilla JS app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/vanilla {% section #step-1 step=1 title="Create web app" %} Open your terminal, and run the following command. ```bash mkdir my-app cd my-app ``` In this directory, create two files with the following code: - `index.html` ```html <html> <head> <script type="module" src="/app.js"></script> </head> <body> <h1>Demo App</h1> <button>Click Me!</button> </body> </html> ``` - `app.js` ```js row.querySelector("button").addEventListener("click", () => { alert("Hello World!"); }); ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Other** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The build settings for this app will be: - **Install command:** `N/A` (leave empty, unless you have installed any external packages) - **Build command:** `N/A` (leave empty, unless you have installed any external packages) - **Output directory:** `./` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Deploy a Vue.js app to Appwrite Sites https://appwrite.io/docs/products/sites/quick-start/vue {% section #step-1 step=1 title="Create Vue.js app" %} First, you must either create a Vue.js app or setup the [Vue.js starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash npm create vue@latest ``` Push this project to a [GitHub repository](https://github.com/new). {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io). {% only_dark %} ![Create Appwrite project](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create Appwrite project](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. {% /section %} {% section #step-3 step=3 title="Create site" %} Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Vue.js** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Vue.js are: - **Install command:** `npm install` - **Build command:** `npm run build` - **Output directory:** `./dist` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. {% /section %} {% section #step-4 step=4 title="Visit site" %} After successful deployment, click on the **Visit site** button. {% /section %} --- ## Rendering https://appwrite.io/docs/products/sites/rendering Rendering refers to how your web application's content is processed and delivered to users. Appwrite Sites supports two primary rendering strategies, each with its own advantages and use cases. Understanding these strategies will help you choose the right approach for your project and optimize for performance, SEO, and user experience. {% cards %} {% cards_item href="/docs/products/sites/rendering/static" title="Static" %} Host a static site or SPA {% /cards_item %} {% cards_item href="/docs/products/sites/rendering/ssr" title="Server Side Rendering" %} Host an SSR site {% /cards_item %} {% /cards %} ### Differences There are several differences between how static hosting and SSR work on Appwrite Sites. | Static/SPA/PWA | SSR | |---|---| | Pages are rendered at build time only | Pages are rendered every time a request is made to the server | | All `console.log` and `console.error` outputs at run-time are be displayed in the browser console | `console.log` and `console.error` outputs on server-side functions in the web app will be displayed in the response logs on Appwrite | | The default `404` error page is Appwrite-branded (can be updated) | The default `404` error page is taken from the framework | | Faster cold starts | Slower cold starts | | Can access environment variables only during build-time | Can access environment variables during build-time and run-time | | Supported for all frameworks | Limited support for certain frameworks (learn more on [Frameworks page](/docs/products/sites/frameworks)) | ### Choosing the right approach When deciding between static and SSR for your Appwrite Sites project, consider these factors: #### Use static hosting when: - Your content doesn't change frequently - You want maximum performance with minimal server load - Your site is primarily client-side with limited data requirements - SEO is important but your content is relatively stable - You need compatibility with any web framework #### Use SSR when: - Your content changes frequently or is user-specific - You need server-side access to data before rendering - SEO is critical for dynamic, frequently changing content - You want to reduce client-side JavaScript load - You're using a supported SSR framework (Next.js, Nuxt, etc.) Many modern applications use a hybrid approach, leveraging static generation for stable content and SSR for dynamic pages. Some frameworks (like Next.js, Nuxt, and SvelteKit support) both approaches within the same application. The SSR rendering strategy in Appwrite Sites supports such applications. --- ## Server Side Rendering https://appwrite.io/docs/products/sites/rendering/ssr Server Side Rendering (SSR) apps generate HTML content dynamically on the server for each request and send fully rendered pages to the browser. This approach improves performance for the initial load and enhances SEO since search engines can easily index the content. While SSR can be slightly slower than static apps due to server-side processing, it provides a good balance between performance and interactivity. Since Appwrite's [CDN](/docs/products/network/cdn) supports dynamic content delivery, any server-side processing implemented in your site will be executed at your user's nearest edge location. The CDN also uses advanced compression algorithms to reduce data transfer sizes and improve delivery times of your site content. Any data relevant to other Appwrite products that you have integrated in your site, such as Auth, Databases, Storage, Functions, and Messaging, will be served from your project's pre-selected [region](/docs/products/network/regions). ### Configuring your Appwrite Site to use SSR When Appwrite builds your site for the first time, it scans your project's configuration files to determine whether the website should be rendered as static pages or using SSR. If you need to manually update these settings, here are the steps to do so: 1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section 2. Select the **Server side rendering** checkbox (you may need to update your project codebase depending on your framework option) 3. Confirm that the appropriate install command, build command, and output directory are set 4. Click on the **Update** button and redeploy your site {% only_dark %} ![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-ssr.png) {% /only_dark %} {% only_light %} ![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.png) {% /only_light %} #### Enabling SSR builds on your web app To enable SSR builds for your web app, you may need to make some additional updates in case of the following frameworks: {% tabs %} {% tabsitem #analog title="Analog" %} Set `ssr: true` in `analog` plugin in the `vite.config.ts` file. {% /tabsitem %} {% tabsitem #angular title="Angular" %} Ensure the `src/server.ts` file uses `@angular/ssr/node` package. {% /tabsitem %} {% tabsitem #nextjs title="Next.js" %} The following output modes are supported: - **Default mode**: No `output` configuration needed in `next.config.js`. - **Standalone mode**: Set `output` to `standalone` in `next.config.js` for smaller build sizes, lesser build times and cold start times. {% /tabsitem %} {% tabsitem #nuxt title="Nuxt" %} Set build command to `npm run build` in site settings. {% /tabsitem %} {% tabsitem #sveltekit title="SvelteKit" %} Use `@sveltejs/adapter-node` adapter in the `svelte.config.js` file. {% /tabsitem %} {% tabsitem #astro title="Astro" %} Use `@astrojs/node` adapter in the `astro.config.mjs` file. {% /tabsitem %} {% tabsitem #remix title="Remix" %} Ensure the `entry.server.tsx` file uses `@remix-run/node` package. {% /tabsitem %} {% tabsitem #tanstack-start title="TanStack Start" %} No additional configuration is needed as SSR is enabled by default. {% /tabsitem %} {% /tabs %} ### Appwrite-specific environment variables You can [access several environment variables](/docs/products/sites/develop#accessing-environment-variables) pertaining to your Appwrite project in SSR apps. {% partial file="sites-env-vars.md" /%} --- ## Static https://appwrite.io/docs/products/sites/rendering/static Static apps, also known as static websites, consist of pre-built HTML, CSS, and JavaScript files that are served to users without any backend processing. These apps do not execute server-side code on each request, meaning the content remains the same until manually updated or rebuilt. Since the pages are pre-generated, static apps offer incredibly fast load times. However, they lack dynamic interactivity and are best suited for use cases like personal portfolios, documentation sites, and landing pages. All static content served from Appwrite's [CDN](/docs/products/network/cdn) is optimized using advanced compression algorithms to reduce data transfer sizes and then served from your user's nearest edge location. ### Configuring your Appwrite Site to use static hosting When Appwrite builds your site for the first time, it scans your project's configuration files to determine whether the website should be rendered as static pages or using SSR. If you need to manually update these settings, here are the steps to do so: 1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section 2. Select the **Static site** checkbox (you may need to update your project codebase depending on your framework option) 3. Confirm that the appropriate install command, build command, and output directory are set 4. Click on the **Update** button and redeploy your site {% only_dark %} ![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-static.png) {% /only_dark %} {% only_light %} ![Rendering strategy](/images/docs/sites/build-settings-rendering-static.png) {% /only_light %} #### Enabling static builds on your web app {% #enabling-static-builds %} To enable static builds for your web app, you may need to make some additional updates in case of the following frameworks: {% tabs %} {% tabsitem #analog title="Analog" %} Set `static: true` in `analog` plugin in the `vite.config.ts` file. {% /tabsitem %} {% tabsitem #nextjs title="Next.js" %} Set `output: export` in the `next.config.js`. {% /tabsitem %} {% tabsitem #nuxt title="Nuxt" %} Set build command to `npm run generate` in site settings. {% /tabsitem %} {% tabsitem #sveltekit title="SvelteKit" %} Use `@sveltejs/adapter-static` adapter in the `svelte.config.js` file. {% /tabsitem %} {% tabsitem #astro title="Astro" %} Ensure you don't set `adapter` in the `astro.config.mjs` file. {% /tabsitem %} {% tabsitem #remix title="Remix" %} Set `ssr: false` in `remix` plugin in the `vite.config.ts` file. {% /tabsitem %} {% tabsitem #tanstack-start title="TanStack Start" %} Add [`prerender` option](https://tanstack.com/start/latest/docs/framework/react/guide/static-prerendering#prerendering) with the `enabled: true` option set in the `tanstackStart` configuration in the `vite.config.ts` file. {% /tabsitem %} {% /tabs %} ### Running SPAs on Appwrite Sites Single Page Applications (or SPAs) are web applications that load a single HTML page and dynamically update content using JavaScript, typically leveraging frameworks like React, Vue, or Angular. Unlike traditional websites, SPAs do not require full-page reloads; instead, they use client-side routing to modify the URL and fetch new content asynchronously from APIs. This results in a smoother, more app-like user experience. Appwrite Sites allows hosting SPAs through the static hosting method. Instead of returning all pre-rendered HTML pages, it allows your client to download relevant JavaScript files, which can then run in the browser. #### Configuring an Appwrite Site to run as an SPA To configure an Appwrite Site to run as an SPA, you must do the following: 1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section 2. Select the **Static site** checkbox (you may need to update your project codebase depending on your framework option) 3. Confirm that the appropriate install command, build command, and output directory are set 4. Provide a fallback file for advanced routing and proper page handling. 5. Click on the **Update** button and redeploy your site. --- ## Templates https://appwrite.io/docs/products/sites/templates Appwrite provides a variety of Site Templates to help you jump-start your web app development. ### Find templates You can find all available templates by navigating to the Appwrite Console under your project > **Sites** > **Templates**. {% only_dark %} ![Site templates](/images/docs/sites/dark/site-templates.png) {% /only_dark %} {% only_light %} ![Site templates](/images/docs/sites/site-templates.png) {% /only_light %} You can filter sites by searching, filter by use case, or filter by framework. Click **Create site** to create a site from a template. ### Create with templates The create site wizard for templates will include the following steps: #### Configure site details Pick a display name for your site and an ID. You will later use the ID to programmatically configure the site. #### Setup a GitHub repo You can choose to clone a new repository to your GitHub profile or organization or to connect to an existing repository. If you choose to connect to an existing repository intead of creating a new one, you must specify the output folder of your build under the [Branch](#select-a-production-branch) step. #### Configure repository Add the name of the repository you'd either like to create (if creating new repo) or add the source code to (if choosing existing repo). The connected repository will hold the source code for your site. When the code in this repository is updated, new deployments will be created. #### Select production branch Select the production branch, root directory, and the silent mode setting for the repo you selected. The production branch specifies the branch connected to your Appwrite Site. When new commits are made to this branch, a new deployment is automatically created and deployed. The root directory specifies the folder containing the source code of your your site. When a PR is made to the branch, a new deployment is built and a preview URL becomes available. A comment is made to your PR about the build unless you enable **Silent mode**. #### Add required environment variables Appwrite Sites uses environment variables to pass constants and secrets to your sites. You can provide information like API keys and other secrets in this step. #### Configure domain name Configure the subdomain of the Appwrite Site to be deployed. Once deployment completes, your application will be available to access on this domain. You can also [add a custom domain](/docs/products/sites/domains#add-a-custom-domain) to this site later. --- ## Storage https://appwrite.io/docs/products/storage Appwrite Storage allows you to manage files in your project. You can use it to store images, videos, rows, and other files for your projects. It provides APIs to upload, download, delete, and list files, with many added utilities. {% info title="Looking for a database?" %} Appwrite Storage stores files like images, PDFs or videos. If you need to store data like profiles, recipes, or transactions, use [Appwrite Databases](/docs/products/databases). {% /info %} ### Get started {% #get-started %} Get started with Appwrite Storage. Learn to setup up a bucket, upload, and download your first file. {% arrow_link href="/docs/products/storage/quick-start" %} Quick start {% /arrow_link %} --- ## Buckets https://appwrite.io/docs/products/storage/buckets Storage buckets are a group of files, similar to tables in Appwrite Databases. Buckets let you limit file size and extensions, whether or not to encrypt the files, and more. ### Create Bucket {% #create-bucket %} You can create your bucket from the Appwrite Console, a [Server SDK](/docs/sdks#server), or the [CLI](/docs/tooling/command-line/buckets). {% tabs %} {% tabsitem #console title="Console" %} You can create a bucket by heading to the **Storage** page and clicking **Create bucket**. {% only_dark %} ![Create bucket on console](/images/docs/storage/dark/create-bucket.png) {% /only_dark %} {% only_light %} ![Create bucket on console](/images/docs/storage/create-bucket.png) {% /only_light %} {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can also create tables programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys). {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const storage = new sdk.Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = storage.createBucket({ bucketId: '<BUCKET_ID>', name: '<NAME>' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); let storage = new sdk.Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; let promise = storage.createBucket({ bucketId: '<BUCKET_ID>', name: '<NAME>' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ```php <?php use Appwrite\Client; use Appwrite\Services\Storage; $client = new Client(); $client ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<PROJECT_ID>') // Your project ID ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; $storage = new Storage($client); $result = $storage->createBucket('<BUCKET_ID>', '<NAME>'); ``` ```python from appwrite.client import Client from appwrite.services.storage import Storage client = Client() (client .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key ) storage = Storage(client) result = storage.create_bucket('<BUCKET_ID>', '<NAME>') ``` ```ruby require 'Appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<PROJECT_ID>') # Your project ID .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key storage = Storage.new(client) response = storage.create_bucket(bucket_id: '<BUCKET_ID>', name: '<NAME>') puts response.inspect ``` ```csharp using Appwrite; using Appwrite.Services; using Appwrite.Models; var client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<PROJECT_ID>") // Your project ID .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key var storage = new Storage(client); Bucket result = await storage.CreateBucket( bucketId: "<BUCKET_ID>", name: "<NAME>"); ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; Future result = storage.createBucket( bucketId: '<BUCKET_ID>', name: '<NAME>', ); result .then((response) { print(response); }).catchError((error) { print(error.response); }); } ``` ```kotlin import io.appwrite.Client import io.appwrite.services.Storage val client = Client(context) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key val storage = Storage(client) val response = storage.createBucket( bucketId = "<BUCKET_ID>", name = "<NAME>", ) ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key Storage storage = new Storage(client); storage.createBucket( "<BUCKET_ID>", "<NAME>", new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key let storage = Storage(client) let bucket = try await storage.createBucket( bucketId: "<BUCKET_ID>", name: "<NAME>" ) ``` {% /multicode %} You can also configure permission, file size and extension restrictions, and more in the `createBucket` method, learn more about the `createBucket` in the [API references](/docs/references/cloud/server-nodejs/storage#createBucket). {% /tabsitem %} {% tabsitem #cli title="CLI" %} Create a bucket using the CLI command `appwrite init buckets`. ```sh appwrite init buckets ``` This will initialize your bucket in your `appwrite.config.json` file. To push your initialized bucket, use the `appwrite push buckets`. ```sh appwrite push buckets ``` This will create your bucket in the Console with all of your `appwrite.config.json` configurations. {% arrow_link href="/docs/tooling/command-line/buckets#commands" %} Learn more about the CLI buckets commands {% /arrow_link %} {% /tabsitem %} {% /tabs %} ### Permissions {% #permissions %} Appwrite uses permissions to control file access. For security, only users that are granted permissions can access a file. This helps prevent accidental data leaks by forcing you to make more concious decisions around permissions. By default, Appwrite doesn't grants permissions to any users when a new bucket is created. This means users can't create new files or read, update, and delete existing files. [Learn about configuring permissions](/docs/products/storage/permissions). ### Encryption {% #encryption %} Appwrite provides added security settings for your buckets. Enable encryption under your bucket's **Settings** > **Security settings**. You can enable encryption to encrypt files in your buckets. If your files are leaked, encrypted files cannot be read by the malicious actor. Files bigger than 20MB cannot be encrypted. ### Compression {% #compression %} Appwrite allows you to compress your files. Two algorithms are available, which are [gzip](https://www.gzip.org/) and [zstd](https://github.com/facebook/zstd). You can enable compress under your bucket's **Settings** > **Compression**. For files larger than 20MB, compression will be skipped even when enabled. ### Maximum file size {% #max-size %} Limit the maximum file size allowed in the bucket to prevent abuse. You can configure maximum file size under your bucket's **Settings** > **Maximum file size**. ### File extensions {% #extensions %} Limit the file extensions allowed in the bucket to prevent abuse. A maximum of 100 file extensions can be added. Leave blank to allow all file types. You can configure maximum file size under your bucket's **Settings** > **File extensions**. --- ## File tokens https://appwrite.io/docs/products/storage/file-tokens File tokens are a type of secret that allow you to share files publicly with anyone. By using file tokens, you can let any external user access your file without having to configure bucket or file permissions. File tokens can either be set to expire on a specific date or work indefinitely. ### File tokens vs secure cookies Currently, Appwrite uses secure cookies to manage sessions for users, which are essential for any Appwrite products with permissions configured. However, because the cookies sent to the users of apps consuming the Appwrite API are considered third-party cookies, certain browsers tend to block them due to their default privacy settings, creating a bad user experience. One way to circumvent this issue in the past was to connect a custom domain to your Appwrite project, as browsers don't inherently block any cookies returned by subdomains of your app. File tokens offer an alternative, simpler path for file sharing in Appwrite Storage, as they don't depend on the session for authorization to share data. ### Create file tokens To create a file token, you must [upload a file](/docs/products/storage/upload-download) to a [storage bucket](/docs/products/storage/buckets). {% tabs %} {% tabsitem #console title="Console" %} Head to the **Storage** page, open a file inside a bucket, scroll down to the **File tokens** section, and click on the **Create file token** button. {% only_dark %} ![Create file token](/images/docs/storage/dark/create-file-token.png) {% /only_dark %} {% only_light %} ![Create file token](/images/docs/storage/create-file-token.png) {% /only_light %} You can then click on the three-dots menu, click on **Copy URL** and get the token-based preview, view, and download URLs for the file. {% only_dark %} ![Copy file token-based URL](/images/docs/storage/dark/copy-file-token-url.png) {% /only_dark %} {% only_light %} ![Copy file token-based URL](/images/docs/storage/copy-file-token-url.png) {% /only_light %} {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can create file tokens programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#createFileToken). Appwrite's Server SDKs require an [API key](/docs/advanced/platform/api-keys) with the `tokens.write` scope enabled. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new sdk.Tokens(client); const result = await tokens.createFileToken( '<BUCKET_ID>', // bucketId '<FILE_ID>', // fileId '' // expire (optional) ); ``` ```server-python from appwrite.client import Client from appwrite.services.tokens import Tokens client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<YOUR_PROJECT_ID>') # Your project ID client.set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens(client) result = tokens.create_file_token( bucket_id = '<BUCKET_ID>', file_id = '<FILE_ID>', expire = '' # optional ) ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key Tokens tokens = Tokens(client); ResourceToken result = await tokens.createFileToken( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', expire: '', // (optional) ); ``` ```server-php <?php use Appwrite\Client; use Appwrite\Services\Tokens; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<YOUR_PROJECT_ID>') // Your project ID ->setKey('<YOUR_API_KEY>'); // Your secret API key $tokens = new Tokens($client); $result = $tokens->createFileToken( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', expire: '' // optional ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<YOUR_PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens.new(client) result = tokens.create_file_token( bucket_id: '<BUCKET_ID>', file_id: '<FILE_ID>', expire: '' # optional ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<YOUR_PROJECT_ID>") // Your project ID .SetKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); ResourceToken result = await tokens.CreateFileToken( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>", expire: "" // optional ); ``` ```server-deno import { Client, Tokens } from "npm:node-appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new Tokens(client); const response = await tokens.createFileToken( '<BUCKET_ID>', // bucketId '<FILE_ID>', // fileId '' // expire (optional) ); ``` ```server-go package main import ( "fmt" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tokens" ) func main() { client := client.NewClient() client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint client.SetProject("<YOUR_PROJECT_ID>") // Your project ID client.SetKey("<YOUR_API_KEY>") // Your secret API key service := tokens.NewTokens(client) response, error := service.CreateFileToken( "<BUCKET_ID>", "<FILE_ID>", tokens.WithCreateFileTokenExpire(""), ) if error != nil { panic(error) } fmt.Println(response) } ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key let tokens = Tokens(client) let resourceToken = try await tokens.createFileToken( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>", expire: "" // optional ) ``` ```server-kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Tokens val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key val tokens = Tokens(client) val response = tokens.createFileToken( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>", expire = "" // optional ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Tokens; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); tokens.createFileToken( "<BUCKET_ID>", // bucketId "<FILE_ID>", // fileId "", // expire (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```server-graphql mutation { tokensCreateFileToken( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>", expire: "" ) { _id _createdAt resourceId resourceType expire secret accessedAt } } ``` ```server-rest POST /v1/tokens/buckets/{bucketId}/files/{fileId} HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Key: <YOUR_API_KEY> { "expire": } ``` {% /multicode %} The created token can then be used along with Appwrite Storage's [view](/docs/references/cloud/server-nodejs/storage#getFileView), [preview](/docs/references/cloud/server-nodejs/storage#getFilePreview), and [download](/docs/references/cloud/server-nodejs/storage#getFileDownload) endpoints. {% /tabsitem %} {% /tabs %} ### List all file tokens You can use the Appwrite Console or one of the Server SDKs to view all created file tokens, their expiry dates and the time each token was last accessed at. {% tabs %} {% tabsitem #console title="Console" %} Head to the **Storage** page, open a file inside a bucket, and scroll down to the **File tokens** section. {% only_dark %} ![List of file tokens](/images/docs/storage/dark/list-file-tokens.png) {% /only_dark %} {% only_light %} ![List of file tokens](/images/docs/storage/list-file-tokens.png) {% /only_light %} {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can list all file tokens programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#list). Appwrite's Server SDKs require an [API key](/docs/advanced/platform/api-keys) with the `tokens.read` scope enabled. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new sdk.Tokens(client); const result = await tokens.list( '<BUCKET_ID>', // bucketId '<FILE_ID>', // fileId [] // queries (optional) ); ``` ```server-python from appwrite.client import Client from appwrite.services.tokens import Tokens client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<YOUR_PROJECT_ID>') # Your project ID client.set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens(client) result = tokens.list( bucket_id = '<BUCKET_ID>', file_id = '<FILE_ID>', queries = [] # optional ) ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key Tokens tokens = Tokens(client); ResourceTokenList result = await tokens.list( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', queries: [], // (optional) ); ``` ```server-php <?php use Appwrite\Client; use Appwrite\Services\Tokens; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<YOUR_PROJECT_ID>') // Your project ID ->setKey('<YOUR_API_KEY>'); // Your secret API key $tokens = new Tokens($client); $result = $tokens->list( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', queries: [] // optional ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<YOUR_PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens.new(client) result = tokens.list( bucket_id: '<BUCKET_ID>', file_id: '<FILE_ID>', queries: [] # optional ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<YOUR_PROJECT_ID>") // Your project ID .SetKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); ResourceTokenList result = await tokens.List( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>", queries: new List<string>() // optional ); ``` ```server-deno import { Client, Tokens } from "npm:node-appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new Tokens(client); const response = await tokens.list( '<BUCKET_ID>', // bucketId '<FILE_ID>', // fileId [] // queries (optional) ); ``` ```server-go package main import ( "fmt" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tokens" ) func main() { client := client.NewClient() client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint client.SetProject("<YOUR_PROJECT_ID>") // Your project ID client.SetKey("<YOUR_API_KEY>") // Your secret API key service := tokens.NewTokens(client) response, error := service.List( "<BUCKET_ID>", "<FILE_ID>", tokens.WithListQueries([]interface{}{}), ) if error != nil { panic(error) } fmt.Println(response) } ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key let tokens = Tokens(client) let resourceTokenList = try await tokens.list( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>", queries: [] // optional ) ``` ```server-kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Tokens val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key val tokens = Tokens(client) val response = tokens.list( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>", queries = listOf() // optional ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Tokens; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); tokens.list( "<BUCKET_ID>", // bucketId "<FILE_ID>", // fileId listOf(), // queries (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```server-rest GET /v1/tokens/buckets/{bucketId}/files/{fileId} HTTP/1.1 Host: cloud.appwrite.io X-Appwrite-Response-Format: 1.7.0 X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Key: <YOUR_API_KEY> ``` {% /multicode %} Using the token IDs, you can also get data pertaining to an [individual file token](/docs/references/cloud/server-nodejs/tokens#get). {% multicode %} ```server-nodejs const token = await tokens.get( '<TOKEN_ID>' // tokenId ); ``` ```server-python token = tokens.get( token_id = '<TOKEN_ID>' ) ``` ```server-dart ResourceToken token = await tokens.get( tokenId: '<TOKEN_ID>', ); ``` ```server-php $token = $tokens->get( tokenId: '<TOKEN_ID>' ); ``` ```server-ruby token = tokens.get( token_id: '<TOKEN_ID>' ) ``` ```server-dotnet ResourceToken token = await tokens.Get( tokenId: "<TOKEN_ID>" ); ``` ```server-deno const token = await tokens.get( '<TOKEN_ID>' // tokenId ); ``` ```server-go token, error := service.Get( "<TOKEN_ID>", ) if error != nil { panic(error) } fmt.Println(token) ``` ```server-swift let resourceToken = try await tokens.get( tokenId: "<TOKEN_ID>" ) ``` ```server-kotlin val token = tokens.get( tokenId = "<TOKEN_ID>" )``` ```server-java tokens.get( "<TOKEN_ID>", // tokenId new CoroutineCallback<>((token, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(token); }) );``` ```server-rest GET /v1/tokens/{tokenId} HTTP/1.1 Host: cloud.appwrite.io X-Appwrite-Response-Format: 1.7.0 X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Key: <YOUR_API_KEY> ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Update file token expiry File tokens can be set to expire on a specific date or stay active forever. This helps keep your files secure by making sure access ends after a set time. While the expiry of a file token is set at the time of creation, you can update it later. {% tabs %} {% tabsitem #console title="Console" %} Head to the **Storage** page, open a file inside a bucket, and scroll down to the **File tokens** section. Click on the three-dots menu next to the created file token and click on **Edit expiry**. {% only_dark %} ![Update file token expiry](/images/docs/storage/dark/update-file-token-expiry.png) {% /only_dark %} {% only_light %} ![Update file token expiry](/images/docs/storage/update-file-token-expiry.png) {% /only_light %} {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can update file token expiry programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#update). Appwrite's Server SDKs require an [API key](/docs/advanced/platform/api-keys) with the `tokens.write` scope enabled. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new sdk.Tokens(client); const result = await tokens.update( '<TOKEN_ID>', // tokenId '' // expire (optional) ); ``` ```server-python from appwrite.client import Client from appwrite.services.tokens import Tokens client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<YOUR_PROJECT_ID>') # Your project ID client.set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens(client) result = tokens.update( token_id = '<TOKEN_ID>', expire = '' # optional ) ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key Tokens tokens = Tokens(client); ResourceToken result = await tokens.update( tokenId: '<TOKEN_ID>', expire: '', // (optional) ); ``` ```server-php <?php use Appwrite\Client; use Appwrite\Services\Tokens; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<YOUR_PROJECT_ID>') // Your project ID ->setKey('<YOUR_API_KEY>'); // Your secret API key $tokens = new Tokens($client); $result = $tokens->update( tokenId: '<TOKEN_ID>', expire: '' // optional ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<YOUR_PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens.new(client) result = tokens.update( token_id: '<TOKEN_ID>', expire: '' # optional ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<YOUR_PROJECT_ID>") // Your project ID .SetKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); ResourceToken result = await tokens.Update( tokenId: "<TOKEN_ID>", expire: "" // optional ); ``` ```server-deno import { Client, Tokens } from "npm:node-appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new Tokens(client); const response = await tokens.update( '<TOKEN_ID>', // tokenId '' // expire (optional) ); ``` ```server-go package main import ( "fmt" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tokens" ) func main() { client := client.NewClient() client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint client.SetProject("<YOUR_PROJECT_ID>") // Your project ID client.SetKey("<YOUR_API_KEY>") // Your secret API key service := tokens.NewTokens(client) response, error := service.Update( "<TOKEN_ID>", tokens.WithUpdateExpire(""), ) if error != nil { panic(error) } fmt.Println(response) } ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key let tokens = Tokens(client) let resourceToken = try await tokens.update( tokenId: "<TOKEN_ID>", expire: "" // optional ) ``` ```server-kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Tokens val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key val tokens = Tokens(client) val response = tokens.update( tokenId = "<TOKEN_ID>", expire = "" // optional ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Tokens; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); tokens.update( "<TOKEN_ID>", // tokenId "", // expire (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```server-graphql mutation { tokensUpdate( tokenId: "<TOKEN_ID>", expire: "" ) { _id _createdAt resourceId resourceType expire secret accessedAt } } ``` ```server-rest PATCH /v1/tokens/{tokenId} HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Key: <YOUR_API_KEY> { "expire": } ``` {% /multicode %} {% /tabsitem %} {% /tabs %} ### Delete file tokens You can use the Appwrite Console or one of the Server SDKs to delete a file token. {% tabs %} {% tabsitem #console title="Console" %} Head to the **Storage** page, open a file inside a bucket, and scroll down to the **File tokens** section. Click on the three-dots menu next to the created file token and click on **Delete**. {% only_dark %} ![Delete file token](/images/docs/storage/dark/delete-file-token.png) {% /only_dark %} {% only_light %} ![Delete file token](/images/docs/storage/delete-file-token.png) {% /only_light %} {% /tabsitem %} {% tabsitem #server-sdk title="Server SDK" %} You can delete a file token programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#delete). Appwrite's Server SDKs require an [API key](/docs/advanced/platform/api-keys) with the `tokens.write` scope enabled. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new sdk.Tokens(client); const result = await tokens.delete( '<TOKEN_ID>' // tokenId ); ``` ```server-python from appwrite.client import Client from appwrite.services.tokens import Tokens client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('<YOUR_PROJECT_ID>') # Your project ID client.set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens(client) result = tokens.delete( token_id = '<TOKEN_ID>' ) ``` ```server-dart import 'package:dart_appwrite/dart_appwrite.dart'; Client client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key Tokens tokens = Tokens(client); await tokens.delete( tokenId: '<TOKEN_ID>', ); ``` ```server-php <?php use Appwrite\Client; use Appwrite\Services\Tokens; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('<YOUR_PROJECT_ID>') // Your project ID ->setKey('<YOUR_API_KEY>'); // Your secret API key $tokens = new Tokens($client); $result = $tokens->delete( tokenId: '<TOKEN_ID>' ); ``` ```server-ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint .set_project('<YOUR_PROJECT_ID>') # Your project ID .set_key('<YOUR_API_KEY>') # Your secret API key tokens = Tokens.new(client) result = tokens.delete( token_id: '<TOKEN_ID>' ) ``` ```server-dotnet using Appwrite; using Appwrite.Models; using Appwrite.Services; Client client = new Client() .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("<YOUR_PROJECT_ID>") // Your project ID .SetKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); await tokens.Delete( tokenId: "<TOKEN_ID>" ); ``` ```server-deno import { Client, Tokens } from "npm:node-appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<YOUR_PROJECT_ID>') // Your project ID .setKey('<YOUR_API_KEY>'); // Your secret API key const tokens = new Tokens(client); const response = await tokens.delete( '<TOKEN_ID>' // tokenId ); ``` ```server-go package main import ( "fmt" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tokens" ) func main() { client := client.NewClient() client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint client.SetProject("<YOUR_PROJECT_ID>") // Your project ID client.SetKey("<YOUR_API_KEY>") // Your secret API key service := tokens.NewTokens(client) response, error := service.Delete( "<TOKEN_ID>", ) if error != nil { panic(error) } fmt.Println(response) } ``` ```server-swift import Appwrite let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key let tokens = Tokens(client) let result = try await tokens.delete( tokenId: "<TOKEN_ID>" ) ``` ```server-kotlin import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Tokens val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>") // Your secret API key val tokens = Tokens(client) val response = tokens.delete( tokenId = "<TOKEN_ID>" ) ``` ```server-java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Tokens; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<YOUR_PROJECT_ID>") // Your project ID .setKey("<YOUR_API_KEY>"); // Your secret API key Tokens tokens = new Tokens(client); tokens.delete( "<TOKEN_ID>", // tokenId new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } System.out.println(result); }) ); ``` ```server-graphql mutation { tokensDelete( tokenId: "<TOKEN_ID>" ) { status } } ``` ```server-rest DELETE /v1/tokens/{tokenId} HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 X-Appwrite-Project: <YOUR_PROJECT_ID> X-Appwrite-Key: <YOUR_API_KEY> ``` {% /multicode %} {% /tabsitem %} {% /tabs %} --- ## Image transformations https://appwrite.io/docs/products/storage/images Appwrite provides utilities to manipulate images for previewing images in your apps. Appwrite Storage's [preview endpoint](/docs/references/cloud/client-web/storage#getFilePreview) let you manipulate resolution, add borders and the border-radius, add background-color, set the opacity for the image, and get the image in the appropriate output format. You can manipulate images resolution to display appropriately on responsive websites. You can also adjust the image border, background color, and border-radius to match the theming of your application. The Appwrite Storage also allows you to change the format and compression of your images for network transfer optimization and to help you speed your application. You can do all that without caring about how the image was originally uploaded. {% info title="Caching" %} When manipulating images in Appwrite, the resulting images are cached by Appwrite and your browser. When you repeatedly use the same transformed images, the performance impact will be minimal. {% /info %} #### Options {% #options %} Below you can find all the different parameters offered by the preview endpoint to manipulate the image. | Parameter | Description | | --------------| --------------------------------------------------------------------------------------------------------------- | | width | Set the width of the output image in pixels, the image will be resized keeping the aspect ratio intact. Accepts integer between `0-4000` | | height | Set the height of the output image in pixels, the image will be resized keeping the aspect ratio intact. Accepts integer between `0-4000` | | gravity | The gravity while cropping the image providing either width, height, or both. Accepts any of: `center`, `top-left`, `top`, `top-right`, `left`, `right`, `bottom-left`, `bottom`, `bottom-right`. | | quality | Set the quality of the output image. Accepts integer between `0-100`, where `100` is the highest quality. | | borderWidth | Set a border with the given width in pixels to the output image. Accepts integer between `0-100`. | | borderColor | Set a border-color for the output image. Accepts any valid hex color value without the leading `#`. | | borderRadius | Set a border-radius in pixels. Accepts an integer between `0-4000`. | | opacity | Set opacity for the output image. Accepts a float value between `0-1`, where `0` makes it transparent. Only works with output formats supporting alpha channels like `png`. | | rotation | Rotate the output image by a degree. Accepts an integer between `-360` to `360`. | | background | Set a background-color. Accepts any valid hex color value without the leading `#`. Only works with output formats supporting alpha channels like `png`. | | output | Set the output image format. If not provided, will use the original image's format. Supported formats are: `jpg`, `jpeg`, `png`, `gif`, `webp`, `avif`, and `heic` | ### Examples {% #examples %} Here are some examples using [Client SDKs](/docs/sdks#client). {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFilePreview({ bucketId: 'photos', fileId: 'sunset.png', width: 1800, gravity: 'center', quality: '90', borderWidth: 5, borderColor: 'CDCA30', borderRadius: 15, background: 'FFFFFF' }); console.log(result.href); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFilePreview( bucketId: 'photos', fileId: 'sunset.png', width: 1800, height: 0, gravity: 'center', quality: '90', borderWidth: 5, borderColor: 'CDCA30', borderRadius: 15, opacity: 1, rotation: 0, background: "FFFFFF", output:'jpg' ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFilePreview( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID let storage = Storage(client) let byteBuffer = try await storage.getFilePreview( bucketId: "photos", fileId: "sunset.png", width: 1800, height: 0, gravity: "center", quality: "90", borderWidth: 5, borderColor: "CDCA30", borderRadius: 15, opacity: 1, rotation: 0, background: "FFFFFF", output:"jpg" ) print(String(describing: byteBuffer) } ``` ```client-android-java import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFilePreview( bucketId = "photos", // bucket ID fileId = "sunset.png", // file ID width = 1800, // width, will be resized using this value. height = 0, // height, ignored when 0 gravity = "center", // crop center quality = "90", // slight compression borderWidth = 5, // border width borderColor = "CDCA30", // border color borderRadius = 15, // border radius opacity = 1, // full opacity rotation = 0, // no rotation background = "FFFFFF", // background color output ="jpg" // output jpg format ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage, ImageGravity } from 'react-native-appwrite'; import { Image } from 'react-native'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); // Get image with transformations const result = storage.getFilePreview( 'photos', // bucket ID 'sunset.png', // file ID 1800, // width, will be resized using this value 0, // height, ignored when 0 ImageGravity.Center,// crop center 90, // slight compression 5, // border width 'CDCA30', // border color 15, // border radius 1, // full opacity 0, // no rotation 'FFFFFF', // background color 'jpg' // output jpg format ); console.log(imageUrl); // URL object // Usage in a component const ImagePreview = () => ( <Image source={{ uri: imageUrl.toString() }} style={{ width: 300, height: 200 }} resizeMode="contain" /> ); ``` {% /multicode %} --- ## Storage permissions https://appwrite.io/docs/products/storage/permissions Permissions define who can access files within a bucket. By default **no permissions** are granted to any users, so no user can access any files. Permissions exist at two levels, bucket level and file level permissions. In Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted. A user with access granted at either bucket level or file level will be able to access a file. Users **don't need access at both levels** to access files. ### Bucket level {% #bucket-level %} Bucket level permissions apply to every file in the bucket. If a user has read, create, update, or delete permissions at the bucket level, the user can access **all files** inside the bucket. Configure bucket level permissions by navigating to **Your bucket** > **Settings** > **Permissions**. [Learn more about permissions and roles](/docs/advanced/platform/permissions) ### File level {% #file-level %} File level permissions grant access to individual files. If a user has read, create, update, or delete permissions at the file level, the user can access the **individual file**. File level permissions are only applied if File Security is enabled in the settings of your bucket. Enable file level permissions by navigating to **Your bucket** > **Settings** > **File security**. File level permissions are configured in individual [files](/docs/products/storage/permissions#file-level). [Learn more about permissions and roles](/docs/advanced/platform/permissions) --- ## Start with Storage https://appwrite.io/docs/products/storage/quick-start You can create your first bucket, upload, and download your first file in minutes. ### Create bucket {% #create-bucket %} You can create a bucket in the Appwrite Console by navigating to **Storage** > **Create bucket**. In your bucket, navigate to **Settings** > **Permissions**, then add a new **Any** role with **CREATE** and **READ** permissions. This allows anyone to create and read files in this bucket. ### Create file {% #create-file %} To upload a file, add this to your app. For web apps, you can use the File object directly. For Node.js apps, use the InputFile class. {% multicode %} ```client-web import { Client, Storage, ID } from "appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const promise = storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: document.getElementById('uploader').files[0] }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```server-nodejs const sdk = require('node-appwrite'); const { InputFile } = require('node-appwrite/file'); const client = new sdk.Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('<PROJECT_ID>') .setKey('<API_KEY>'); const storage = new sdk.Storage(client); const nodeFile = InputFile.fromPath('/path/to/file.jpg', 'file.jpg'); await storage.createFile({ bucketId: '<BUCKET_ID>', fileId: sdk.ID.unique(), file: nodeFile }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK final client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); final storage = Storage(client); final file = await storage.createFile( bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: InputFile.fromPath(path: './path-to-files/image.jpg', filename: 'image.jpg'), ); } ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") let storage = Storage(client) let file = try await storage.createFile( bucketId: "<BUCKET_ID>", fileId: ID.unique(), file: InputFile.fromBuffer(yourByteBuffer, filename: "image.jpg", mimeType: "image/jpeg" ) ) } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Storage suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val file = storage.createFile( bucketId = "<BUCKET_ID>", fileId = ID.unique(), file = File("./path-to-files/image.jpg"), ) } ``` ```client-react-native import { Client, Storage, ID } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const promise = storage.createFile( '<BUCKET_ID>', ID.unique(), { name: 'image.jpg', type: 'image/jpeg', size: 1234567, uri: 'file:///path/to/file.jpg', } ); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```http POST /v1/storage/buckets/{bucketId}/files HTTP/1.1 Content-Type: multipart/form-data; boundary="cec8e8123c05ba25" Content-Length: *Length of your entity body in bytes* X-Appwrite-Project: <PROJECT_ID> --cec8e8123c05ba25 Content-Disposition: form-data; name="operations" { "query": "mutation CreateFile($bucketId: String!, $fileId: String!, $file: InputFile!) { storageCreateFile(bucketId: $bucketId, fileId: $fileId, file: $file) { id } }", "variables": { "bucketId": "<BUCKET_ID>", "fileId": "<FILE_ID>", "file": null } } --cec8e8123c05ba25 Content-Disposition: form-data; name="map" { "0": ["variables.file"] } --cec8e8123c05ba25 Content-Disposition: form-data; name="0"; filename="file.txt" Content-Type: text/plain File content. --cec8e8123c05ba25-- ``` {% /multicode %} ### Download file {% #download-file %} To download a file, use the `getFileDownload` method. {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFileDownload({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFileDownload( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFileDownload( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID let storage = Storage(client) let byteBuffer = try await storage.getFileDownload( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>" ) print(String(describing: byteBuffer)) } ``` ```client-android-kotlin import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFileDownload( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>" ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const result = storage.getFileDownload({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // Resource URL ``` {% /multicode %} --- ## Upload and download https://appwrite.io/docs/products/storage/upload-download You can upload and download files both programmatically using SDKs or through the Appwrite Console. ### Create file {% #create-file %} After you create a bucket or have navigated to bucket details, you can access the **Files** tab so you can upload, view, delete and update files in the bucket using the Appwrite project's dashboard. You can also perform all those operations from Appwrite's client SDK, server SDKs, and REST APIs as long as you have the proper permission. When you are in the **Files** tab, you can click **Add File** and select a file to upload. If the bucket is configured to accept the file type and size you are uploading, your file will be uploaded, and you will see the file in the list of files. You can also upload files programmatically using our SDKs: {% multicode %} ```client-web import { Client, Storage, ID } from "appwrite"; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const promise = storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: document.getElementById('uploader').files[0] }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```server-nodejs const sdk = require('node-appwrite'); const { InputFile } = require('node-appwrite/file'); const client = new sdk.Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('<PROJECT_ID>') .setKey('<API_KEY>'); const storage = new sdk.Storage(client); // If running in a browser environment, you can use File directly const browserFile = new File(['hello'], 'hello.txt'); await storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: browserFile }); // If running in Node.js, use InputFile const nodeFile = InputFile.fromPath('/path/to/file.jpg', 'file.jpg'); await storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: nodeFile }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK final client = Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); final storage = Storage(client); final file = await storage.createFile( bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: InputFile.fromPath(path: './path-to-files/image.jpg', filename: 'image.jpg'), ); } ``` ```client-android-kotlin import io.appwrite.Client import io.appwrite.services.Storage suspend fun main() { val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val file = storage.createFile( bucketId = "<BUCKET_ID>", fileId = ID.unique(), file = File("./path-to-files/image.jpg"), ) } ``` ```client-react-native import { Client, Storage, ID } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const promise = storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: { name: 'image.jpg', type: 'image/jpeg', size: 1234567, uri: 'file:///path/to/file.jpg', } }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") let storage = Storage(client) let file = try await storage.createFile( bucketId: "<BUCKET_ID>", fileId: ID.unique(), file: InputFile.fromBuffer(yourByteBuffer, filename: "image.jpg", mimeType: "image/jpeg" ) ) } ``` ```http POST /v1/storage/buckets/{bucketId}/files HTTP/1.1 Content-Type: multipart/form-data; boundary="cec8e8123c05ba25" Content-Length: *Length of your entity body in bytes* X-Appwrite-Project: <PROJECT_ID> --cec8e8123c05ba25 Content-Disposition: form-data; name="operations" { "query": "mutation CreateFile($bucketId: String!, $fileId: String!, $file: InputFile!) { storageCreateFile(bucketId: $bucketId, fileId: $fileId, file: $file) { id } }", "variables": { "bucketId": "<BUCKET_ID>", "fileId": "<FILE_ID>", "file": null } } --cec8e8123c05ba25 Content-Disposition: form-data; name="map" { "0": ["variables.file"] } --cec8e8123c05ba25 Content-Disposition: form-data; name="0"; filename="file.txt" Content-Type: text/plain File content. --cec8e8123c05ba25-- ``` {% /multicode %} ### Large files {% #large-files %} When you are trying to upload any files above 5MB, you will need to upload them in chunks for better reliability and performance. If you're using an Appwrite SDK, this is handled automatically. If you're not using an SDK, you can [learn more about REST API file handling](/docs/apis/rest#files). ### InputFile {% #input-file %} Every language and platform handles file inputs differently. This section rows the expected input type of each SDK. Where applicable, Appwrite provides an `InputFile` class to accept multiple file sources, like paths, buffers, or plain text. ### Client SDKs {% #client-sdks %} {% tabs %} {% tabsitem #web title="Web" %} The Appwrite Web SDK expects a [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object for file creation. This is most commonly associated with DOM file inputs. For example, for the input tag `<input type="file" id="uploader">`, you would call create file like this: ```js const promise = storage.createFile({ bucketId: '<BUCKET_ID>', fileId: ID.unique(), file: document.getElementById('uploader').files[0] }); ``` {% /tabsitem %} {% tabsitem #flutter title="Flutter" %} The Appwrite Flutter SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(path: [PATH], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a provided path, `filename` and `contentType` are optional. Used for Flutter apps on mobile and desktop. | | `InputFile.fromBytes(bytes: [BYTE LIST], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a byte list, `contentType` is optional. Used for Flutter apps on the web. | {% /tabsitem %} {% tabsitem #android title="Android" %} The Appwrite Android SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------ | ------------------------------------------------ | | `InputFile.fromPath(path: String)` | Used to upload files from a provided path. | | `InputFile.fromFile(file: File)` | Used to upload files from a [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) object. | | `InputFile.fromBytes(bytes: ByteArray, filename: String, mimeType: String)` | Used to upload files from a [ByteArray](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-byte-array/) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | {% /tabsitem %} {% tabsitem #apple title="Apple" %} The Appwrite Apple SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(_ path: String)` | Used to upload files from a provided path. | | `InputFile.fromData(_ data: Data, filename: String, mimeType: String)` | Used to upload files from a [Data](https://developer.apple.com/documentation/foundation/data) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | | `InputFile.fromBuffer(_ buffer: ByteBuffer, filename: String, mimeType: String)` | Used to upload files from a [NIO Buffer](https://swiftinit.org/reference/swift-nio/niocore/bytebuffer) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | {% /tabsitem %} {% tabsitem #react-native title="React Native" %} The Appwrite React Native SDK expects a file object with the following properties for file inputs: | Property | Description | | -------- | ----------- | | `name` | The name of the file. | | `type` | The MIME type of the file. | | `size` | The size of the file in bytes. | | `uri` | The URI of the file on the device. | This object structure aligns with what is typically returned from image picker libraries such as `react-native-image-picker`: ```js // Example with react-native-image-picker import { launchImageLibrary } from 'react-native-image-picker'; const pickImage = async () => { const result = await launchImageLibrary({ mediaType: 'photo', }); if (result.assets && result.assets[0]) { const fileInfo = result.assets[0]; return { name: fileInfo.fileName, type: fileInfo.type, size: fileInfo.fileSize, uri: fileInfo.uri, }; } }; ``` You can also use the file picker or row picker from Expo: ```js // Example with expo-row-picker import * as DocumentPicker from 'expo-row-picker'; const pickDocument = async () => { const result = await DocumentPicker.getRowAsync(); if (result.assets && result.assets[0]) { return { name: result.assets[0].name, type: result.assets[0].mimeType, size: result.assets[0].size, uri: result.assets[0].uri, }; } }; ``` {% /tabsitem %} {% /tabs %} ### Server SDKs {% #server-sdks %} {% tabs %} {% tabsitem #nodejs title="Node.js" %} In browser environments, you can use the `File` object directly. For Node.js environments, import the `InputFile` class from 'node-appwrite/file'. When using `InputFile`, the following methods are available: | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(filePath, filename)` | Used to upload files from a provided path. | | `InputFile.fromBuffer(buffer, filename)` | Used to upload files from a [Buffer](https://nodejs.org/api/buffer.html#buffer) or [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object. | | `InputFile.fromPlainText(content, filename)` | Used to upload files in plain text. Expects a string encoded in UTF-8. | {% /tabsitem %} {% tabsitem #php title="PHP" %} The Appwrite PHP SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.withPath(string $path, ?string $mimeType = null, ?string $filename = null)` | Used to upload files from a provided path. | | `InputFile.withData(string $data, ?string $mimeType = null, ?string $filename = null)` | Used to upload files from a string. | {% /tabsitem %} {% tabsitem #python title="Python" %} The Appwrite Python SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.from_path(path)` | Used to upload files from a provided path. | | `InputFile.from_bytes(bytes)` | Used to upload files from an array of bytes. | {% /tabsitem %} {% tabsitem #ruby title="Ruby" %} The Appwrite Ruby SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.from_path(path)` | Used to upload files from a provided path. | | `InputFile.from_string(string)` | Used to upload files from a String. | | `InputFile.from_bytes(bytes)` | Used to upload files from an array of bytes. | {% /tabsitem %} {% tabsitem #deno title="Deno" %} The Appwrite Deno SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(filePath, filename)` | Used to upload files from a provided path. | | `InputFile.fromBuffer(buffer, filename)` | Used to upload files from a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) object. | | `InputFile.fromPlainText(content, filename)` | Used to upload files in plain text. Expects a string encoded in UTF-8. | {% /tabsitem %} {% tabsitem #dart title="Dart" %} The Appwrite Dart SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(path: [PATH], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a provided path, `filename` and `contentType` are optional. | | `InputFile.fromBytes(bytes: [BYTE LIST], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a byte list, `contentType` is optional. | {% /tabsitem %} {% tabsitem #kotlin title="Kotlin" %} The Appwrite Kotlin SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(path: String)` | Used to upload files from a provided path. | | `InputFile.fromFile(file: File)` | Used to upload files from a [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) object. | | `InputFile.fromBytes(bytes: ByteArray, filename: String, mimeType: String)` | Used to upload files from a [ByteArray](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-byte-array/) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | {% /tabsitem %} {% tabsitem #swift title="Swift" %} The Appwrite Swift SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.fromPath(_ path: String)` | Used to upload files from a provided path. | | `InputFile.fromData(_ data: Data, filename: String, mimeType: String)` | Used to upload files from a [Data](https://developer.apple.com/documentation/foundation/data) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | | `InputFile.fromBuffer(_ buffer: ByteBuffer, filename: String, mimeType: String)` | Used to upload files from a [NIO Buffer](https://swiftinit.org/reference/swift-nio/niocore/bytebuffer) object. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | {% /tabsitem %} {% tabsitem #dotnet title=".NET" %} The Appwrite .NET SDK expects an `InputFile` class for file inputs. | Method | Description | | ------------------------------------------ | ------------------------------------------------------------ | | `InputFile.FromPath(string path)` | Used to upload files from a provided path. | | `InputFile.FromBytes(byte[] bytes, string filename, string mimeType)` | Used to upload files from an array of bytes. Specify the file [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) using the `mimeType` param. | {% /tabsitem %} {% /tabs %} ### Get file {% #get-file %} To get a metadata about a file, use the `getFile` method. {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const promise = storage.getFile({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFile( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFile( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") let storage = Storage(client) let byteBuffer = try await storage.getFile( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>" ) print(String(describing: byteBuffer) } ``` ```client-android-kotlin import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFile( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>" ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); const promise = storage.getFile({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); promise.then(function (response) { console.log(response); // Success }, function (error) { console.log(error); // Failure }); ``` {% /multicode %} ### Download file {% #download-file %} To download a file, use the `getFileDownload` method. {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFileDownload({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFileDownload( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFileDownload( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID let storage = Storage(client) let byteBuffer = try await storage.getFileDownload( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>" ) print(String(describing: byteBuffer)) } ``` ```client-android-kotlin import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFileDownload( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>" ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); // Downloads the file data as ArrayBuffer const result = await storage.getFileDownload({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // ArrayBuffer with file data // To get just the download URL without downloading: const downloadUrl = storage.getFileDownloadURL({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(downloadUrl); // URL object ``` {% /multicode %} ### Get File Preview {% #get-file-preview %} To get a file preview image , use the `getFilePreview` method. {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFilePreview({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFilePreview( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFilePreview( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID let storage = Storage(client) let byteBuffer = try await storage.getFilePreview( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>" ) print(String(describing: byteBuffer)) } ``` ```client-android-kotlin import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFilePreview( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>" ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage, ImageGravity } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); // Downloads the preview image data as ArrayBuffer const result = await storage.getFilePreview({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', width: 200, height: 200, gravity: ImageGravity.Center, quality: 100 }); console.log(result); // ArrayBuffer with image data // To get just the preview URL without downloading: const previewUrl = storage.getFilePreviewURL({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', width: 200, height: 200, gravity: ImageGravity.Center, quality: 100 }); console.log(previewUrl); // URL object ``` {% /multicode %} ### View File {% #view-file%} To view a file, use the `getFileView` method. {% multicode %} ```client-web import { Client, Storage } from "appwrite"; const client = new Client(); const storage = new Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; const result = storage.getFileView({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // Resource URL ``` ```client-flutter import 'package:appwrite/appwrite.dart'; void main() { // Init SDK Client client = Client(); Storage storage = Storage(client); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint .setProject('<PROJECT_ID>') // Your project ID ; // downloading file Future result = storage.getFileView( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ).then((bytes) { final file = File('path_to_file/filename.ext'); file.writeAsBytesSync(bytes) }).catchError((error) { print(error.response); }) } //displaying image preview FutureBuilder( future: storage.getFileView( bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>', ), //works for both public file and private file, for private files you need to be logged in builder: (context, snapshot) { return snapshot.hasData && snapshot.data != null ? Image.memory( snapshot.data, ) : CircularProgressIndicator(); }, ); ``` ```client-apple import Appwrite func main() async throws { let client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID let storage = Storage(client) let byteBuffer = try await storage.getFileView( bucketId: "<BUCKET_ID>", fileId: "<FILE_ID>" ) print(String(describing: byteBuffer)) } ``` ```client-android-kotlin import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import io.appwrite.Client import io.appwrite.services.Storage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint .setProject("<PROJECT_ID>") // Your project ID val storage = Storage(client) val result = storage.getFileView( bucketId = "<BUCKET_ID>", fileId = "<FILE_ID>" ) println(result); // Resource URL } } ``` ```client-react-native import { Client, Storage } from 'react-native-appwrite'; const client = new Client() .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); const storage = new Storage(client); // Downloads the file data as ArrayBuffer const result = await storage.getFileView({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(result); // ArrayBuffer with file data // To get just the view URL without downloading: const viewUrl = storage.getFileViewURL({ bucketId: '<BUCKET_ID>', fileId: '<FILE_ID>' }); console.log(viewUrl); // URL object ``` {% /multicode %} --- ## Start with Android (Kotlin) https://appwrite.io/docs/quick-starts/android Learn how to setup your first Android project powered by Appwrite and the [Appwrite Android SDK](https://github.com/appwrite/sdk-for-android). {% info title="Using Java?" %} Check out the [Start with Android (Java)](/docs/quick-starts/android-java) guide. {% /info %} {% section #step-1 step=1 title="Create Android project" %} Open Android Studio and click **New Project** to create a new project. Choose your desired project template, for example **Empty Activity**, and click **Next**. Now enter your app **name** and **package name**. You will need both of these later when you create your project in the Appwrite console. Click **Finish** to create your project. {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add an **Android app**. Add your app's **name** and **package name**, your package name is the one entered when creating an Android project. For existing projects, you should use the **applicationId** in your app-level [build.gradle](https://github.com/appwrite/playground-for-android/blob/master/app/build.gradle#L11) file. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-3 step=3 title="Add the Appwrite SDK" %} To add the Appwrite SDK for Android as a dependency, add the following to your app-level **build.gradle.kts** file inside the **dependencies** block. ```kotlin implementation("io.appwrite:sdk-for-android:8.1.0") ``` In order to allow creating OAuth sessions, the following activity needs to be added inside the `<application>` tag, along side the existing `<activity>` tags in your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to replace the **<PROJECT_ID>** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite Console. ```xml <manifest ...> ... <application ...> ... <!-- Add this inside the `<application>` tag, along side the existing `<activity>` tags --> <activity android:name="io.appwrite.views.CallbackActivity" android:exported="true"> <intent-filter android:label="android_web_auth"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="appwrite-callback-<PROJECT_ID>" /> </intent-filter> </activity> </application> </manifest> ``` {% /section %} {% section #step-4 step=4 title="Create Appwrite Singleton" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `Appwrite.kt` and add the following code to it, replacing `<PROJECT_ID>` with your project ID. ```kotlin package <YOUR_ROOT_PACKAGE_HERE> import android.content.Context import io.appwrite.Client import io.appwrite.ID import io.appwrite.models.* import io.appwrite.services.* object Appwrite { lateinit var client: Client lateinit var account: Account fun init(context: Context) { client = Client(context) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") account = Account(client) } suspend fun onLogin( email: String, password: String, ): Session { return account.createEmailPasswordSession( email, password, ) } suspend fun onRegister( email: String, password: String, ): User<Map<String, Any>> { return account.create( userId = ID.unique(), email, password, ) } suspend fun onLogout() { account.deleteSession("current") } } ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `MainActivity.kt`. ```kotlin package <YOUR_ROOT_PACKAGE_HERE> import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.* import androidx.compose.ui.text.input.* import androidx.compose.ui.unit.* import <YOUR_ROOT_PACKAGE_HERE>.ui.theme.MyApplicationTheme import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Appwrite.init(applicationContext) setContent { MyApplicationTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { val coroutineScope = rememberCoroutineScope() var user by remember { mutableStateOf("") } var email by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } if (user.isNotEmpty()) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "Logged in as $user") Button(onClick = { coroutineScope.launch { Appwrite.onLogout() } }) { Text("Logout") } } } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { TextField( value = email, onValueChange = { email = it }, label = { Text("Username") }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) TextField( value = password, onValueChange = { password = it }, label = { Text("Password") }, modifier = Modifier .fillMaxWidth() .padding(16.dp), visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) ) Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Button(onClick = { coroutineScope.launch { try { Appwrite.onLogin(email, password) user = email } catch (e: Exception) { e.printStackTrace() } } }) { Text("Login") } Button(onClick = { coroutineScope.launch { try { Appwrite.onRegister(email, password) } catch (e: Exception) { e.printStackTrace() } } }) { Text("Register") } } } } } } } } ``` {% /section %} {% section #step-6 step=6 title="Type safety with models" %} For enhanced type safety, you can use custom model classes with the `nestedType` parameter: ```kotlin import io.appwrite.services.TablesDB data class User( val name: String, val email: String, val isVerified: Boolean = false ) // Usage with type safety val tablesDB = TablesDB(Appwrite.client) try { val users = tablesDB.listRows( databaseId = "[DATABASE_ID]", tableId = "[TABLE_ID]", nestedType = User::class.java // Enables type safety ) for (user in users.rows) { Log.d("Appwrite", "User: ${user.name} (${user.email})") } } catch (e: AppwriteException) { Log.e("Appwrite", "Error: ${e.message}") } ``` {% info title="Generate types automatically" %} Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate model classes automatically: `appwrite types ./models` {% /info %} {% /section %} {% section #step-7 step=7 title="All set" %} Run your project by clicking **Run app** in Android Studio. {% /section %} --- ## Start with Android (Java) https://appwrite.io/docs/quick-starts/android-java Learn how to setup your first Android project powered by Appwrite and the [Appwrite Android SDK](https://github.com/appwrite/sdk-for-android) using Java. {% info title="Using Kotlin?" %} Check out the [Start with Android (Kotlin)](/docs/quick-starts/android) guide. {% /info %} {% section #step-1 step=1 title="Create Android project" %} Open Android Studio and click **New Project** to create a new project. Choose your desired project template, for example **Empty Activity**, and click **Next**. Now enter your app **name** and **package name**. You will need both of these later when you create your project in the Appwrite console. Click **Finish** to create your project. {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add an **Android app**. Add your app's **name** and **package name**, your package name is the one entered when creating an Android project. For existing projects, you should use the **applicationId** in your app-level [build.gradle](https://github.com/appwrite/playground-for-android/blob/master/app/build.gradle#L11) file. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-3 step=3 title="Add the Appwrite SDK" %} To add the Appwrite SDK for Android as a dependency, add the following to your app-level **build.gradle** file inside the **dependencies** block. ```groovy implementation "io.appwrite:sdk-for-android:8.1.0" ``` In order to allow creating OAuth sessions, the following activity needs to be added inside the `<application>` tag, along side the existing `<activity>` tags in your [AndroidManifest.xml](https://github.com/appwrite/playground-for-android/blob/master/app/src/main/AndroidManifest.xml). Be sure to replace the **<PROJECT_ID>** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite Console. ```xml <manifest ...> ... <application ...> ... <!-- Add this inside the `<application>` tag, along side the existing `<activity>` tags --> <activity android:name="io.appwrite.views.CallbackActivity" android:exported="true"> <intent-filter android:label="android_web_auth"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="appwrite-callback-<PROJECT_ID>" /> </intent-filter> </activity> </application> </manifest> ``` {% /section %} {% section #step-4 step=4 title="Create Appwrite helper class" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `AppwriteHelper.java` and add the following code to it, replacing `<PROJECT_ID>` with your project ID. ```java package <YOUR_ROOT_PACKAGE_HERE>; import android.content.Context; import java.util.Map; import io.appwrite.Client; import io.appwrite.ID; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.models.Session; import io.appwrite.models.User; import io.appwrite.services.Account; public class AppwriteHelper { private static AppwriteHelper instance; private Client client; private Account account; private AppwriteHelper(Context context) { client = new Client(context) .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>"); account = new Account(client); } public static synchronized AppwriteHelper getInstance(Context context) { if (instance == null) { instance = new AppwriteHelper(context.getApplicationContext()); } return instance; } public interface AuthCallback<T> { void onSuccess(T result); void onError(Exception error); } public void login(String email, String password, final AuthCallback<Session> callback) { account.createEmailPasswordSession( email, password, new CoroutineCallback<>(result -> { callback.onSuccess(result); return null; }, error -> { callback.onError(error); return null; })); } public void register(String email, String password, final AuthCallback<User<Map<String, Object>>> callback) { account.create( ID.unique(), email, password, new CoroutineCallback<>(result -> { callback.onSuccess(result); return null; }, error -> { callback.onError(error); return null; }) ); } public void logout(final AuthCallback<Object> callback) { account.deleteSession("current", new CoroutineCallback<>(result -> { callback.onSuccess(result); return null; }, error -> { callback.onError(error); return null; })); } } ``` {% /section %} {% section #step-5 step=5 title="Create a login UI in XML" %} First, update your `activity_main.xml` layout file: ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context=".MainActivity"> <TextView android:id="@+id/textViewStatus" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="16dp" android:textSize="18sp" android:visibility="gone" /> <Button android:id="@+id/buttonLogout" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Logout" android:layout_marginBottom="16dp" android:visibility="gone" /> <EditText android:id="@+id/editTextEmail" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:hint="Email" android:inputType="textEmailAddress" /> <EditText android:id="@+id/editTextPassword" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:hint="Password" android:inputType="textPassword" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/buttonLogin" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="8dp" android:text="Login" /> <Button android:id="@+id/buttonRegister" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="8dp" android:text="Register" /> </LinearLayout> </LinearLayout> ``` {% /section %} {% section #step-6 step=6 title="Create MainActivity" %} Now update your `MainActivity.java` file with the following code: ```java package <YOUR_ROOT_PACKAGE_HERE>; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.util.Map; import io.appwrite.models.Session; import io.appwrite.models.User; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private EditText editTextEmail; private EditText editTextPassword; private Button buttonLogin; private Button buttonRegister; private TextView textViewStatus; private Button buttonLogout; private AppwriteHelper appwrite; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize Appwrite appwrite = AppwriteHelper.getInstance(getApplicationContext()); setContentView(R.layout.activity_main); // Initialize UI components editTextEmail = findViewById(R.id.editTextEmail); editTextPassword = findViewById(R.id.editTextPassword); buttonLogin = findViewById(R.id.buttonLogin); buttonRegister = findViewById(R.id.buttonRegister); textViewStatus = findViewById(R.id.textViewStatus); buttonLogout = findViewById(R.id.buttonLogout); // Set up click listeners buttonLogin.setOnClickListener(v -> login()); buttonRegister.setOnClickListener(v -> register()); buttonLogout.setOnClickListener(v -> logout()); } private void login() { String email = editTextEmail.getText().toString().trim(); String password = editTextPassword.getText().toString().trim(); if (email.isEmpty() || password.isEmpty()) { Toast.makeText(this, "Please enter email and password", Toast.LENGTH_SHORT).show(); return; } appwrite.login(email, password, new AppwriteHelper.AuthCallback<Session>() { @Override public void onSuccess(Session result) { runOnUiThread(() -> { Toast.makeText(MainActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); showLoggedInUI(email); }); } @Override public void onError(Exception error) { runOnUiThread(() -> { Log.e(TAG, "Login failed", error); Toast.makeText(MainActivity.this, "Login failed: " + error.getMessage(), Toast.LENGTH_SHORT).show(); }); } }); } private void register() { String email = editTextEmail.getText().toString().trim(); String password = editTextPassword.getText().toString().trim(); if (email.isEmpty() || password.isEmpty()) { Toast.makeText(this, "Please enter email and password", Toast.LENGTH_SHORT).show(); return; } appwrite.register(email, password, new AppwriteHelper.AuthCallback<User<Map<String, Object>>>() { @Override public void onSuccess(User<Map<String, Object>> result) { runOnUiThread(() -> { Toast.makeText(MainActivity.this, "Registration successful. You can now login.", Toast.LENGTH_SHORT).show(); }); } @Override public void onError(Exception error) { runOnUiThread(() -> { Log.e(TAG, "Registration failed", error); Toast.makeText(MainActivity.this, "Registration failed: " + error.getMessage(), Toast.LENGTH_SHORT).show(); }); } }); } private void logout() { appwrite.logout(new AppwriteHelper.AuthCallback<Object>() { @Override public void onSuccess(Object result) { runOnUiThread(() -> { Toast.makeText(MainActivity.this, "Logout successful", Toast.LENGTH_SHORT).show(); showLoginUI(); }); } @Override public void onError(Exception error) { runOnUiThread(() -> { Log.e(TAG, "Logout failed", error); Toast.makeText(MainActivity.this, "Logout failed: " + error.getMessage(), Toast.LENGTH_SHORT).show(); }); } }); } private void showLoggedInUI(String email) { editTextEmail.setVisibility(View.GONE); editTextPassword.setVisibility(View.GONE); buttonLogin.setVisibility(View.GONE); buttonRegister.setVisibility(View.GONE); textViewStatus.setVisibility(View.VISIBLE); buttonLogout.setVisibility(View.VISIBLE); textViewStatus.setText("Logged in as " + email); } private void showLoginUI() { editTextEmail.setVisibility(View.VISIBLE); editTextPassword.setVisibility(View.VISIBLE); buttonLogin.setVisibility(View.VISIBLE); buttonRegister.setVisibility(View.VISIBLE); textViewStatus.setVisibility(View.GONE); buttonLogout.setVisibility(View.GONE); } } ``` {% /section %} {% section #step-7 step=7 title="All set" %} Run your project by clicking **Run app** in Android Studio. {% /section %} --- ## Start with Angular https://appwrite.io/docs/quick-starts/angular Learn how to setup your first Angular project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Angular project" %} Create an Angular project. If you don't have Angular CLI installed, run this command. ```sh npm install -g @angular/cli ``` Then, create a project. ```sh ng new my-app cd my-app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/lib/appwrite.ts` and add the following code to it, replace `<PROJECT_ID>` with your project ID. ```client-web import { Client, Account} from 'appwrite'; export const client = new Client(); client .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') .setProject('<PROJECT_ID>'); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} First, add imports for the `FormsModule` from Angular to handle the login form.. ```ts import { FormsModule } from '@angular/forms'; ... @NgModule({ declarations: [ // ... ], imports: [ // ... FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ``` Then, replace the contents of `src/app/app.component.html`. ```html <div> <p> {{ loggedInUser ? 'Logged in as ' + loggedInUser.name : 'Not logged in' }} </p> <div> <input type="email" placeholder="Email" [(ngModel)]="email" /> <input type="password" placeholder="Password" [(ngModel)]="password" /> <input type="text" placeholder="Name" [(ngModel)]="name" /> <button (click)="login(email, password)"> Login </button> <button (click)="register(email, password, name)"> Register </button> <button (click)="logout()"> Logout </button> </div> </div> ``` Lastly, update `src/app/app.component.ts`. ```ts import { Component } from '@angular/core'; import { account, ID } from '../lib/appwrite'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { loggedInUser: any = null; email: string = ''; password: string = ''; name: string = ''; async login(email: string, password: string) { await account.createEmailPasswordSession({ email, password }); this.loggedInUser = await account.get(); } async register(email: string, password: string, name: string) { await account.create({ userId: ID.unique(), email, password, name }); this.login(email, password); } async logout() { await account.deleteSession({ sessionId: 'current' }); this.loggedInUser = null; } } ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `ng serve --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with Apple https://appwrite.io/docs/quick-starts/apple Learn how to setup your first Apple project powered by Appwrite and the [Appwrite Apple SDK](https://github.com/appwrite/sdk-for-apple). {% section #step-1 step=1 title="Create Apple project" %} Open Xcode and click **Create a new Xcode project**. Choose your desired project template, for example **iOS App**, and click **Next**. Now enter your app **product name** and **bundle identifier** and click **Next**. You will need both of these values later when you create your project in the Appwrite console. Choose a directory for your project in and click **Create** to create your project. {% /section %} {% section #step-2 step=2 title="Create Appwrite project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add an **Apple app**. Choose any of **iOS**, **macOS**, **watchOS** or **tvOS** as your Apple platform. If you are creating a multi-platform app, you can add more platforms later. Add your app's **product name** and **bundle identifier**, your bundle identifier is the one entered when creating an Xcode project. For existing projects, you should use the **bundle identifier** from your project files **Identity** section. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-3 step=3 title="Add the Appwrite SDK" %} To add the Appwrite SDK for Apple as a dependency, open the **File** menu and click **Add Packages**. In the **Package URL** search box, enter https://github.com/appwrite/sdk-for-apple. Once the SDK is found, use `10.1.0` as version, select **Up to Next Major Version** as your **Dependency Rule** and click **Add Package**. When dependency resolution is complete, click **Add Package** again to add the SDK package to your target. In order to allow creating OAuth sessions, the following URL scheme must be added to your **Info.plist** file. ```xml <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>io.appwrite</string> <key>CFBundleURLSchemes</key> <array> <string>appwrite-callback-<PROJECT_ID></string> </array> </dict> </array> ``` If you're using UIKit as opposed to SwiftUI, you will also need to add the following to your **SceneDelegate.swift** file. ```swift func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let url = URLContexts.first?.url, url.absoluteString.contains("appwrite-callback") else { return } WebAuthComponent.handleIncomingCookie(from: url) } ``` {% /section %} {% section #step-4 step=4 title="Create Appwrite Singleton" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `Appwrite.swift` and add the following code to it, replacing `<PROJECT_ID>` with your project ID. ```swift import Foundation import Appwrite import JSONCodable class Appwrite { var client: Client var account: Account public init() { self.client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") self.account = Account(client) } public func onRegister( _ email: String, _ password: String ) async throws -> User<[String: AnyCodable]> { try await account.create( userId: ID.unique(), email: email, password: password ) } public func onLogin( _ email: String, _ password: String ) async throws -> Session { try await account.createEmailPasswordSession( email: email, password: password ) } public func onLogout() async throws { _ = try await account.deleteSession( sessionId: "current" ) } } ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `ContentView.swift`. ```swift import SwiftUI class ViewModel: ObservableObject { @Published var email: String = "" @Published var password: String = "" } struct ContentView: View { @ObservedObject var viewModel = ViewModel() let appwrite = Appwrite() var body: some View { VStack { TextField( "Email", text: $viewModel.email ) SecureField( "Password", text: $viewModel.password ) Button( action: { Task { try await appwrite.onRegister( viewModel.email, viewModel.password ) }}, label: { Text("Register") } ) Button( action: { Task { try await appwrite.onLogin( viewModel.email, viewModel.password ) }}, label: { Text("Login") } ) } .padding() } } ``` {% /section %} {% section #step-6 step=6 title="Type safety with models" %} For enhanced type safety, you can use custom model structs with the `nestedType` parameter: ```swift import Appwrite struct User: Codable { let name: String let email: String let isVerified: Bool } // Usage with type safety let appwrite = Appwrite() let tablesDB = TablesDB(appwrite.client) do { let users = try await tablesDB.listRows( databaseId: "[DATABASE_ID]", tableId: "[TABLE_ID]", nestedType: User.self // Enables type safety ) for user in users.rows { print("User: \(user.name) (\(user.email))") } } catch { print("Error: \(error.localizedDescription)") } ``` {% info title="Generate types automatically" %} Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate model structs automatically: `appwrite types collection` {% /info %} {% /section %} {% section #step-7 step=7 title="All set" %} Run your project by clicking **Start active scheme** in Xcode. {% /section %} --- ## Start with Astro https://appwrite.io/docs/quick-starts/astro Improve the docs, add this guide. We still don't have this guide in place, but we do have some great news. The Appwrite docs, just like Appwrite, is completely open sourced. This means, anyone can help improve them and add new guides and tutorials. If you see this page, **we're actively looking for contributions to this page**. Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. --- ## Start with Dart https://appwrite.io/docs/quick-starts/dart Learn how to setup your first Dart project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Integrate with your server**, add an **API Key** with the following scopes. | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} {% /section %} {% section #step-2 step=2 title="Create Dart project" %} Create a Dart CLI application. ```sh dart create -t console my_app cd my_app ``` After entering the project directory, remove the `lib/` and `test/` directories. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Dart Appwrite SDK. ```sh dart pub add dart_appwrite:16.0.0 ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Also, click on the **View API Keys** button to find the API key that was created earlier. Open `bin/my_app.dart` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key. ```dart import 'package:dart_appwrite/dart_appwrite.dart'; var client = Client(); Future<void> main() async { client .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<YOUR_API_KEY>"); } ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```dart var databases; var todoDatabase; var todoTable; Future<void> prepareDatabase() async { tablesDB = TablesDB(client); todoDatabase = await tablesDB.create( databaseId: ID.unique(), name: 'TodosDB' ); todoTable = await tablesDB.createTable( databaseId: todoDatabase.$id, tableId: ID.unique(), name: 'Todos' ); await tablesDB.createStringColumn( databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'title', size: 255, xrequired: true ); await tablesDB.createStringColumn( databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'description', size: 255, xrequired: false, xdefault: 'This is a test description' ); await tablesDB.createBooleanColumn( databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'isComplete', xrequired: true ); } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```dart Future<void> seedDatabase() async { var testTodo1 = { 'title': 'Buy apples', 'description': 'At least 2KGs', 'isComplete': true }; var testTodo2 = { 'title': 'Wash the apples', 'isComplete': true }; var testTodo3 = { 'title': 'Cut the apples', 'description': 'Don\'t forget to pack them in a box', 'isComplete': false }; await tablesDB.createRow( databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo1 ); await tablesDB.createRow( databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo2 ); await tablesDB.createRow( databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo3 ); } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data. ```dart Future<void> getTodos() async { var todos = await tablesDB.listRows( databaseId: todoDatabase.$id, tableId: todoTable.$id ); todos.rows.forEach((todo) { print('Title: ${todo.data['title']}\nDescription: ${todo.data['description']}\nIs Todo Complete: ${todo.data['isComplete']}\n\n'); }); } ``` Finally, revisit the `main()` function and call the functions created in previous steps. ```dart Future<void> main() async { client .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<YOUR_API_KEY>"); await prepareDatabase(); await Future.delayed(const Duration(seconds: 1)); await seedDatabase(); await getTodos(); } ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `dart run bin/my_app.dart` and view the response in your console. {% /section %} --- ## Start with Deno https://appwrite.io/docs/quick-starts/deno {% info title="Deno SDK Deprecation" %} The dedicated Deno SDK has been deprecated in favor of using the Node.js SDK directly through npm specifiers, thanks to Deno's excellent Node.js compatibility. This change simplifies maintenance and ensures you always have access to the latest features. {% /info %} Learn how to setup your first Deno project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% /section %} {% section #step-2 step=2 title="Create Deno project" %} Create a Deno CLI application. ```sh mkdir my-app cd my-app echo "console.log('Hello, Deno!');" > mod.ts ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Appwrite SDK using npm specifiers at the top of your file. ``` // import all as sdk import * as sdk from "npm:node-appwrite"; // import only what you need import { Client, ... other imports } from "npm:node-appwrite"; ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open `mod.ts` in your IDE and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key. ```ts import { Client, ID, TablesDB, Models } from "npm:node-appwrite"; const client: Client = new Client(); client .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") .setProject("<PROJECT_ID>") .setKey("<YOUR_API_KEY>"); ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```ts const tablesDB: TablesDB = new TablesDB(client); var todoDatabase: Models.Database; var todoTable: Models.Table; interface Todo { title: string; description: string; isComplete?: boolean; } async function prepareDatabase(): Promise<void> { todoDatabase = await tablesDB.create({ databaseId: ID.unique(), name: 'TodosDB' }); todoTable = await tablesDB.createTable({ databaseId: todoDatabase.$id, tableId: ID.unique(), name: 'Todos' }); await tablesDB.createStringColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'title', size: 255, required: true }); await tablesDB.createStringColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'description', size: 255, required: false, xdefault: 'This is a test description' }); await tablesDB.createBooleanColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'isComplete', required: true }); } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```ts async function seedDatabase(): Promise<void> { const testTodo1: Todo = { title: 'Buy apples', description: 'At least 2KGs', isComplete: true }; const testTodo2: Todo = { title: 'Wash the apples', isComplete: true }; const testTodo3: Todo = { title: 'Cut the apples', description: 'Don\'t forget to pack them in a box', isComplete: false }; await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo1 }); await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo2 }); await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: ID.unique(), data: testTodo3 }); } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data and a function to execute the requests in order. Run the functions to by calling `runAllTasks();`. ```ts async function getTodos(): Promise<void> { const todos = await tablesDB.listRows({ databaseId: todoDatabase.$id, tableId: todoTable.$id }); todos.rows.forEach((todo: Todo) => { console.log(`Title: ${todo.title}\nDescription: ${todo.description}\nIs Todo Complete: ${todo.isComplete}\n\n`); }); } async function runAllTasks(): Promise<void> { await prepareDatabase(); await seedDatabase(); await getTodos(); } runAllTasks(); ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `deno mod.ts` and view the response in your console. {% /section %} --- ## Start with .NET https://appwrite.io/docs/quick-starts/dotnet Learn how to setup your first .NET project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} {% /section %} {% section #step-2 step=2 title="Create .NET project" %} Create a .NET CLI application. ```sh dotnet new console -o MyApp cd MyApp ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the .NET Appwrite SDK. ```sh dotnet add package Appwrite --version 0.13.0 ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open the file `Program.cs` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key. ```csharp using Appwrite; using Appwrite.Models; using Appwrite.Services; var client = new Client(); client .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") .SetProject("<PROJECT_ID>") .SetKey("<YOUR_API_KEY>"); ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```csharp var tablesDB = new TablesDB(client); Database todoDatabase; Table todoTable; todoDatabase = await tablesDB.Create( databaseId: ID.Unique(), name: "TodosDB" ); todoTable = await tablesDB.CreateTable( databaseId: todoDatabase.Id, tableId: ID.Unique(), name: "Todos" ); await tablesDB.CreateStringColumn( databaseId: todoDatabase.Id, tableId: todoTable.Id, key: "title", size: 255, required: true ); await tablesDB.CreateStringColumn( databaseId: todoDatabase.Id, tableId: todoTable.Id, key: "description", size: 255, required: false, xdefault: "This is a test description" ); await tablesDB.CreateBooleanColumn( databaseId: todoDatabase.Id, tableId: todoTable.Id, key: "isComplete", required: true ); ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```csharp var testTodo1 = new Dictionary<string, object>() { {"title", "Buy apples"}, {"description", "At least 2KGs"}, {"isComplete", true} }; var testTodo2 = new Dictionary<string, object>() { {"title", "Wash the apples"}, {"isComplete", true} }; var testTodo3 = new Dictionary<string, object>() { {"title", "Cut the apples"}, {"description", "Don't forget to pack them in a box"}, {"isComplete", false} }; await tablesDB.createRow( databaseId: todoDatabase.Id, tableId: todoTable.Id, rowId: ID.Unique(), data: testTodo1 ); await tablesDB.createRow( databaseId: todoDatabase.Id, tableId: todoTable.Id, rowId: ID.Unique(), data: testTodo2 ); await tablesDB.createRow( databaseId: todoDatabase.Id, tableId: todoTable.Id, rowId: ID.Unique(), data: testTodo3 ); ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data. ```csharp var todos = await tablesDB.listRows( databaseId: todoDatabase.Id, tableId: todoTable.Id ); foreach (var todo in todos.Documents) { Console.WriteLine($"Title: {todo.Data["title"]}\nDescription: {todo.Data["description"]}\nIs Todo Complete: {todo.Data["isComplete"]}\n\n"); } ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `dotnet run` and view the response in your console. {% /section %} --- ## Start with Flutter https://appwrite.io/docs/quick-starts/flutter Learn how to setup your first Flutter project powered by Appwrite. {% section #step-1 step=1 title="Create Flutter project" %} Create a Flutter project. ```sh flutter create my_app && cd my_app ``` {% /section %} {% section #step-2 step=2 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Flutter app**. You can choose between many different platforms. {% tabs %} {% tabsitem #web title="Web" %} Add your app **name** and **Hostname**. If you're testing your app locally, **Hostname** should be `localhost`. For web, in order to capture the OAuth2 callback URL and send it to the application using JavaScript `postMessage()`, you need to create an html file inside `./web` folder of your Flutter project. For example `auth.html` with the following content. ```html <!DOCTYPE html> <title>Authentication complete

Authentication is complete. If this does not happen automatically, please close the window.

``` The redirection URL passed to the authentication service must be the same as the URL on which the application is running including schema, host, and port if applicable. The path must point to the created HTML file, `/auth.html` in this case. The callbackUrlScheme parameter in the authenticate() method isn't applicable when you're developing for web platforms. This means you can use this parameter to define URL schemes specifically for native platforms without affecting the web version of your Flutter application. {% info title="Flutter web cross-domain communication & cookies" %} While running Flutter Web, make sure your Appwrite project and your Flutter client use the same top-level domain and protocol (HTTP or HTTPS) to communicate. When communicating between different domains or protocols, you may receive HTTP status error 401 because some modern browsers block cross-site or insecure cookies for enhanced privacy. In production, Appwrite allows you to set multiple [custom-domains](/docs/advanced/platform/custom-domains) for each project. {% /info %} {% /tabsitem %} {% tabsitem #ios title="iOS" %} Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. The Appwrite SDK uses `ASWebAuthenticationSession` on iOS 12+ and `SFAuthenticationSession` on iOS 11 to allow OAuth authentication. You have to change your iOS Deployment Target in Xcode to be iOS >= 11 to be able to build your app on an emulator or a real device. 1. In **XCode**, open `Runner.xcworkspace` in your app's iOS folder. 2. To view your app's settings, select the Runner project in the XCode project navigator. Then, in the main view sidebar, select the **Runner target**. 3. Select the **General** tab. 4. In **Deployment Info** > **Target**, select iOS 11.0 or above {% /tabsitem %} {% tabsitem #android title="Android" %} Add your app's **name** and **package name**, Your package name is generally the **applicationId** in your app-level [build.gradle](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/build.gradle#L41) file. In order to capture the Appwrite OAuth callback url, the following activity needs to be added inside the `` tag, along side the existing `` tags in your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml). Be sure to replace the **** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite Console. ```xml ... ... ``` {% /tabsitem %} {% tabsitem #linux title="Linux" %} Add your app **name** and **package name**. Your package name is generally the **name** in your [pubspec.yaml](https://github.com/appwrite/playground-for-flutter/blob/master/pubspec.yaml#L1) file. If you cannot find the correct package name, run the application in Linux and make any request with proper exception handling. You should get the application ID needed to add in the received error message. {% /tabsitem %} {% tabsitem #macos title="macOS" %} Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. The Appwrite SDK uses `ASWebAuthenticationSession` on macOS 10.15+ to allow OAuth authentication. You have to change your macOS **Deployment Target** in XCode to be macOS >= 10.15 to be able to build your app for macOS. In order to capture the Appwrite OAuth 2 callback url, the following URL scheme needs to added to your `Info.plist`. ```xml CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLName io.appwrite CFBundleURLSchemes appwrite-callback- ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} For **Windows**, add your app *name* and *package name*. Your package name is generally the **name** in your [pubspec.yaml](https://github.com/appwrite/playground-for-flutter/blob/master/pubspec.yaml#L1) file. If you cannot find the correct package name, run the application in Windows, and make any request with proper exception handling. You should get the application ID needed to add in the received error message. {% /tabsitem %} {% /tabs %} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Appwrite SDK for Flutter. ```sh flutter pub add appwrite:17.0.0 ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open the generated `lib/main.dart` and add the following code to it, replace `` with your project ID. This imports and initializes Appwrite. ```dart import 'package:flutter/material.dart'; import 'package:appwrite/appwrite.dart'; import 'package:appwrite/models.dart' as models; void main() { WidgetsFlutterBinding.ensureInitialized(); Client client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject(""); Account account = Account(client); runApp(MaterialApp( home: MyApp(account: account), )); } class MyApp extends StatefulWidget { final Account account; MyApp({required this.account}); @override MyAppState createState() { return MyAppState(); } } ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Then, append the following widgets to `lib/main.dart` create your login page. ```dart class MyAppState extends State { models.User? loggedInUser; final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); final TextEditingController nameController = TextEditingController(); Future login(String email, String password) async { await widget.account.createEmailPasswordSession({ email: email, password: password }); final user = await widget.account.get(); setState(() { loggedInUser = user; }); } Future register(String email, String password, String name) async { await widget.account.create({ userId: ID.unique(), email: email, password: password, name: name }); await login(email, password); } Future logout() async { await widget.account.deleteSession(sessionId: 'current'); setState(() { loggedInUser = null; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(loggedInUser != null ? 'Logged in as ${loggedInUser!.name}' : 'Not logged in'), SizedBox(height: 16.0), TextField( controller: emailController, decoration: InputDecoration(labelText: 'Email'), ), SizedBox(height: 16.0), TextField( controller: passwordController, decoration: InputDecoration(labelText: 'Password'), obscureText: true, ), SizedBox(height: 16.0), TextField( controller: nameController, decoration: InputDecoration(labelText: 'Name'), ), SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ ElevatedButton( onPressed: () { login(emailController.text, passwordController.text); }, child: Text('Login'), ), SizedBox(width: 16.0), ElevatedButton( onPressed: () { register(emailController.text, passwordController.text, nameController.text); }, child: Text('Register'), ), SizedBox(width: 16.0), ElevatedButton( onPressed: () { logout(); }, child: Text('Logout'), ), ], ), ], ), ), ); } } ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `flutter run` and select a browser, platform, or emulator to run your project. {% /section %} --- ## Start with Go https://appwrite.io/docs/quick-starts/go Learn how to set up your first Go project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% /section %} {% section #step-2 step=2 title="Create Go project" %} Create a go application. ```sh mkdir my-app cd my-app go mod init go-appwrite/main ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Go Appwrite SDK. ```sh go get github.com/appwrite/sdk-for-go ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file called `app.go`, initialize a function, and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. Import the Appwrite dependencies for appwrite, client, databases, and models. ```go package main import ( "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tablesDB" "github.com/appwrite/sdk-for-go/models" "github.com/appwrite/sdk-for-go/query" ) var ( appwriteClient client.Client todoDatabase *models.Database todoTable *models.Table appwriteDatabases *tablesDB.TablesDB ) func main() { appwriteClient = appwrite.NewClient( appwrite.WithProject(""), appwrite.WithKey(""), ) } ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. Import the id Appwrite dependency by adding `"github.com/appwrite/sdk-for-go/id"` to the imported dependencies list. ```go func prepareDatabase() { tablesDB = appwrite.NewTablesDB(appwriteClient) todoDatabase, _ = tablesDB.Create( id.Unique(), "TodosDB", ) todoTable, _ = tablesDB.CreateTable( todoDatabase.Id, id.Unique(), "Todos", ) tablesDB.CreateStringColumn( todoDatabase.Id, todoTable.Id, "title", 255, true, ) tablesDB.CreateStringColumn( todoDatabase.Id, todoTable.Id, "description", 255, false, ) tablesDB.CreateBooleanColumn( todoDatabase.Id, todoTable.Id, "isComplete", true, ) } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data to your new table. ```go func seedDatabase() { testTodo1 := map[string]interface{}{ "title": "Buy apples", "description": "At least 2KGs", "isComplete": true, } testTodo2 := map[string]interface{}{ "title": "Wash the apples", "isComplete": true, } testTodo3 := map[string]interface{}{ "title": "Cut the apples", "description": "Don't forget to pack them in a box", "isComplete": false, } tablesDB.createRow( todoDatabase.Id, todoTable.Id, id.Unique(), testTodo1, ) tablesDB.createRow( todoDatabase.Id, todoTable.Id, id.Unique(), testTodo2, ) tablesDB.createRow( todoDatabase.Id, todoTable.Id, id.Unique(), testTodo3, ) } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data. ```go type Todo struct { Title string `json:"title"` Description string `json:"description"` IsComplete bool `json:"isComplete"` } type TodoList struct { *models.DocumentList Documents []Todo `json:"rows"` } func getTodos() { // Retrieve rows (default limit is 25) todoResponse, _ := tablesDB.ListRows( todoDatabase.Id, todoTable.Id, ) var todos TodoList todoResponse.Decode(&todos) fmt.Println("Todos:") for _, todo := range todos.Documents { fmt.Printf("Title: %s\nDescription: %s\nIs Todo Complete: %t\n\n", todo.Title, todo.Description, todo.IsComplete) } } func getCompletedTodos() { // Use queries to filter completed todos with pagination todoResponse, _ := tablesDB.ListRows( todoDatabase.Id, todoTable.Id, tablesDB.WithListRowsQueries([]string{ query.Equal("isComplete", true), query.OrderDesc("$createdAt"), query.Limit(5), }), ) var todos TodoList todoResponse.Decode(&todos) fmt.Println("Completed todos (limited to 5):") for _, todo := range todos.Documents { fmt.Printf("Title: %s\nDescription: %s\nIs Todo Complete: %t\n\n", todo.Title, todo.Description, todo.IsComplete) } } func getIncompleteTodos() { // Query for incomplete todos todoResponse, _ := tablesDB.ListRows( todoDatabase.Id, todoTable.Id, tablesDB.WithListRowsQueries([]string{ query.Equal("isComplete", false), query.OrderAsc("title"), }), ) var todos TodoList todoResponse.Decode(&todos) fmt.Println("Incomplete todos (ordered by title):") for _, todo := range todos.Documents { fmt.Printf("Title: %s\nDescription: %s\nIs Todo Complete: %t\n\n", todo.Title, todo.Description, todo.IsComplete) } } ``` Make sure to update `main()` with the functions you created. Your `main()` function should look something like this: ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/appwrite" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/tablesDB" "github.com/appwrite/sdk-for-go/id" "github.com/appwrite/sdk-for-go/models" "github.com/appwrite/sdk-for-go/query" ) var ( appwriteClient client.Client todoDatabase *models.Database todoTable *models.Table tablesDB *tablesDB.TablesDB ) func main() { appwriteClient = appwrite.NewClient( appwrite.WithProject(""), appwrite.WithKey(""), ) prepareDatabase() seedDatabase() getTodos() getCompletedTodos() getIncompleteTodos() } ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `go run .` and view the response in your console. {% /section %} --- ## Start with Kotlin https://appwrite.io/docs/quick-starts/kotlin Learn how to setup your first Kotlin project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% info title="Server SDK" %} This tutorial is for the Kotlin Server SDK, meant for server and backend applications. If you're trying to build a client-side app, like an Android app, follow the [Start with Android guide](https://appwrite.io/docs/quick-starts/android). {% /info %} If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} {% /section %} {% section #step-2 step=2 title="Create Kotlin project" %} Create a Kotlin application by opening **IntelliJ IDEA** > **New Project** and create a **Kotlin** application. This quick start will use **Gradle** as the build system, with the Kotlin DSL. You can follow with Maven or IntelliJ if you're more comfortable. Follow the wizard and open your new project. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Open your `build.gradle.kts` file and implement the following dependency. ```groovy dependencies { ... other dependencies implementation("io.appwrite:sdk-for-kotlin:9.0.0") } ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open the file `Main.kt` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```kotlin import io.appwrite.Client import io.appwrite.ID import io.appwrite.services.TablesDB import io.appwrite.models.Database import io.appwrite.models.Table import kotlinx.coroutines.coroutineScope val client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```kotlin val tablesDB = TablesDB(client) var todoDatabase: Database? = null var todoTable: Table? = null suspend fun prepareDatabase() { todoDatabase = tablesDB.create(ID.unique(), "TodosDB") todoTable = tablesDB.createTable(todoDatabase?.id!!, ID.unique(), "Todos") tablesDB.createStringColumn( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, key = "title", size = 255, required = true ) tablesDB.createStringColumn( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, key = "description", size = 255, required = false, default = "This is a test description." ) tablesDB.createBooleanColumn( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, key = "isComplete", required = true ) } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```kotlin suspend fun seedDatabase() { val testTodo1 = mapOf( "title" to "Buy apples", "description" to "At least 2KGs", "isComplete" to true ) val testTodo2 = mapOf( "title" to "Wash the apples", "isComplete" to true ) val testTodo3 = mapOf( "title" to "Cut the apples", "description" to "Don't forget to pack them in a box", "isComplete" to false ) tablesDB.createRow( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, rowId = ID.unique(), data = testTodo1 ) tablesDB.createRow( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, rowId = ID.unique(), data = testTodo2 ) tablesDB.createRow( databaseId = todoDatabase?.id!!, tableId = todoTable?.id!!, rowId = ID.unique(), data = testTodo3 ) } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data. ```kotlin suspend fun getTodos() { val todos = tablesDB.listRows(todoDatabase?.id!!, todoTable?.id!!) for (todo in todos.rows) { println( """ Title: ${todo.data["title"]} Description: ${todo.data["description"]} Is Todo Complete: ${todo.data["isComplete"]} """.trimIndent() ) } } suspend fun main() = coroutineScope { prepareDatabase() seedDatabase() getTodos() } ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with IntelliJ and view the response in your console. {% /section %} --- ## Start with Next.js https://appwrite.io/docs/quick-starts/nextjs Learn how to setup your first Next.js project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Next.js project" %} Create a Next.js project by running the following command: ```sh npx create-next-app@latest && cd my-app ``` When prompted, configure your project with these recommended settings: - **Would you like to use TypeScript?** → No - **Would you like to use ESLint?** → Yes - **Would you like to use Tailwind CSS?** → No (unless you plan to use it) - **Would you like to use `src/` directory?** → Yes/No (either works for this tutorial) - **Would you like to use App Router?** → Yes - **Would you like to customize the default import alias?** → No These settings will create a minimal Next.js setup that's perfect for getting started with Appwrite. {% /section %} {% section #step-3 step=3 title="Install Appwrite SDK" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Define Appwrite service" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `app/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account } from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Create or update `app/page.js` file and add the following code to it. ```js "use client"; import { useState } from "react"; import { account, ID } from "./appwrite"; const LoginPage = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const login = async (email, password) => { const session = await account.createEmailPasswordSession({ email, password }); setLoggedInUser(await account.get()); }; const register = async () => { await account.create({ userId: ID.unique(), email, password, name }); login(email, password); }; const logout = async () => { await account.deleteSession({ sessionId: 'current' }); setLoggedInUser(null); }; if (loggedInUser) { return (

Logged in as {loggedInUser.name}

); } return (

Not logged in

setEmail(e.target.value)} /> setPassword(e.target.value)} /> setName(e.target.value)} />
); }; export default LoginPage; ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev` and open [Localhost on Port 3000](http://localhost:3000) in your browser. Don't forget to add some CSS to suit your style. {% /section %} --- ## prompt https://appwrite.io/docs/quick-starts/nextjs/prompt Goal: Add Appwrite auth to a new Next.js app (App Router), with a working login/register/logout page. Do exactly these steps in order. Confirm each step succeeds before continuing. If any command fails, show the error and fix it automatically. Respect user's package manager at all time. Don't use NPM if the user uses something else. 1. Create Next.js app - Run: npx create-next-app@latest my-app --use-npm --no-tailwind --eslint - Change dir: cd my-app - When prompted: TypeScript = No, ESLint = Yes, Tailwind = No, src dir = your choice, App Router = Yes, Import alias = No. 2. Install Appwrite SDK - Run: npm install appwrite 3. Create Appwrite client module (ask user for details; never assume) - Ask the user for: - Appwrite Cloud Region (e.g. fra, nyc) - Project ID (from Console -> Settings) If the user doesn’t know, guide them to Appwrite Console to copy these. Do not attempt to infer or access their project. - Hardcode the endpoint and project ID in the file: app/appwrite.js (or app/appwrite.ts if TS) if provided, else leave placeholder and ask the user to provide them. - Create file: app/appwrite.js (or app/appwrite.ts if TS) with key snippet: ```js import { Client, Account } from 'appwrite'; const endpoint = ''; const projectId = ''; if (!endpoint || !projectId) throw new Error('Missing Appwrite endpoint and project ID'); export const client = new Client().setEndpoint(endpoint).setProject(projectId); export const account = new Account(client); export { ID } from 'appwrite'; ``` 4. Build the login page (client component) - Create/replace app/page.js with this component using "use client". - It must render: - Email/password inputs - Name input for registration - Buttons: Login, Register, Logout - Shows "Logged in as " when a session exists - Implement functions: - login(email, password): account.createEmailPasswordSession({ email, password }) then set user via account.get() - register(): account.create({ userId: ID.unique(), email, password, name }) then call login - logout(): account.deleteSession({ sessionId: 'current' }) then clear user state 5. Verify environment (ask user to confirm) - Confirm with the user that the endpoint and project ID are hardcoded in the file: app/appwrite.js (or app/appwrite.ts if TS). - Ensure the Web app platform exists in Appwrite Console with Hostname = `localhost`. If missing, guide the user to add it. 6. Run and test - Run: npm run dev - Open: http://localhost:3000 - Test flows: - Register a new user and auto login works - Logout then login again - Surface any Appwrite errors (invalid project, endpoint, CORS/hostname) and fix by guiding updates to appwrite.js and Console settings. 7. Optional hardening - If the user wants TypeScript, create app/appwrite.ts and app/page.tsx with proper types. - Add minimal styling if requested; functionality first. Deliverables - A running Next.js app with working Appwrite auth (register/login/logout) - Files created/updated: package.json (deps), app/appwrite.js, app/page.js --- ## Start with Node.js https://appwrite.io/docs/quick-starts/node Learn how to setup your first Node.js project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% /section %} {% section #step-2 step=2 title="Create Node.js project" %} Create a Node.js CLI application. ```sh mkdir my-app cd my-app npm init ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Node.js Appwrite SDK. ```sh npm install node-appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `app.js` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```js const sdk = require("node-appwrite"); const client = new sdk.Client(); client .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey(""); ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```js const tablesDB = new sdk.TablesDB(client); var todoDatabase; var todoTable; async function prepareDatabase() { todoDatabase = await tablesDB.create({ databaseId: sdk.ID.unique(), name: 'TodosDB' }); todoTable = await tablesDB.createTable({ databaseId: todoDatabase.$id, tableId: sdk.ID.unique(), name: 'Todos' }); await tablesDB.createStringColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'title', size: 255, required: true }); await tablesDB.createStringColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'description', size: 255, required: false, default: 'This is a test description' }); await tablesDB.createBooleanColumn({ databaseId: todoDatabase.$id, tableId: todoTable.$id, key: 'isComplete', required: true }); } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```js async function seedDatabase() { var testTodo1 = { title: 'Buy apples', description: 'At least 2KGs', isComplete: true }; var testTodo2 = { title: 'Wash the apples', isComplete: true }; var testTodo3 = { title: 'Cut the apples', description: 'Don\'t forget to pack them in a box', isComplete: false }; await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: sdk.ID.unique(), data: testTodo1 }); await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: sdk.ID.unique(), data: testTodo2 }); await tablesDB.createRow({ databaseId: todoDatabase.$id, tableId: todoTable.$id, rowId: sdk.ID.unique(), data: testTodo3 }); } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data and a function to execute the requests in order. Run the functions to by calling `runAllTasks();`. ```js const { Query } = require('node-appwrite'); async function getTodos() { // Retrieve rows (default limit is 25) var todos = await tablesDB.listRows({ databaseId: todoDatabase.$id, tableId: todoTable.$id }); console.log("Todos:"); todos.rows.forEach(todo => { console.log(`Title: ${todo.title}\nDescription: ${todo.description}\nIs Todo Complete: ${todo.isComplete}\n\n`); }); } async function getCompletedTodos() { // Use queries to filter completed todos with pagination var todos = await tablesDB.listRows({ databaseId: todoDatabase.$id, tableId: todoTable.$id, queries: [ Query.equal("isComplete", true), Query.orderDesc("$createdAt"), Query.limit(5) ] }); console.log("Completed todos (limited to 5):"); todos.rows.forEach(todo => { console.log(`Title: ${todo.title}\nDescription: ${todo.description}\nIs Todo Complete: ${todo.isComplete}\n\n`); }); } async function getIncompleteTodos() { // Query for incomplete todos var todos = await tablesDB.listRows({ databaseId: todoDatabase.$id, tableId: todoTable.$id, queries: [ Query.equal("isComplete", false), Query.orderAsc("title") ] }); console.log("Incomplete todos (ordered by title):"); todos.rows.forEach(todo => { console.log(`Title: ${todo.title}\nDescription: ${todo.description}\nIs Todo Complete: ${todo.isComplete}\n\n`); }); } async function runAllTasks() { await prepareDatabase(); await seedDatabase(); await getTodos(); await getCompletedTodos(); await getIncompleteTodos(); } runAllTasks(); ``` {% /section %} {% section #step-8 step=8 title="Type safety with TypeScript" %} For better type safety in TypeScript Node.js projects, define interfaces and use generics: ```typescript interface Todo { title: string; description: string; isComplete: boolean; } import { Client, TablesDB } from 'node-appwrite'; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const tablesDB = new TablesDB(client); // Type-safe database operations async function getTodos() { const todos = await tablesDB.listRows({ databaseId: '', tableId: '' }); todos.rows.forEach(todo => { console.log(`Title: ${todo.title} - Complete: ${todo.isComplete}`); }); } ``` {% info title="Generate types automatically" %} Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types` {% /info %} {% /section %} {% section #step-9 step=9 title="All set" %} Run your project with `node app.js` and view the response in your console. {% /section %} --- ## Start with Nuxt https://appwrite.io/docs/quick-starts/nuxt Learn how to setup your first Nuxt project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Nuxt project" %} Create a Nuxt project. ```sh npx nuxi@latest init my-app && cd my app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `utils/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account} from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `app.vue`. ```html ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with PHP https://appwrite.io/docs/quick-starts/php Learn how to setup your first PHP project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% /section %} {% section #step-2 step=2 title="Create PHP project" %} Create a PHP CLI application. ```sh mkdir my-app cd my-app composer init ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the PHP Appwrite SDK. ```sh composer require appwrite/appwrite:15.0.0 ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `index.php` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```php setEndpoint('https://.cloud.appwrite.io/v1') ->setProject('') ->setKey(''); ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```php $tablesDB = new TablesDB($client); function prepareDatabase($tablesDB) { $todoDatabase = $tablesDB->create( databaseId: ID::unique(), name: 'TodosDB' ); $todoTable = $tablesDB->createTable( databaseId: $todoDatabase['$id'], tableId: ID::unique(), name: 'Todos' ); $tablesDB->createStringColumn( databaseId: $todoDatabase['$id'], tableId: $todoTable['$id'], key: 'title', size: 255, required: true ); $tablesDB->createStringColumn( databaseId: $todoDatabase['$id'], tableId: $todoTable['$id'], key: 'description', size: 255, required: false, ); $tablesDB->createBooleanColumn( databaseId: $todoDatabase['$id'], tableId: $todoTable['$id'], key: 'isComplete', required: true ); return [$todoDatabase, $todoTable]; } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```php function seedDatabase($tablesDB, $todoDatabase, $todoTable) { $testTodo1 = [ 'title' => 'Buy apples', 'description' => 'At least 2KGs', 'isComplete' => true ]; $testTodo2 = [ 'title' => 'Wash the apples', 'isComplete' => true ]; $testTodo3 = [ 'title' => 'Cut the apples', 'description' => 'Don\'t forget to pack them in a box', 'isComplete' => false ]; $tablesDB->createRow( $todoDatabase['$id'], $todoTable['$id'], ID::unique(), $testTodo1 ); $tablesDB->createRow( $todoDatabase['$id'], $todoTable['$id'], ID::unique(), $testTodo2 ); $tablesDB->createRow( $todoDatabase['$id'], $todoTable['$id'], ID::unique(), $testTodo3 ); } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data and a function to execute the requests in order. Run the functions to by calling `runAllTasks();`. ```php use Appwrite\Query; function getTodos($tablesDB, $todoDatabase, $todoTable) { // Retrieve rows (default limit is 25) $todos = $tablesDB->listRows( $todoDatabase['$id'], $todoTable['$id'] ); echo "Todos:\n"; foreach ($todos['rows'] as $todo) { echo "Title: {$todo['title']}\n" . "Description: {$todo['description']}\n" . "Is Todo Complete: {$todo['isComplete']}\n\n"; } } function getCompletedTodos($tablesDB, $todoDatabase, $todoTable) { // Use queries to filter completed todos with pagination $todos = $tablesDB->listRows( $todoDatabase['$id'], $todoTable['$id'], [ Query::equal('isComplete', true), Query::orderDesc('$createdAt'), Query::limit(5) ] ); echo "Completed todos (limited to 5):\n"; foreach ($todos['rows'] as $todo) { echo "Title: {$todo['title']}\n" . "Description: {$todo['description']}\n" . "Is Todo Complete: {$todo['isComplete']}\n\n"; } } function getIncompleteTodos($tablesDB, $todoDatabase, $todoTable) { // Query for incomplete todos $todos = $tablesDB->listRows( $todoDatabase['$id'], $todoTable['$id'], [ Query::equal('isComplete', false), Query::orderAsc('title') ] ); echo "Incomplete todos (ordered by title):\n"; foreach ($todos['rows'] as $todo) { echo "Title: {$todo['title']}\n" . "Description: {$todo['description']}\n" . "Is Todo Complete: {$todo['isComplete']}\n\n"; } } function runAllTasks($tablesDB) { [$todoDatabase, $todoTable] = prepareDatabase($tablesDB); seedDatabase($tablesDB, $todoDatabase, $todoTable); getTodos($tablesDB, $todoDatabase, $todoTable); getCompletedTodos($tablesDB, $todoDatabase, $todoTable); getIncompleteTodos($tablesDB, $todoDatabase, $todoTable); } runAllTasks($tablesDB); ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `php src/index.php` and view the response in your console. {% /section %} --- ## Start with Python https://appwrite.io/docs/quick-starts/python Learn how to setup your first Python project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} {% /section %} {% section #step-2 step=2 title="Create Python project" %} Create a directory for the project. ```sh mkdir my_app cd my_app ``` After that, create a virtual environment in this directory and activate it. ```sh ### Create a venv python -m venv .venv ### Active the venv in Unix shell source .venv/bin/activate ### Or in Powershell .venv/Scripts/Activate.ps1 ``` Finally, create a file `my_app.py`. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Python Appwrite SDK. ```sh pip install appwrite==13.6.1 ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open `my_app.py` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```py from appwrite.client import Client from appwrite.services.tables_db import TablesDB from appwrite.id import ID client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') client.set_project('') client.set_key('') ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```py tablesDB = TablesDB(client) todoDatabase = None todoTable = None def prepare_database(): global todoDatabase global todoTable todoDatabase = tablesDB.create( database_id=ID.unique(), name='TodosDB' ) todoTable = tablesDB.create_table( database_id=todoDatabase['$id'], table_id=ID.unique(), name='Todos' ) tablesDB.create_string_column( database_id=todoDatabase['$id'], table_id=todoTable['$id'], key='title', size=255, required=True ) tablesDB.create_string_column( database_id=todoDatabase['$id'], table_id=todoTable['$id'], key='description', size=255, required=False, default='This is a test description.' ) tablesDB.create_boolean_column( database_id=todoDatabase['$id'], table_id=todoTable['$id'], key='isComplete', required=True ) ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```py def seed_database(): testTodo1 = { 'title': "Buy apples", 'description': "At least 2KGs", 'isComplete': True } testTodo2 = { 'title': "Wash the apples", 'isComplete': True } testTodo3 = { 'title': "Cut the apples", 'description': "Don\'t forget to pack them in a box", 'isComplete': False } tablesDB.create_row( database_id=todoDatabase['$id'], table_id=todoTable['$id'], row_id=ID.unique(), data=testTodo1 ) tablesDB.create_row( database_id=todoDatabase['$id'], table_id=todoTable['$id'], row_id=ID.unique(), data=testTodo2 ) tablesDB.create_row( database_id=todoDatabase['$id'], table_id=todoTable['$id'], row_id=ID.unique(), data=testTodo3 ) ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data, then execute the functions in `_main_`. ```py from appwrite.query import Query def get_todos(): ### Retrieve rows (default limit is 25) todos = tablesDB.list_rows( database_id=todoDatabase['$id'], table_id=todoTable['$id'] ) print("Todos:") for todo in todos['rows']: print(f"Title: {todo['title']}\nDescription: {todo['description']}\nIs Todo Complete: {todo['isComplete']}\n\n") def get_completed_todos(): ### Use queries to filter completed todos with pagination todos = tablesDB.list_rows( database_id=todoDatabase['$id'], table_id=todoTable['$id'], queries=[ Query.equal("isComplete", True), Query.order_desc("$createdAt"), Query.limit(5) ] ) print("Completed todos (limited to 5):") for todo in todos['rows']: print(f"Title: {todo['title']}\nDescription: {todo['description']}\nIs Todo Complete: {todo['isComplete']}\n\n") def get_incomplete_todos(): ### Query for incomplete todos todos = tablesDB.list_rows( database_id=todoDatabase['$id'], table_id=todoTable['$id'], queries=[ Query.equal("isComplete", False), Query.order_asc("title") ] ) print("Incomplete todos (ordered by title):") for todo in todos['rows']: print(f"Title: {todo['title']}\nDescription: {todo['description']}\nIs Todo Complete: {todo['isComplete']}\n\n") if __name__ == "__main__": prepare_database() seed_database() get_todos() get_completed_todos() get_incomplete_todos() ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `python my_app.py` and view the response in your console. {% /section %} --- ## Start with Qwik https://appwrite.io/docs/quick-starts/qwik Improve the docs, add this guide. We still don't have this guide in place, but we do have some great news. The Appwrite docs, just like Appwrite, is completely open sourced. This means, anyone can help improve them and add new guides and tutorials. If you see this page, **we're actively looking for contributions to this page**. Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. --- ## Start with React https://appwrite.io/docs/quick-starts/react Learn how to setup your first React project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create React project" %} Create a Vite project. ```sh npm create vite@latest my-app -- --template react && cd my-app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account} from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `src/App.jsx`. ```js import React, { useState } from 'react'; import { account, ID } from './lib/appwrite'; const App = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); async function login(email, password) { await account.createEmailPasswordSession({ email, password }); setLoggedInUser(await account.get()); } return (

{loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}

setEmail(e.target.value)} /> setPassword(e.target.value)} /> setName(e.target.value)} />
); }; export default App; ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with React Native https://appwrite.io/docs/quick-starts/react-native Learn how to setup your first React Native project powered by Appwrite. The React Native SDK is still in `beta`. Proceed with caution if you plan to use this SDK in production. {% info title="React for web" %} Looking to start with React for web? Follow the [React quickstart](/docs/quick-starts/react) and [React tutorial](/docs/tutorials/react/step-1) flows. {% /info %} {% section #step-1 step=1 title="Create React Native project" %} Create a React Native project using [npx](https://www.npmjs.com/package/npx). ```sh npx create-expo-app my-app cd my-app ``` {% /section %} {% section #step-2 step=2 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Android app** or a **Apple app**. {% tabs %} {% tabsitem #ios title="iOS" %} Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. **Note**: If you've followed the commands above, you have created an Expo project. This means you need to define the Bundle Identifier in the `app.json` configuration. [More info](https://docs.expo.dev/versions/latest/config/app/#bundleidentifier) {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} {% /tabsitem %} {% tabsitem #android title="Android" %} Add your app's **name** and **package name**, Your package name is generally the `applicationId` in your app-level [build.gradle](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/build.gradle#L41) file. **Note**: If you've followed the commands above, you have created an Expo project. This means you need to define the package name in the `app.json` configuration. [More info](https://docs.expo.dev/versions/latest/config/app/#package) {% arrow_link href="https://developer.android.com/build/configure-app-module" %} Learn more about Android app module {% /arrow_link %} {% /tabsitem %} {% /tabs %} You can skip optional steps. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Appwrite SDK for React Native and required dependencies. ```sh npx expo install react-native-appwrite react-native-url-polyfill ``` {% /section %} {% section #step-4 step=4 title="Implement Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open `app/(tabs)/index.tsx` and add the following code to it, replace `` with your project ID and `` with your application id or package name. This imports and initializes Appwrite and defines some basic authentication methods. ```client-react-native import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native'; import { Client, Account, ID, Models } from 'react-native-appwrite'; import React, { useState } from 'react'; let client: Client; let account: Account; client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('66e943139f030e2feaf8') // Your Project ID .setPlatform('com.example.my-app'); // Your package name / bundle identifier account = new Account(client); export default function App() { const [loggedInUser, setLoggedInUser] = useState | null>(null); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); async function login(email: string, password: string) { await account.createEmailPasswordSession({ email, password }); setLoggedInUser(await account.get()); } async function register(email: string, password: string, name: string) { await account.create({ userId: ID.unique(), email, password, name }); await login(email, password); setLoggedInUser(await account.get()); } return ( // ... Implement your UI here ); } const styles = StyleSheet.create({ // ... define some styles }); ``` {% /section %} {% section #step-5 step=5 title="Create a login form" %} With `Client` and `Account` service initialized, you can now use them to make your first requests to Appwrite. Add the following components to your `App.js` file to create a simple login form. ```client-react-native {loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'} setEmail(text)} /> setPassword(text)} secureTextEntry /> setName(text)} /> login(email, password)} > Login register(email, password, name)} > Register { await account.deleteSession({ sessionId: 'current' }); setLoggedInUser(null); }} > Logout ``` You can also add some simple styling to your app by adding the following styles to your `App.js` file. ```client-react-native const styles = StyleSheet.create({ root: { marginTop: 40, marginBottom: 40 }, input: { height: 40, borderColor: 'gray', borderWidth: 1, marginBottom: 10, paddingHorizontal: 10, }, button: { backgroundColor: 'gray', padding: 10, marginBottom: 10, alignItems: 'center', }, }); ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npx expo start`. {% arrow_link href="https://github.com/appwrite/playground-for-react-native" %} Explore the React Native playground {% /arrow_link %} {% /section %} --- ## prompt https://appwrite.io/docs/quick-starts/react-native/prompt Goal: Add Appwrite auth to a new React Native (Expo) app with a minimal login/register/logout UI. Rules - Never assume project details. Ask the user for Cloud Region, Project ID, and package/bundle ID. - Use explicit config (no hardcoding in code except reading constants/env the user sets). - Respect the user’s package manager and Expo workflow. 1. Scaffold Expo app - Run: npx create-expo-app my-app && cd my-app 2. Install SDK and polyfills - Run: npx expo install react-native-appwrite react-native-url-polyfill 3. Configure identifiers (ask user) - Ask user for Android package name and iOS bundle identifier. Guide them to set these in `app.json`. - Ask for Cloud Region and Project ID from Console → Settings. 4. Client setup (key snippet) - File: `app/lib/appwrite.ts` (or `.js`) ```ts import 'react-native-url-polyfill/auto'; import { Client, Account, ID } from 'react-native-appwrite'; const endpoint = 'https://.cloud.appwrite.io/v1'; // ask user for const project = ''; // ask user for ID const platform = ''; // ask user for this const client = new Client().setEndpoint(endpoint).setProject(project).setPlatform(platform); export const account = new Account(client); export { ID }; ``` 5. UI wiring (idea + key snippets) - Screen file (e.g., `app/(tabs)/index.tsx`): ```tsx import React, { useState } from 'react'; import { View, Text, TextInput, TouchableOpacity } from 'react-native'; import { account, ID } from '../lib/appwrite'; export default function AuthScreen() { const [user, setUser] = useState(null); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); async function login(e: string, p: string) { await account.createEmailPasswordSession({ email: e, password: p }); setUser(await account.get()); } async function register() { await account.create({ userId: ID.unique(), email, password, name }); await login(email, password); } async function logout() { await account.deleteSession({ sessionId: 'current' }); setUser(null); } return ( {user ? Logged in as {user.name} : null} login(email, password)}> Login Register {user && ( Logout )} ); } ``` - Minimal JSX: display user name when logged in; inputs for email/password/name; buttons for Login/Register/Logout. 6. Verify platforms - Ask user to add Android and/or iOS platform in Console. Use the configured package/bundle identifiers. 7. Run & test - Run: npx expo start - Test register → auto login → logout → login. Deliverables - `app/lib/appwrite.ts`, updated screen with minimal form and actions --- ## Start with Refine https://appwrite.io/docs/quick-starts/refine Learn how to setup your first Refine project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Refine project" %} Create a Refine project with Appwrite support. ```sh npm create refine-app@latest -- --preset refine-appwrite ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Using the `refine-appwrite` preset eliminates the need for extra dependencies for a quick start. If you want to integrate Appwrite into an existing Refine app, simply use this command: ```sh npm install @refinedev/appwrite ``` Then follow [this guide on the Refine documentation site](https://refine.dev/docs/packages/documentation/data-providers/appwrite). {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Navigate to `src/utility/appwriteClient.ts` and add your API credentials. ```ts import { Account, Appwrite, Storage } from "@refinedev/appwrite"; const APPWRITE_URL = ''; // Replace with your Appwrite API Endpoint const APPWRITE_PROJECT = ""; // Replace with your project ID const appwriteClient = new Appwrite(); appwriteClient.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT); const account = new Account(appwriteClient); const storage = new Storage(appwriteClient); export { account, appwriteClient, storage }; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Replace the code in `src/App.tsx` with the following. ```client-web import { Authenticated, Refine } from '@refinedev/core'; import { dataProvider, liveProvider } from '@refinedev/appwrite'; import { AuthPage, ErrorComponent, RefineThemes, ThemedLayoutV2, useNotificationProvider, } from '@refinedev/antd'; import routerProvider, { CatchAllNavigate, NavigateToResource, } from '@refinedev/react-router-v6'; import '@refinedev/antd/dist/reset.css'; import { App as AntdApp, ConfigProvider } from 'antd'; import { BrowserRouter, Outlet, Route, Routes } from 'react-router-dom'; import { appwriteClient } from './utility'; import { authProvider } from './authProvider'; const App: React.FC = () => { return ( ', })} liveProvider={liveProvider(appwriteClient, { databaseId: '', })} authProvider={authProvider} routerProvider={routerProvider} notificationProvider={useNotificationProvider} > } > } > }> } > } /> } /> } > } /> ); }; export default App; ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with Ruby https://appwrite.io/docs/quick-starts/ruby Learn how to setup your first Ruby project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% /section %} {% section #step-2 step=2 title="Create Ruby project" %} Create a Ruby CLI application. ```sh mkdir my-app cd my-app bundle init ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Ruby Appwrite SDK. Make sure to lock your SDK to version `10.0.0` to avoid breaking changes. ```sh bundle add appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `app.rb` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```ruby ### Initialize the Appwrite client require 'appwrite' include Appwrite client = Client.new() client .set_endpoint('https://.cloud.appwrite.io/v1') # Your Appwrite Endpoint .set_project('') # Your project ID .set_key('') # Your secret API key ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```ruby tablesDB = TablesDB.new(client) todo_database = nil todo_table = nil def prepare_database(databases) todo_database = tablesDB.create( database_id: ID.unique(), name: 'TodosDB' ) todo_table = tablesDB.create_table( database_id: todo_database.id, table_id: ID.unique(), name: 'Todos' ) tablesDB.create_string_column( database_id: todo_database.id, table_id: todo_table.id, key: 'title', size: 255, required: true ) tablesDB.create_string_column( database_id: todo_database.id, table_id: todo_table.id, key: 'description', size: 255, required: false ) tablesDB.create_boolean_column( database_id: todo_database.id, table_id: todo_table.id, key: 'isComplete', required: false, default: false ) return todo_database, todo_table end ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```ruby def seed_database(databases, todo_database, todo_table) test_todo1 = { title: 'Buy apples', description: 'At least 2KGs', isComplete: true } test_todo2 = { title: 'Wash the apples', isComplete: true } test_todo3 = { title: 'Cut the apples', description: 'Don\'t forget to pack them in a box', isComplete: false } tablesDB.create_row( database_id: todo_database.id, table_id: todo_table.id, row_id: ID.unique(), data: test_todo1 ) tablesDB.create_row( database_id: todo_database.id, table_id: todo_table.id, row_id: ID.unique(), data: test_todo2 ) tablesDB.create_row( database_id: todo_database.id, table_id: todo_table.id, row_id: ID.unique(), data: test_todo3 ) end ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data and a function to execute the requests in order. Run the functions to by calling `run_all_tasks()`. ```ruby def get_todos(databases, todo_database, todo_table) todos = tablesDB.list_rows( database_id: todo_database.id, table_id: todo_table.id ) todos.rows.each do |todo| puts "Title: #{todo.data['title']}\nDescription: #{todo.data['description']}\nIs Todo Complete: #{todo.data['isComplete']}\n\n" end end def run_all_tasks(databases) todo_database, todo_table = prepare_database(databases) seed_database(databases, todo_database, todo_table) get_todos(databases, todo_database, todo_table) end run_all_tasks(databases) ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with `ruby app.rb` and view the response in your console. {% /section %} --- ## Start with Solid https://appwrite.io/docs/quick-starts/solid Learn how to setup your first Solid project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Solid project" %} Create a Vite project. ```sh npm create vite@latest my-app -- --template solid && cd my-app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account } from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `src/App.jsx`. ```client-web import { createSignal } from 'solid-js' import { account, ID } from './lib/appwrite'; const App = () => { const [loggedInUser, setLoggedInUser] = createSignal(null); const [email, setEmail] = createSignal(''); const [password, setPassword] = createSignal(''); const [name, setName] = createSignal(''); async function login(email, password) { await account.createEmailPasswordSession({ email, password }); setLoggedInUser(await account.get()); } async function register(email, password, name) { await account.create({ userId: ID.unique(), email, password, name }); login(email, password); } async function logout() { await account.deleteSession({ sessionId: 'current' }); setLoggedInUser(null); } if (loggedInUser()) { return (

Logged in as {loggedInUser().name}

); } return (

Not logged in

setEmail(e.target.value)} /> setPassword(e.target.value)} /> setName(e.target.value)} />
); }; export default App; ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with SvelteKit https://appwrite.io/docs/quick-starts/sveltekit Learn how to setup your first SvelteKit project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create SvelteKit project" %} Create a SvelteKit project. ```sh npx sv create ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account } from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Replace the contents of `src/routes/+page.svelte` with the following code. ```html

{loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}

``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev` and open [localhost on port 5173](http://localhost:5173) in your browser. {% /section %} --- ## Start with Swift https://appwrite.io/docs/quick-starts/swift Learn how to setup your first Swift project powered by Appwrite. {% info title="Server SDK" %} This tutorial is for the Swift Server SDK, meant for server and backend applications. If you're trying to build a client-side app, like an iOS, macOS, watchOS or tvOS app, follow the [Start with Apple guide](https://appwrite.io/docs/quick-starts/apple). {% /info %} {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). | | | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). | | | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). | | | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). | | | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). | Other scopes are optional. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} {% /section %} {% section #step-2 step=2 title="Create Swift project" %} Create a Swift CLI application by opening **XCode** > **Create a new XCode project** > **macOS** > **Command Line Tool**. Follow the wizard and open your new project. {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the Swift Appwrite SDK by going to **File** > **Add Packages...** and search for the repo url `https://github.com/appwrite/sdk-for-swift` and select `sdk-for-swift`. Specify version as `10.0.0` with rule **Up to Next Major Version**. {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Open the file `main.swift` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. ```swift import Foundation import Appwrite import AppwriteModels let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") .setProject("") .setKey("") ``` {% /section %} {% section #step-5 step=5 title="Initialize database" %} Once the Appwrite Client is initialized, create a function to configure a todo table. ```swift let tablesDB = TablesDB(client) func prepareDatabase() async -> (Database?, Table?) { let todoDatabase = try? await tablesDB.create( databaseId: ID.unique(), name: "TodosDB" ) let todoTable = try? await tablesDB.createTable( databaseId: todoDatabase!.id, tableId: ID.unique(), name: "Todos" ) try? await tablesDB.createStringColumn( databaseId: todoDatabase!.id, tableId: todoTable!.id, key: "title", size: 255, xrequired: true ) try? await tablesDB.createStringColumn( databaseId: todoDatabase!.id as! String, tableId: todoTable!.id as! String, key: "description", size: 255, xrequired: false, xdefault: "This is a test description." ) try? await tablesDB.createBooleanColumn( databaseId: todoDatabase!.id as! String, tableId: todoTable!.id as! String, key: "isComplete", xrequired: true ) return (todoDatabase, todoTable) } ``` {% /section %} {% section #step-6 step=6 title="Add rows" %} Create a function to add some mock data into your new table. ```swift func seedDatabase(todoDatabase: Database?, todoTable: Table?) async { let testTodo1: [String: Any] = [ "title": "Buy apples", "description": "At least 2KGs", "isComplete": true ] let testTodo2: [String: Any] = [ "title": "Wash the apples", "isComplete": true ] let testTodo3: [String: Any] = [ "title": "Cut the apples", "description": "Don't forget to pack them in a box", "isComplete": false ] try? await tablesDB.createRow( databaseId: todoDatabase!.id, tableId: todoTable!.id, rowId: ID.unique(), data: testTodo1 ) try? await tablesDB.createRow( databaseId: todoDatabase!.id, tableId: todoTable!.id, rowId: ID.unique(), data: testTodo2 ) try? await tablesDB.createRow( databaseId: todoDatabase!.id, tableId: todoTable!.id, rowId: ID.unique(), data: testTodo3 ) } ``` {% /section %} {% section #step-7 step=7 title="Retrieve rows" %} Create a function to retrieve the mock todo data. ```swift func getTodos(todoDatabase: Database?, todoTable: Table?) async { let todos = try? await tablesDB.listRows( databaseId: todoDatabase!.id as! String, tableId: todoTable!.id as! String ) for row in todos?.rows ?? [] { if let todo = row.data as? [String: Any] { print("Title: \(todo["title"] ?? "")\n" + "Description: \(todo["description"] ?? "")\n" + "Is Todo Complete: \(todo["isComplete"] ?? "")\n\n" ) } } } let (todoDatabase, todoTable) = await prepareDatabase() await seedDatabase(todoDatabase: todoDatabase, todoTable: todoTable) await getTodos(todoDatabase: todoDatabase, todoTable: todoTable) ``` {% /section %} {% section #step-8 step=8 title="All set" %} Run your project with XCode and see the results in the console. {% /section %} --- ## Start with TanStack Start https://appwrite.io/docs/quick-starts/tanstack-start Learn how to setup your first TanStack Start project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create TanStack Start project" %} Create a TanStack Start project. ```sh npm create @tanstack/start@latest my-app && cd my-app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/utils/appwrite.ts` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account, ID, Models } from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID }; export type { Models }; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Create or update `src/routes/index.tsx` with the following code. ```tsx import { useState } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import { account, ID, type Models } from '../utils/appwrite'; export const Route = createFileRoute('/')({ component: Index, }); function Index() { const [loggedInUser, setLoggedInUser] = useState | null>(null); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); async function login(email: string, password: string) { await account.createEmailPasswordSession({ email, password, }); setLoggedInUser(await account.get()); } async function register() { await account.create({ userId: ID.unique(), email, password, name, }); await login(email, password); } async function logout() { await account.deleteSession({ sessionId: 'current' }); setLoggedInUser(null); } if (loggedInUser) { return (

Logged in as {loggedInUser.name}

); } return (

Not logged in

setEmail(e.target.value)} /> setPassword(e.target.value)} /> setName(e.target.value)} />
); } ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev` and open [localhost on port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with Vue.js https://appwrite.io/docs/quick-starts/vue Learn how to setup your first Vue project powered by Appwrite. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Create Vue project" %} Create a Vue project. ```sh npm init vue@latest my-app && cd my-app ``` {% /section %} {% section #step-3 step=3 title="Install Appwrite" %} Install the JavaScript Appwrite SDK. ```sh npm install appwrite ``` {% /section %} {% section #step-4 step=4 title="Import Appwrite" %} Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID. ```client-web import { Client, Account} from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` {% /section %} {% section #step-5 step=5 title="Create a login page" %} Add the following code to `src/App.vue`. ```html ``` {% /section %} {% section #step-6 step=6 title="All set" %} Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser. {% /section %} --- ## Start with Web https://appwrite.io/docs/quick-starts/web Learn how to add Appwrite to your web apps. {% section #step-1 step=1 title="Create project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost` or the domain on which you're hosting your web app. {% partial file="note-on-cors.md" /%} {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /section %} {% section #step-2 step=2 title="Install Appwrite" %} You can install the Appwrite Web SDK using a package manager. ```sh npm install appwrite ``` You can also add the Appwrite Web SDK using CDN by adding a script tag to your HTML file. The SDK will be available globally through the `Appwrite` namespace. ```html ``` {% /section %} {% section #step-3 step=3 title="Initialize Appwrite" %} If you installed via npm, you can import `Client` and `Account` from the Appwrite SDK. ```client-web import { Client, Account } from 'appwrite'; export const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account = new Account(client); export { ID } from 'appwrite'; ``` If you're using CDN, the library loads directly in your browser as a global object, so you access it through Appwrite instead of imports. ```js const client = new Appwrite.Client() client .setEndpoint('https://cloud.appwrite.io/v1') .setProject('') // Replace with your project ID const account = new Appwrite.Account(client) const tablesDB = new Appwrite.TablesDB(client) ``` {% /section %} {% section #step-4 step=4 title="Using TypeScript" %} If you prefer TypeScript, you can import TypeScript models from the Appwrite SDK. ```ts // appwrite.ts import { Client, TablesDB, Account } from "appwrite"; // Import type models for Appwrite import { type Models } from 'appwrite'; const client: Client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); // Replace with your project ID export const account: Account = new Account(client); export const tablesDB: TablesDB = new TablesDB(client); // You then use the imported type definitions like this const authUser: Models.Session = await account.createEmailPasswordSession({ email, password }); ``` {% /section %} {% section #step-5 step=5 title="Extending TypeScript models" %} Sometimes you'll need to extend TypeScript models with your own type definitions. For example, when you fetch a list of rows from a table, you can define the expected structure of the rows like this. ```ts interface Idea extends Models.Row { title: string; description: string; userId: string; } ``` When you fetch rows, you can use this new `Idea` interface like this. ```ts const response = await tablesDB.listRows({ databaseId: ideasDatabaseId, tableId: ideasTableId, queries: [Query.orderDesc("$createdAt"), Query.limit(queryLimit)] }); const ideas = response.rows as Idea[]; ``` {% /section %} {% section #step-6 step=6 title="All set" %} The Appwrite SDK works with your favorite Web frameworks. Learn to use Appwrite by adding authentication to a simple web app. {% cards %} {% cards_item href="/docs/quick-starts/nextjs" title="Next.js" %} Get started with Appwrite and Next.js {% /cards_item %} {% cards_item href="/docs/quick-starts/react" title="React" %} Get started with Appwrite and React {% /cards_item %} {% cards_item href="/docs/quick-starts/vue" title="Vue.js" %} Get started with Appwrite and Vue.js {% /cards_item %} {% cards_item href="/docs/quick-starts/nuxt" title="Nuxt" %} Get started with Appwrite and Nuxt {% /cards_item %} {% cards_item href="/docs/quick-starts/sveltekit" title="SvelteKit" %} Get started with Appwrite and SvelteKit {% /cards_item %} {% cards_item href="/docs/quick-starts/angular" title="Angular" %} Get started with Appwrite and Angular {% /cards_item %} {% /cards %} Learn to use Appwrite by building an idea tracker app. {% cards %} {% cards_item href="/docs/tutorials/react" title="React" %} Get started with Appwrite and React {% /cards_item %} {% cards_item href="/docs/tutorials/vue" title="Vue.js" %} Get started with Appwrite and Vue.js {% /cards_item %} {% cards_item href="/docs/tutorials/nuxt" title="Nuxt" %} Get started with Appwrite and Nuxt {% /cards_item %} {% cards_item href="/docs/tutorials/sveltekit" title="SvelteKit" %} Get started with Appwrite and SvelteKit {% /cards_item %} {% /cards %} {% /section %} {% section #step-7 step=7 title="Type safety with TypeScript" %} #### Type safety with TypeScript For better type safety in TypeScript projects, define interfaces and use generics: ```typescript interface User { name: string; email: string; isVerified: boolean; } import { Client, TablesDB } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new TablesDB(client); // Type-safe database operations try { const users = await databases.listRows({ databaseId: '[DATABASE_ID]', tableId: '[TABLE_ID]' }); users.rows.forEach(user => { console.log(`User: ${user.name} (${user.email})`); }); } catch (error) { console.log(error); } ``` {% info title="Generate types automatically" %} Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types` {% /info %} {% /section %} --- ## API reference https://appwrite.io/docs/references Appwrite lets you build integrations on web, mobile, native, and server platforms through a set of APIs. You can use one of our many [SDKs](/docs/sdks) or integrate directly through the [REST API](/docs/apis/rest) or [GraphQL API](/docs/apis/graphql). #### Client vs Server APIs {% #client-vs-server %} Client APIs and SDKs are for integrating with Appwrite to build client-based applications and websites. Client APIs only give access to resources if users have been [granted permissions](/docs/advanced/platform/permissions). Server API and SDKs are for integrating with Appwrite to build backend or server applications. Server APIs are constrained by an [API key's](/docs/advanced/platform/api-keys) scope, ignoring user permissions. #### APIs {% #api %} Before using the Appwrite APIs, in the **Settings** of your Appwrite project, obtain your **API endpoint** and **Project ID**. Client APIs require an active session, created from [signing up and logging in](/docs/products/auth/accounts#signup-login). Server APIs require [API keys](/docs/advanced/platform/api-keys). {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} {% cards %} {% cards_item href="/docs/references/cloud/client-web/account" title="Account" %} Add signup, login, logout, and other common authentication methods to client apps. {% /cards_item %} {% cards_item href="/docs/references/cloud/server-nodejs/users" title="Users" %} Manage users from an admin scope to build server integrations. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/teams" title="Teams" %} Group users to share access to rows, files, and functions. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/databases" title="Databases" %} Scalable and robust database backed by your favorite technologies. {% /cards_item %} {% cards_item href="/docs/references/cloud/server-nodejs/sites" title="Sites" %} Build and deploy websites on the internet at scale. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/storage" title="Storage" %} Securely store files with advanced compression, encryption and image transformations. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/functions" title="Functions" %} Deploy and scale serverless functions in secure, isolated runtimes. {% /cards_item %} {% cards_item href="/docs/references/cloud/server-nodejs/messaging" title="Messaging" %} Send push notifications, SMS, or emails to users or groups of users using your app. {% /cards_item %} {% cards_item href="/docs/references/cloud/server-nodejs/tokens" title="Tokens" %} Create and manage temporary access tokens for secure file sharing and resource access control. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/locale" title="Localization" %} Utility APIs to customize your app based on your users' location. {% /cards_item %} {% cards_item href="/docs/references/cloud/client-web/avatars" title="Avatars" %} Complete everyday tasks related to your app image, icons, and avatars. {% /cards_item %} {% /cards %} #### Error handling {% #error-handling %} When building with Appwrite, implement proper error handling to provide user-friendly messages instead of exposing raw error responses. For implementation details and best practices, refer to our [Error handling guide](/docs/advanced/platform/error-handling) and [Response codes](/docs/advanced/platform/response-codes#error-types) documentation. --- ## Quick start https://appwrite.io/docs/references/quick-start Follow these steps before you begin using the Appwrite SDKs or accessing Appwrite through the REST and GraphQL API. {% section #select-the-right-API step=1 title="Select the right API" %} Appwrite has two types of APIs for different use cases, select one or both depending on your use case. If you're creating a **web, mobile, or native application** used by end-users that will register and create accounts, install a [Client SDK](/docs/sdks#client) and follow steps for Client APIs. If you're create a server application, like a **backend, admin app, or a CLI tool**, install a [Server SDK](/docs/sdks#server) and follow steps for Server APIs. If you're creating a **Server-side Rendered (SSR)** web app, install a [Server SDK](/docs/sdks#server) and follow steps for SSR. {% /section %} {% section #configure-project step=2 title="Configure project" %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). If this is your first time using Appwrite, create an account and create your first project. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} Then, configure your project depending on use case. You can follow all three flows to enable all three use cases. {% tabs %} {% tabsitem #client title="Client" %} Under **Add a platform**, add a platform for **each** web, mobile, and native app you plan to create. This means, a different platform for each web app hosted under a different domain, and a different platform for each mobile or native app that use a different package ID. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. {% /tabsitem %} {% tabsitem #server title="Server" %} Under **Integrate with your server**, add an **API Key**. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} Enable the scopes for the Appwrite products you plan to use for your app. It's a good idea to only grant scopes that you need, and edit the API keys as your needs change for security. {% /tabsitem %} {% tabsitem #ssr title="SSR" %} Under **Integrate with your server**, add an **API Key** with the following scopes. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/integrate-server.png) {% /only_light %} | Category {% width=120 %} | Required scopes | Purpose | |-----------|-----------------------|---------| | Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. | {% /tabsitem %} {% /tabs %} {% /section %} {% section #initialize-sdks step=3 title="Initialize SDKs" %} When using the Appwrite APIs, you need to pass information like endpoint, project ID, credentials and other metadata for Appwrite to properly parse your request. {% tabs %} {% tabsitem #client title="Client" %} Client apps need to be configured with endpoint and project ID, so the Appwrite SDK knows which endpoint and project to connect to. {% multicode %} ```client-web import { Client } from "appwrite"; const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; ``` ```client-flutter import 'package:appwrite/appwrite.dart'; Client client = Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID ; ``` ```client-apple import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID ``` ```client-android-kotlin import io.appwrite.Client val client = Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID ``` {% /multicode %} {% /tabsitem %} {% tabsitem #server title="Server" %} Server apps need to be configured with endpoint, project ID, and an API key so the Appwrite SDK knows which endpoint and project to connect to, as well as have credentials to perform admin actions. {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('') // Your secret JSON Web Token ; ``` ```deno import * as sdk from "npm:node-appwrite"; // Init SDK let client = new sdk.Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('') // Your secret JSON Web Token ; ``` ```php setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setJWT('') // Your secret JSON Web Token ; ``` ```python from appwrite.client import Client client = Client() (client .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('') # Your secret JSON Web Token ) ``` ```ruby require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint .set_project('') # Your project ID .set_jwt('') # Your secret JSON Web Token ``` ```csharp using Appwrite; using Appwrite.Services; var client = new Client() .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .SetProject("") // Your project ID .SetJWT(""); // Your secret JSON Web Token ``` ```dart import 'package:dart_appwrite/dart_appwrite.dart'; void main() { // Init SDK Client client = Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setJWT('') // Your secret JSON Web Token ; } ``` ```kotlin import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT(""); // Your secret JSON Web Token ``` ```java import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT(""); // Your secret JSON Web Token ``` ```swift import Appwrite let client = Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint .setProject("") // Your project ID .setJWT("") // Your secret JSON Web Token ``` {% /multicode %} {% /tabsitem %} {% tabsitem #ssr title="SSR" %} Appwrite uses Server SDKs for SSR apps. The initialization is different #### Admin client {% #admin-client %} {% info title="Admin clients" %} Admin clients should only be used if you need to perform admin actions that bypass permissions or [unauthenticated requests that bypass rate limits](/docs/products/auth/server-side-rendering#rate-limits). {% /info %} To initialize the admin client, we'll need to first [generated an API key](/docs/advanced/platform/api-keys#create-api-key). The API key should have the following scope in order to perform authentication: | Category {% width=120 %} | Required scopes | Purpose | |-----------|---------------------|---------| | Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. | {% multicode %} ```server-nodejs import { Client } from "node-appwrite"; // Using the server SDK const adminClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey(''); // Your secret API key ``` ```php use Appwrite\Client; use Appwrite\Services\Account; $adminClient = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setKey(''); // Your secret API key ``` {% /multicode %} It is important to use an API key, as this will allow your server requests to bypass [rate limits](/docs/advanced/platform/rate-limits). If you don't use an API key, your server will be rate limited as if it were a client from a single IP address. #### Session client {% #session-client %} The session client will be used to make requests to Appwrite on behalf of the end-user. It will be initialized with the session, usually stored within a cookie. You should create a new client for each request and **never** share the client between requests. {% multicode %} ```server-nodejs const sessionClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const session = req.cookies.session; // Get the session cookie from the request if (session) { sessionClient.setSession(session); } ``` ```php $sessionClient = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject(''); // Your project ID $session = $_COOKIE['session']; // Get the session cookie from the request if ($session) { $sessionClient->setSession($session); } ``` {% /multicode %} {% /tabsitem %} You will use the initialized client in all requests you make to Appwrite. {% /tabs %} If you're using Appwrite without an SDK, follow the guides for the [REST API](/docs/apis/rest) or [GraphQL API](/docs/apis/graphql). {% /section %} {% section #examples step=4 title="Examples" %} If you prefer to explore examples, follow one of the following quick starts. #### Client apps {% #client-app %} Examples when building with Client APIs {% cards %} {% cards_item href="/docs/quick-starts/web" title="Web" icon="icon-nextjs" %} Just plain JavaScript and TypeScript. {% /cards_item %} {% cards_item href="/docs/quick-starts/nextjs" title="Next.js" icon="icon-nextjs" %} Get started with Appwrite and Next.js {% /cards_item %} {% cards_item href="/docs/quick-starts/react" title="React" icon="icon-react" %} Get started with Appwrite and React {% /cards_item %} {% cards_item href="/docs/quick-starts/vue" title="Vue.js" icon="web-icon-vue" %} Get started with Appwrite and Vue.js {% /cards_item %} {% cards_item href="/docs/quick-starts/nuxt" title="Nuxt" icon="web-icon-nuxt" %} Get started with Appwrite and Nuxt {% /cards_item %} {% cards_item href="/docs/quick-starts/sveltekit" title="SvelteKit" icon="icon-svelte" %} Get started with Appwrite and SvelteKit {% /cards_item %} {% cards_item href="/docs/quick-starts/angular" title="Angular" icon="icon-angular" %} Get started with Appwrite and Angular {% /cards_item %} {% cards_item href="/docs/quick-starts/flutter" title="Flutter" icon="icon-flutter" %} Get started with Appwrite and Flutter {% /cards_item %} {% cards_item href="/docs/quick-starts/apple" title="Apple" icon="icon-apple" %} Get started with Appwrite and Apple {% /cards_item %} {% cards_item href="/docs/quick-starts/android" title="Android" icon="icon-android" %} Get started with Appwrite and Android {% /cards_item %} {% /cards %} #### Server apps {% #server-app %} Examples when building with Server APIs {% cards %} {% cards_item href="/docs/quick-starts/node" title="Node.js" icon="icon-node_js" %} Get started with Appwrite and Node.js {% /cards_item %} {% cards_item href="/docs/quick-starts/python" title="Python" icon="icon-python" %} Get started with Appwrite and Python {% /cards_item %} {% cards_item href="/docs/quick-starts/dart" title="Dart" icon="icon-dart" %} Get started with Appwrite and Dart {% /cards_item %} {% cards_item href="/docs/quick-starts/php" title="PHP" icon="icon-php" %} Get started with Appwrite and PHP {% /cards_item %} {% cards_item href="/docs/quick-starts/ruby" title="Ruby" icon="icon-ruby" %} Get started with Appwrite and Ruby {% /cards_item %} {% cards_item href="/docs/quick-starts/dotnet" title=".NET" icon="icon-dotnet" %} Get started with Appwrite and .NET {% /cards_item %} {% cards_item href="/docs/quick-starts/deno" title="Deno" icon="icon-deno" %} Get started with Appwrite and Deno {% /cards_item %} {% cards_item href="/docs/quick-starts/go" title="Go" icon="icon-go" %} Get started with Appwrite and Go {% /cards_item %} {% cards_item href="/docs/quick-starts/swift" title="Swift" icon="icon-swift" %} Get started with Appwrite and Swift {% /cards_item %} {% cards_item href="/docs/quick-starts/kotlin" title="Kotlin" icon="icon-kotlin" %} Get started with Appwrite and Kotlin {% /cards_item %} {% /cards %} {% /section %} --- ## SDKs https://appwrite.io/docs/sdks Appwrite provides SDK libraries for major programming languages and platforms so you don't have to write code for interacting with our API protocols from scratch. We're always working on improving and extending the current stack of available platforms and SDKs, listed below is a list of official libraries the Appwrite team is maintaining. ### Client {% #client %} Client libraries for integrating with Appwrite to build client-based applications and websites. Read one of the many [quick starts](/docs/quick-starts) guides for your framework of choice to start building your first application. {% table %} *   {% width=48 %} * Platform * GitHub Repository *   {% width=80 %} --- * {% only_dark %}{% icon_image src="/images/platforms/dark/javascript.svg" alt="Javascript logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/javascript.svg" alt="Javascript logo" size="m" /%}{% /only_light %} * Web SDK `21.0.0` * [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/flutter.svg" alt="Flutter logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/flutter.svg" alt="Flutter logo" size="m" /%}{% /only_light %} * Flutter SDK `20.0.0` * [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/react.svg" alt="React logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/react.svg" alt="React logo" size="m" /%}{% /only_light %} * React Native SDK `0.15.0` * [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native) * `beta` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/apple.svg" alt="Apple logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/apple.svg" alt="Apple logo" size="m" /%}{% /only_light %} * Apple SDK `13.0.0` * [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/android.svg" alt="Android logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/android.svg" alt="Android logo" size="m" /%}{% /only_light %} * Android SDK `11.0.0` * [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android) * {% /table %} ### Server {% #server %} Server libraries for integrating with Appwrite to build server side integrations or use inside your [Appwrite Functions](/docs/products/functions). Read one of the many [quick starts](/docs/quick-starts) guides for your language/runtime of choice to start building your first server integration. {% table %} *   {% width=48 %} * Platform * GitHub Repository *   {% width=80 %} --- * {% only_dark %}{% icon_image src="/images/platforms/dark/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_light %} * Node.js SDK `20.0.0` * [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/python.svg" alt="Python logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/python.svg" alt="Python logo" size="m" /%}{% /only_light %} * Python SDK `13.2.0` * [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dart.svg" alt="Dart logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dart.svg" alt="Dart logo" size="m" /%}{% /only_light %} * Dart SDK `19.0.0` * [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/php.svg" alt="PHP logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/php.svg" alt="PHP logo" size="m" /%}{% /only_light %} * PHP SDK `17.2.0` * [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_light %} * Ruby SDK `19.0.0` * [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_light %} * .NET SDK `0.19.0` * [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet) * `beta` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/go.svg" alt="Go logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/go.svg" alt="Go logo" size="m" /%}{% /only_light %} * Go SDK `0.13.0` * [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go) * `beta` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/swift.svg" alt="Swift logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/swift.svg" alt="Swift logo" size="m" /%}{% /only_light %} * Swift SDK `13.0.0` * [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_light %} * Kotlin SDK `12.0.0` * [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin) * {% /table %} If you would like to help us extend our platforms and SDKs stack, you are more than welcome to contact us or contribute to the [Appwrite SDK Generator](https://github.com/appwrite/sdk-generator) project GitHub repository and read our contribution guide. ### Protocols {% #protocols %} We are always looking to add new SDKs to our platform. If the SDK you are looking for is still missing, labeled as beta or experimental, or you simply do not want to integrate with an SDK, you can always integrate with Appwrite directly using any standard HTTP, GraphQL, or WebSocket clients and the relevant Appwrite protocol. Appwrite supports multiple API protocols for maximum flexibility and developer convenience. You can learn more about how to integrate directly with them using one of the following available guides: {% cards %} {% cards_item href="/docs/apis/realtime" title="Realtime API" %} Integrate with the Appwrite Realtime API {% /cards_item %} {% cards_item href="/docs/apis/rest" title="REST API" %} Integrate with the Appwrite REST API {% /cards_item %} {% cards_item href="/docs/apis/graphql" title="GraphQL API" %} Integrate with the Appwrite GraphQL API {% /cards_item %} {% /cards %} ### Utility classes {% #utility-classes %} Appwrite's SDKs provide useful utility classes to make your development experience easier. Use these classes and methods to reduce guess work and get better code suggestions in your IDE. #### IDs {% #ids %} Appwrite has utility classes to help you handle IDs. These generate the correct ID format to be passed to the Appwrite APIs. {% tabs %} {% tabsitem #client title="Client SDKs" %} {% multicode %} ```client-web import { Client, Account } from "appwrite"; // Generate a unique ID ID.unique() // Generate a custom ID ID.custom("my-custom-id") ``` ```client-flutter import 'package:appwrite/appwrite.dart'; // Generate a unique ID ID.unique() // Generate a custom ID ID.custom() ``` ```client-apple import Appwrite // Generate a unique ID ID.unique() // Generate a custom ID ID.custom("my-custom-id") ``` ```client-android-kotlin import io.appwrite.ID // Generate a unique ID ID.unique() // Generate a custom ID ID.custom("my-custom-id") ``` {% /multicode %} {% /tabsitem %} {% tabsitem #server title="Server SDKs" %} {% multicode %} ```server-nodejs const sdk = require('node-appwrite'); // Generate a unique ID sdk.ID.unique() // Generate a custom ID sdk.ID.custom("my-custom-id") ``` ```deno import * as sdk from "npm:node-appwrite"; // Generate a unique ID sdk.ID.unique() // Generate a custom ID sdk.ID.custom("my-custom-id") ``` ```php `. #### Enums {% #enums %} Appwrite has enumeration classes for predefined strings used different parameters used for the Appwrite APIs. These enums are available for authenticator type, name, OAuth provider, password hash types, browsers, authentication factors, index types, credit cards, image gravity, image format, relationship types, SMTP encryption, Function runtime, messaging provider type, compression algorithms, execution methods, and country flags. {% tabs %} {% tabsitem #client title="Client SDKs" %} You can discover the available enums in each SDK at the source. {% table %} *   {% width=48 %} * Platform * Enums *   {% width=80 %} --- * {% only_dark %}{% icon_image src="/images/platforms/dark/javascript.svg" alt="Javascript logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/javascript.svg" alt="Javascript logo" size="m" /%}{% /only_light %} * Web SDK `14.0.1` * [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web/tree/dev/src/enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/flutter.svg" alt="Flutter logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/flutter.svg" alt="Flutter logo" size="m" /%}{% /only_light %} * Flutter SDK `12.0.3` * [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter/tree/dev/lib/src/enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/react.svg" alt="React logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/react.svg" alt="React logo" size="m" /%}{% /only_light %} * React Native SDK `0.1.0` * [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native) * `beta` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/apple.svg" alt="Apple logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/apple.svg" alt="Apple logo" size="m" /%}{% /only_light %} * Apple SDK `5.0.0` * [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple/tree/dev/Sources/AppwriteEnums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/android.svg" alt="Android logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/android.svg" alt="Android logo" size="m" /%}{% /only_light %} * Android SDK `5.1.0` * [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android/tree/dev/library/src/main/java/io/appwrite/enums) * {% /table %} {% /tabsitem %} {% tabsitem #server title="Server SDKs" %} You can discover the available enums in each SDK at the source. {% table %} *   {% width=48 %} * Platform * Enums *   {% width=80 %} --- * {% only_dark %}{% icon_image src="/images/platforms/dark/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/nodejs.svg" alt="Node.js logo" size="m" /%}{% /only_light %} * Node.js SDK `14.1.0` * [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node/tree/dev/lib/enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/python.svg" alt="Python logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/python.svg" alt="Python logo" size="m" /%}{% /only_light %} * Python SDK `6.1.0` * [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python/tree/dev/appwrite/enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dart.svg" alt="Dart logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dart.svg" alt="Dart logo" size="m" /%}{% /only_light %} * Dart SDK `12.1.0` * [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart/blob/dev/lib/enums.dart) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/php.svg" alt="PHP logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/php.svg" alt="PHP logo" size="m" /%}{% /only_light %} * PHP SDK `12.1.0` * [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php/tree/dev/src/Appwrite/Enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/ruby.svg" alt="Ruby logo" size="m" /%}{% /only_light %} * Ruby SDK `12.1.0` * [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby/tree/dev/lib/appwrite/enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/dotnet.svg" alt=".NET logo" size="m" /%}{% /only_light %} * .NET SDK `0.10.1` * [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet/tree/dev/src/Appwrite/Enums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/go.svg" alt="Go logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/go.svg" alt="Go logo" size="m" /%}{% /only_light %} * Go SDK `0.2.0` * [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go) * `beta` --- * {% only_dark %}{% icon_image src="/images/platforms/dark/swift.svg" alt="Swift logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/swift.svg" alt="Swift logo" size="m" /%}{% /only_light %} * Swift SDK `6.1.0` * [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift/tree/dev/Sources/AppwriteEnums) * --- * {% only_dark %}{% icon_image src="/images/platforms/dark/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_dark %} {% only_light %}{% icon_image src="/images/platforms/kotlin.svg" alt="Kotlin logo" size="m" /%}{% /only_light %} * Kotlin SDK `6.1.0` * [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin/tree/dev/src/main/kotlin/io/appwrite/enums) * {% /table %} {% /tabsitem %} {% /tabs %} #### Queries and permissions {% #queries-and-permissions %} Appwrite has utility classes for queries and permissions. You can learn more about [query utility classes](/docs/products/databases/queries) and [permissions utility classes](/docs/advanced/platform/permissions) in their own pages. #### File I/O {% #file-io %} Depending on your platform, you will also need some helpers to interface with system I/O. Learn more about [storage input file classes](/docs/products/storage/upload-download#input-file). ### Community {% #community %} If you have created your own framework or any other technology specific integration and would like us to list it here please [contact us](/contact-us). If you would like to help us expand Appwrite's list of SDKs, you can contribute to Appwrite's [SDK Generator](https://github.com/appwrite/sdk-generator) project on GitHub and read our [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md). --- ## The Appwriter https://appwrite.io/docs/tooling/appwriter {% only_dark %} ![The Appwriter](/images/docs/keyboard/dark/appwriter.png) {% /only_dark %} {% only_light %} ![The Appwriter](/images/docs/keyboard/appwriter.png) {% /only_light %} The Appwriter is an exclusive mechanical keyboard custom-designed by the Appwrite team. It is optimized to improve developer productivity and is specially tuned to use with the Appwrite Console. The Appwriter uses icons from the Appwrite Console and Docs on specific keys, making memorizing keyboard shortcuts easier. For example, `G` then `D` is the shortcut for navigating to your project's databases, and the icon on the `D` key matches the icon for Appwrite Databases. ### What's in the box {% #whats-in-the-box %} - Appwriter keyboard - USB-C data/charging cable - 2-in-1 keycap and switch puller - Extra PC layout keycaps - Information card with a QR code The keyboard comes with Mac layout keys pre-installed for macOS which can also be swapped with the included PC keycaps. ### Specifications {% #specifications %} |Specification|Details| |----|----| |Battery|3000mAh rechargeable battery| |Design|75% compact design keyboard| |Keycaps|84 custom dye sublimated PBT keycaps| |Switches|Gateron G Pro yellow pre-lubed switches| |Connections|Tri-mode connectivity (2.4Ghz wireless / bluetooth / wired USB-C)| |Case|Noise dampening case-foam| |RGB backlight|16 Million colors & multiple lighting effects| |Switch support|Hot-swappable switches with 3 & 5 pin| |Rollover|N-key rollover| ### Quick start {% #quick-start %} There are currently three modes to connect the Appwriter to the computer: wired, wireless, and Bluetooth. There is a switch under the keyboard to toggle between B, T, or G, which switch to wired, wireless, or Bluetooth mode, respectively. #### Wired mode {% #wired-mode %} To use the wired mode, connect the USB-C data cable from the keyboard to the computer and push the Appwriter switch towards the T. When connecting the keyboard for the first time using wired mode, your computer will install all required drivers on a successful connection. The Appwriter's battery will charge while connected using the wired mode. Make sure to use a charger that doesn't exceed a voltage/current = DC5V = 1A (max). Using a charger that exceeds this voltage and current ratings could easily damage your keyboard. #### Wireless mode {% #wireless-mode %} Push the Appwriter switch towards the G if you want to use the wireless mode with the wireless receiver. After switching to wireless mode, long press the `FN + 4` keys until the `4` key light flashes quickly, and plug the receiver into a computer USB port. #### Bluetooth mode {% #bluetooth-mode %} To switch the keyboard to Bluetooth mode, push the switch to B. The keyboard can pair to three different devices. To pair a device, long press the `FN` and `1`, `2`, or `3` keys to enter pairing mode. Once the `1`, `2`, or `3` key flashes quickly, select the Appwriter on your device's Bluetooth devices list. To connect to a device, short press the `FN + 1/2/3` keys depending on the device you want to connect the keyboard with. ### General keyboard controls {% #general-keyboard-controls %} |Shortcut|Action| |---|---| | `FN + Esc` | Hold for 3 seconds to reset to factory defaults | | `FN + Win` | Disable/enable Win key | | `FN + S` | Set keyboard to Mac mode | | `FN + A` | Set keyboard to Windows mode | | `FN + Backspace` | System power/sleep | | `FN + Delete` | Change keyboard backlight effect | | `FN + Home` | Change keyboard backlight color | | `FN + Pg Up` | Toggle keyboard backlight | | `FN + Up Arrow` | Increase keyboard brightness | | `FN + Down Arrow` | Decrease keyboard brightness | | `FN + Left Arrow` | Slow down keyboard backlight effect | | `FN + Right Arrow` | Speed up keyboard backlight effect | #### Windows mode {% #windows-mode %} To switch to Windows mode, switch the preinstalled macOS keycap with the Windows keycap and use the `FN + A` shortcut to enable Windows mode; this will enable Windows-specific keyboard commands to work, like `Ctrl + C` or `Ctrl + A`. #### MacOS mode {% #macos-mode %} To use macOS-specific keyboard commands such as `Command + C` or `Command + A`, keep the preinstalled macOS keycaps in and use the `FN + S` shortcut to enable macOS mode. ### How to get the Appwriter {% #how-to-get-the-appwriter %} The Appwriter is available for preorder in the [Appwrite Store](https://appwrite.store/products/preorder-the-appwriter). You can also participate in our giveaways and win the Appwriter. Look out for events & giveaways on our social media: - [Discord](https://appwrite.io/discord) - [Github](https://github.com/appwrite/appwrite) - [X](https://x.com/appwrite) - [YouTube](https://www.youtube.com/c/appwrite) --- ## Assistant https://appwrite.io/docs/tooling/assistant The **Appwrite Assistant** is an AI-powered tool engineered to augment Appwrite-related tasks with technical precision. It operates on a foundation of training data sourced from the Appwrite documentation website, enabling it to furnish insights into Appwrite's features, APIs, and documentation. Additionally, it offers functionality for code snippet generation, sample project creation, and problem troubleshooting. {% only_dark %} ![Using Appwrite Assistant's AI features](/images/docs/assistant/dark/ask-ai.png) {% /only_dark %} {% only_light %} ![Using Appwrite Assistant's AI features](/images/docs/assistant/ask-ai.png) {% /only_light %} {% info title="Development" %} While the Appwrite Assistant remains under active development and is considered experimental, it undergoes incremental refinement. Its proficiency in comprehending user queries and delivering relevant responses improves with sustained usage. {% /info %} ### Getting started {% #getting-started %} To engage the Appwrite Assistant, access the Command Center within your Appwrite Console, and proceed to the `Ask the AI` tab in the navigation. ### Querying {% #querying %} The Appwrite Assistant accommodates queries related to Appwrite, encompassing topics such as: - How to add platform in the console? - How to integrate Appwrite in my Flutter project? - How can I create a user account? - How to store a file in a bucket? ### Optimizations {% #optimizations %} For optimal utilization of the Appwrite Assistant, consider the following recommendations: - **Precision**: Articulate queries with specificity. - **Natural Language**: The Assistant comprehends plain English, obviating the need for technical terminology. - **Query Refinement**: In cases of misinterpretation, rephrase queries. - **Manual Documentation Search**: In scenarios where the Assistant falls short, manually search the Appwrite documentation. ### Privacy {% #privacy %} When you use the Appwrite Assistant, your questions are sent to OpenAI to generate a response. OpenAI may collect and store your questions and responses for the purposes of improving their services. --- ## Command Center https://appwrite.io/docs/tooling/command-center The Appwrite **Command Center** is designed to improve the developer experience by enabling straightforward navigation and exploration of features, settings, and sections of the Appwrite Console. The Command Center is enhanced with [AI capabilities](/docs/tooling/assistant) and is the home of the Appwrite assistant. It allows you to execute tasks and access features within the Appwrite Console efficiently using keyboard shortcuts and advanced context-aware search. {% only_dark %} ![Command center](/images/docs/command-center/dark/command-center.png) {% /only_dark %} {% only_light %} ![Command center](/images/docs/command-center/command-center.png) {% /only_light %} ### Getting started {% #getting-started %} You can access the Command Center by pressing `⌘` + `K` on Mac or `Ctrl` + `K` on Windows and Linux devices or by clicking the search icon in the Console top navigation bar. A modal will appear, presenting a search input and a list of commands relevant to your current Console context. The Command Center emphasizes keyboard navigation. You can browse through commands using the `up` and `down` arrow keys and execute them with the `Enter` key. The search input lets you quickly filter and find specific commands or entities within the Console. Additionally, some commands have dedicated keyboard shortcuts that can be used for immediate execution without opening the Command Center. ### Navigation {% #navigation %} The Command Center includes a variety of navigation commands that are also useful for exploring the different options and features the Console offers. You can quickly access different sections like Databases, Auth, Security, and Functions screens using the Command Center. You will also find context-sensitive commands on each page that adapt based on your current location within the Console, providing relevant options and shortcuts. ### Resource creation {% #resource-creation %} The Command Center offers context-sensitive commands for creating entities like buckets, functions, database columns, etc. Specific commands trigger the opening of new panels, facilitating deeper interaction and task completion directly from the Command Center. ### AI Assistant {% #ai-assistant %} An integral part of the Command Center is the [Appwrite AI Assistant](/docs/tooling/assistant), trained on Appwrite's extensive documentation, content, and knowledge base. The Assistant can answer Appwrite-related queries with detailed explanations, step-by-step instructions, and relevant code snippets, enhancing your ability to utilize Appwrite quickly and efficiently. ### Keyboard optimization {% #keyboard-optimization %} Many developers favor keyboard interactions for efficiency and speed. The Command Center was designed with keyboard optimization in mind. It caters to the needs of keyboard-centric developers, enabling various tasks and efficient navigation across the Console without relying on a mouse or trackpad. You can use your `up` and `down` arrow keys to navigate between different commands and your `Enter` and `Escape` keys to enter and exit specific context screens. The Command Center also includes many built-in shortcuts that can be used from any console screen and allow greater productivity. --- ## Buckets https://appwrite.io/docs/tooling/command-line/buckets {% partial file="cli-disclaimer.md" /%} The Appwrite CLI allows you to configure and deploy buckets across projects. You can also configure your files using the CLI commands. ### Initialize bucket {% #initialize-bucket %} Create a new bucket using the following command: ```sh appwrite init buckets ``` ### Pull bucket {% #pull-bucket %} You can also pull your existing Appwrite buckets from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull buckets ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing buckets, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "buckets": [ { "$id": "", "$createdAt": "2024-06-21T16:20:25.516+00:00", "$updatedAt": "2024-06-21T16:21:16.855+00:00", "$permissions": [ "create(\"any\")", "read(\"any\")", "update(\"any\")", "delete(\"any\")" ], "fileSecurity": false, "name": "test", "enabled": true, "maximumFileSize": 5368709120, "allowedFileExtensions": [], "compression": "none", "encryption": true, "antivirus": true } ] } ``` ### Push bucket {% #push-bucket %} {% partial file="cli-push-command.md" /%} ```sh appwrite push buckets ``` ### Commands {% #commands %} The storage command allows you to manage your project's buckets and files. Appwrite storage CLI commands generally follow the following syntax: ```sh appwrite storage [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list-buckets [options]` * Get a list of all the storage buckets. You can use the query params to filter your results. --- * `create-bucket [options]` * Create a new storage bucket. --- * `get-bucket [options]` * Get a storage bucket by its unique ID. This endpoint response returns a JSON object with the storage bucket metadata. --- * `update-bucket [options]` * Update a storage bucket by its unique ID. --- * `delete-bucket [options]` * Delete a storage bucket by its unique ID. --- * `list-files [options]` * Get a list of all the user files. You can use the query params to filter your results. --- * `create-file [options]` * Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https://appwrite.io/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console. Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of '5MB'. The 'content-range' header values should always be in bytes. When the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in 'x-appwrite-id' header to allow the server to know that the partial upload is for the existing file and not for a new one. If you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally. --- * `get-file [options]` * Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata. --- * `update-file [options]` * Update a file by its unique ID. Only users with write permissions have access to update this resource. --- * `delete-file [options]` * Delete a file by its unique ID. Only users with write permissions have access to delete this resource. --- * `get-file-download [options]` * Get a file content by its unique ID. The endpoint response returns with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to the user downloads directory. --- * `get-file-preview [options]` * Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image. Preview is supported only for image files smaller than 10MB. --- * `get-file-view [options]` * Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header. --- {% /table %} --- ## Commands https://appwrite.io/docs/tooling/command-line/commands {% info title="CLI Version" %} All commands are compatible with the latest version of the CLI. We recommend running the [CLI on its latest version](/docs/tooling/command-line/installation#update-your-cli). {% /info %} Other than commands to create and push databases, tables, functions, messaging-topics, teams, and buckets, the Appwrite CLI can be used as a Server SDK as well. The Appwrite CLI has a command for every Server API endpoint. Commands generally follow the following syntax: ```sh appwrite [COMMAND] [OPTIONS] ``` ### Commands {% #commands %} Below is a list of the available commands in the Appwrite CLI. You can get more information on each command by running `appwrite [COMMAND] --help`. #### General commands {% #general-commands %} {% table %} * Command * Description --- * `client [options]` * The client command allows you to configure your CLI. --- * `locale` * The locale command allows you to customize your app based on your users' location. --- * `graphql` * The graphql command allows you to query and mutate any resource type on your Appwrite server. --- * `types [options] ` * The types command generates type definitions based on your Appwrite database schema. Learn more about [type generation](/docs/products/databases/type-generation). --- {% /table %} #### Account commands {% #account-commands %} {% table %} * Command * Description --- * `login [options]` * The login command allows you to authenticate into the CLI. This command expects the console account that you use to log into the Appwrite Console. --- * `logout` * The logout command allows you to log out of your Appwrite account. --- * `register` * Prints link to register an Appwrite account. --- * `whoami` * The whomai command gives information about the currently logged-in user. --- {% /table %} #### Deployment commands {% #deployment-commands %} {% table %} * Command * Description --- * `init [options]` * The init command provides a convenient wrapper for creating and initializing projects, functions, tables, buckets, teams, and messaging-topics in Appwrite. --- * `pull` * The pull command helps you pull your Appwrite project, functions, tables, buckets, teams, and messaging-topics. --- * `push` * The push command provides a convenient wrapper for pushing your functions, tables, buckets, teams, and topics. --- * `run` * The run command allows you to run projects locally to allow easy development and quick debugging. --- {% /table %} #### Project commands {% #appwrite-project-commands %} {% table %} * Command * Description --- * `account` * The account command allows you to authenticate and manage a user account. --- * `users` * The users command allows you to manage your project users. --- * `teams` * The teams command allows you to group users of your project and enable them to share read and write access to your project resources. --- * `databases` * The databases command allows you to create structured tables of rows and query and filter lists of rows. --- * `functions` * The functions command allows you to view, create, and manage your Appwrite Functions. --- * `messaging` * The messaging command allows you to send, create, edit, and delete messages. --- * `storage` * The storage command allows you to manage your project files. --- * `avatars` * The avatars command provides utilities to manage images, icons, and avatars. --- {% /table %} #### Command options {% #command-options %} {% table %} * Command * Description --- * `-v, --version` * Output the version number --- * `-V, --verbose` * Show complete error log --- * `-j, --json` * Output in JSON format --- * `-f,--force` * Flag to confirm all warnings --- * `-a,--all` * Flag to push all resources --- * `--id [id...]` * Flag to pass a list of ids for a given action --- * `--report` * Enable reporting in case of CLI errors --- * `-h, --help` * Display help for command --- {% /table %} ### Verbose {% #verbose %} In case of errors with any command, you can get more information about what went wrong using the `--verbose` flag ```sh appwrite users list --verbose ``` ### JSON {% #json %} By default, output is rendered in a tabular format. To format the output as JSON, use the `--json` flag. ```sh appwrite users list --json ``` ### Force {% #force %} By default, when pushing or pulling resources, the Appwrite CLI will ask you to confirm destructive operations. Use the `--force` flag to verify all questions. ```sh appwrite push tables --force ``` ### All {% #all %} By default, when pushing or pulling resources, Appwrite CLI would ask you to select specific resources. Use the `--all` flag to select all available options. ```sh appwrite pull functions --all ``` ### Error reporting {% #report %} If you encounter errors with any command, you can use the --report flag to generate a GitHub reporting link. ```sh appwrite login --report ``` ### View on console {% #console-flow %} Many resources support the option to view them in the console. Use the `--console` flag to get a direct link to the console, and add the optional `--open` flag to automatically open it in the default browser. ```sh appwrite databases get-row \ --database-id "" \ --table-id "" \ --row-id "" \ --console --open ``` ### Examples {% #examples %} #### Create user {% #create-user %} To create a new user in your project, you can use the create command. ```sh appwrite users create --user-id "unique()" \ --email hello@appwrite.io \ --password very_strong_password ``` #### List users {% #list-users %} To get a list of all your project users, you can use the list command. ```sh appwrite users list ``` #### List tables {% #list-tables %} To get a list of all your [tables](/docs/tooling/command-line/tables), you can use the `list-tables` command. ```sh appwrite databases list-tables --database-id "" ``` If you wish to parse the output from the CLI, you can request the CLI output in JSON format using the `--json` flag ```sh appwrite databases list-tables --database-id "" --json ``` #### Get table {% #get-table %} To get more information on a particular table, you can make use of the `get-table` command and pass in the table-id. ```sh appwrite databases get-table --database-id "" --table-id "" ``` #### Create row {% #create-row %} To create a new row in an existing table, use the `create-row` command. ```sh appwrite databases create-row \ --database-id "" --table-id "" \ --row-id 'unique()' --data '{ "Name": "Iron Man" }' \ --permissions 'read("any")' 'write("team:abc")' ``` --- ## Functions https://appwrite.io/docs/tooling/command-line/functions {% partial file="cli-disclaimer.md" /%} The CLI handles the creation, deployment, and execution of Appwrite Functions, as well as the configuration of the variables. You can also [develop your function locally](/docs/products/functions/develop-locally) using CLI commands. ### Initialize function {% #initialize-function %} Create a new function using the following command: ```sh appwrite init functions ``` ### Pull function {% #pull-function %} You can also pull your existing Appwrite Functions from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull functions ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing functions, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "functions": [ { "$id": "", "$createdAt": "2024-04-22T22:29:31.427+00:00", "$updatedAt": "2024-06-26T19:08:26.582+00:00", "execute": [ "any" ], "name": "userAuth", "enabled": true, "live": true, "logging": true, "runtime": "node-18.0", "deployment": "", "vars": [ { "$id": "eyJhbGciOiJIUzI1N", "$createdAt": "2024-04-22T22:51:51.745+00:00", "$updatedAt": "2024-04-23T00:13:10.886+00:00", "key": "", "value": "", "resourceType": "function", "resourceId": "eyJhbGciOiJIUzI1N" }, { "$id": "N1IzUIJiOicGbhJye", "$createdAt": "2024-04-22T23:32:12.901+00:00", "$updatedAt": "2024-04-22T23:32:12.901+00:00", "key": "", "value": "", "resourceType": "function", "resourceId": "N1IzUIJiOicGbhJye" }, { "$id": "OicGbhJyeN1IzUIJi", "$createdAt": "2024-04-22T23:32:12.910+00:00", "$updatedAt": "2024-04-22T23:32:12.910+00:00", "key": "", "value": "", "resourceType": "function", "resourceId": "OicGbhJyeN1IzUIJi" }, { "$id": "bhJyIJiON1IzUicGe", "$createdAt": "2024-04-22T23:32:12.912+00:00", "$updatedAt": "2024-04-22T23:32:12.912+00:00", "key": "", "value": "", "resourceType": "function", "resourceId": "bhJyIJiON1IzUicGe" } ], "events": [], "schedule": "", "timeout": 15, "entrypoint": "userAuth.js", "commands": "npm install", "version": "v3", "installationId": "a0e499a8", "providerRepositoryId": "7389", "providerBranch": "user-appwrite-funcs", "providerRootDirectory": "functions", "providerSilentMode": false, "path": "functions/49dbf3" } ] } ``` ### Push function {% #push-function %} {% partial file="cli-push-command.md" /%} ```sh appwrite push functions ``` ### Commands {% #commands %} The functions command lets you view, create, and manage your Appwrite Functions. Appwrite functions CLI commands generally follow the following syntax: ```sh appwrite functions [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list [options]` * Get a list of all the project's functions. You can use the query params to filter your results. --- * `create [options]` * Create a new function. You can pass a list of [permissions](https://appwrite.io/docs/permissions) to allow different project users or teams with access to execute the function using the client API. --- * `list-runtimes` * Get a list of all runtimes that are currently active on your instance. --- * `get [options]` * Get a function by its unique ID. --- * `update [options]` * Update function by its unique ID. --- * `delete [options]` * Delete a function by its unique ID. --- * `list-deployments [options]` * Get a list of all the project's code deployments. You can use the query params to filter your results. --- * `create-deployment [options]` * Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID. This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https://appwrite.io/docs/functions). Use the "command" param to set the entrypoint used to execute your code. --- * `get-deployment [options]` * Get a code deployment by its unique ID. --- * `update-deployment [options]` * Update the function code deployment ID using the unique function ID. Use this endpoint to switch the code deployment that should be executed by the execution endpoint. --- * `delete-deployment [options]` * Delete a code deployment by its unique ID. --- * `download-deployment [options]` * Get a Deployment's contents by its unique ID. This endpoint supports range requests for partial or streaming file download. --- * `list-executions [options]` * Get a list of all the current user function execution logs. You can use the query params to filter your results. --- * `create-execution [options]` * Trigger a function execution. The returned object will return you the current execution status. You can ping the 'Get Execution' endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously. --- * `get-execution [options]` * Get a function execution log by its unique ID. --- * `list-variables [options]` * Get a list of all variables of a specific function. --- * `create-variable [options]` * Create a new function environment variable. These variables can be accessed in the function at runtime as environment variables. --- * `get-variable [options]` * Get a variable by its unique ID. --- * `update-variable [options]` * Update a variable by its unique ID. --- * `delete-variable [options]` * Delete a variable by its unique ID. --- {% /table %} --- ## Installation https://appwrite.io/docs/tooling/command-line/installation The [Appwrite Command Line Interface (CLI)](https://github.com/appwrite/sdk-for-cli) is an application that allows you to interact with Appwrite to perform server-side tasks using your terminal. This includes creating and managing projects, managing resources (rows, files, users), creating and deploying Appwrite Functions, and other operations available through Appwrite's API. ### Getting started {% #getting-started %} The CLI is packaged both as an [npm module](https://www.npmjs.com/package/appwrite-cli) as well as a [standalone binary](https://github.com/appwrite/sdk-for-cli/releases/latest) for your operating system, making it completely dependency free, platform independent, and language agnostic. If you plan to use the CLI to initialize new Appwrite Functions, ensure that [Git is installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) on your machine. #### Install with npm {% #install-with-npm %} If you have npm set up, run the command below to install the CLI. ```sh npm install -g appwrite-cli ``` #### Install with script {% #install-with-script %} For a completely dependency-free installation, the CLI also ships with a convenient installation script for your operating system {% tabs %} {% tabsitem #macos title="macOS" %} Using [Homebrew](https://brew.sh/) ```sh brew install appwrite ``` or terminal ```sh curl -sL https://appwrite.io/cli/install.sh | bash ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} Using [Powershell](https://learn.microsoft.com/en-us/powershell/) ```sh iwr -useb https://appwrite.io/cli/install.ps1 | iex ``` or [Scoop](https://scoop.sh/) ```sh scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/scoop/appwrite.config.json ``` {% /tabsitem %} {% tabsitem #linux title="Linux" %} ```sh curl -sL https://appwrite.io/cli/install.sh | bash ``` {% /tabsitem %} {% /tabs %} ### Update your CLI {% #update-your-cli %} {% tabs %} {% tabsitem #npm title="npm" %} ```sh npm install -g appwrite-cli ``` {% /tabsitem %} {% tabsitem #macos title="macOS" %} Using [Homebrew](https://brew.sh/) ```sh brew install appwrite ``` or terminal ```sh curl -sL https://appwrite.io/cli/install.sh | bash ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} ```sh iwr -useb https://appwrite.io/cli/install.ps1 | iex ``` {% /tabsitem %} {% tabsitem #linux title="Linux" %} ```sh curl -sL https://appwrite.io/cli/install.sh | bash ``` {% /tabsitem %} {% tabsitem #scoop title="Scoop" %} ```sh scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/scoop/appwrite.config.json ``` {% /tabsitem %} {% /tabs %} #### Verify installation {% #verify-installation %} After the installation or the update is complete, you can verify the Appwrite CLI is available by checking its version number. ```sh appwrite -v ``` ### Login {% #login %} Before you can use the CLI, you need to login to your Appwrite account using ```sh appwrite login ``` Add the `--endpoint` flag if you're using a self-hosted instance of Appwrite. This flag requires you to add the URL string you're using for your self-hosted instance after the `--endpoint` flag. ```sh appwrite login --endpoint "" ``` You can log in to multiple accounts or change the **current** account by re-running the command. ### Initialization {% #initialization %} After you're logged in, the CLI needs to be initialized with your Appwrite project. You can initialize the CLI using: ```sh appwrite init project ``` This will create your `appwrite.config.json` file, where you will configure your various services like tables, functions, teams, topics, and buckets. ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1" } ``` You can run your first CLI command after logging in. Try fetching information about your Appwrite project. ```sh appwrite projects get --project-id "" ``` {% info title="Self-signed certificates" %} By default, requests to domains with self-signed SSL certificates (or no certificates) are disabled. If you trust the domain, you can bypass the certificate validation using ```sh appwrite client --self-signed true ``` {% /info %} #### Next steps {% #next-steps %} You can use the CLI to create and deploy tables, functions, teams, topics, and buckets. Deployment commands allow you to configure your Appwrite project programmatically and replicate functions and table schemas across Appwrite projects. [Learn more about deployment](/docs/tooling/command-line/tables) Besides utility commands, the CLI can be used to execute commands like a Server SDK. [Find a full list of commands](/docs/tooling/command-line/commands) You can choose to use the CLI in a headless and non-interactive mode without the need for config files or sessions. This is useful for CI or scripting use cases. [Learn more about CI mode](/docs/tooling/command-line/non-interactive) ### Help {% #help %} If you get stuck anywhere, you can always use the `help` command to get the usage examples. ```sh appwrite help ``` ### Configuration {% #configuration %} At any point, if you would like to change your server's endpoint, project ID, or self-signed certificate acceptance, use the `client` command. ```sh appwrite client --endpoint https://.cloud.appwrite.io/v1 appwrite client --key 23f24gwrhSDgefaY appwrite client --self-signed true appwrite client --reset // Resets your CLI configuration appwrite client --debug // Prints your current configuration ``` ### Uninstall {% #uninstall %} If you installed Appwrite CLI using NPM, you can use the following command to uninstall it. ```sh npm uninstall -g appwrite-cli ``` If you installed the Appwrite CLI with brew or the installation script for your operating system, use the following command to uninstall it. {% tabs %} {% tabsitem #macos title="macOS" %} Using [Homebrew](https://brew.sh/) ```sh brew uninstall appwrite ``` or terminal ```sh rm -f /usr/local/bin/appwrite | bash ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} Using [Powershell](https://learn.microsoft.com/en-us/powershell/) ```sh $APPWRITE_INSTALL_DIR = Join-Path -Path $env:LOCALAPPDATA -ChildPath "Appwrite"; Remove-Item -Force -Path $APPWRITE_INSTALL_DIR ``` or [Scoop](https://scoop.sh/) ```sh scoop uninstall appwrite ``` {% /tabsitem %} {% tabsitem #linux title="Linux" %} ```sh rm -f /usr/local/bin/appwrite | bash ``` {% /tabsitem %} {% /tabs %} You can also remove the configuration, cookies, and API Keys the Appwrite CLI stored. To remove those, run the following command. {% tabs %} {% tabsitem #macos title="macOS" %} ```sh rm -rf ~/.appwrite | bash ``` {% /tabsitem %} {% tabsitem #windows title="Windows" %} Using [Powershell](https://learn.microsoft.com/en-us/powershell/) ```sh $APPWRITE_CONFIG_DIR = Join-Path -Path $env:UserProfile -ChildPath ".appwrite"; Remove-Item -Recurse -Force -Path $APPWRITE_CONFIG_DIR ``` or [Scoop](https://scoop.sh/) ```sh appwrite client --reset ``` {% /tabsitem %} {% tabsitem #linux title="Linux" %} ```sh rm -rf ~/.appwrite | bash ``` {% /tabsitem %} {% /tabs %} --- ## Non-interactive https://appwrite.io/docs/tooling/command-line/non-interactive The Appwrite CLI can be used in a non-interactive and headless manner, without saving configuration or sessions. This is especially useful when you want to automate tasks on a continuous integration server. You can enable the non-interactive mode for the Appwrite CLI by setting the `project ID`, `endpoint`, and `API Key`: ```sh appwrite client \ --endpoint https://.cloud.appwrite.io/v1 \ --project-id \ --key YOUR_API_KEY ``` When you set the global configuration parameters using the `appwrite client` command, they take precedence over the local configuration parameters in your `appwrite.config.json` thereby switching the CLI to non-interactive mode. In this mode, the CLI can only interact with one project at a time. ### API Keys {% #api-keys %} In non-interactive mode, the CLI uses an API key to authenticate. Your API key must have sufficient permissions to execute the commands you plan to use. [Learn more about API Keys](/docs/advanced/platform/api-keys). ### Deployment {% #deployment %} Appwrite's `push` commands can also be executed in a non-interactive mode. This applies to the following resources: functions, tables, buckets, teams, and messaging topics. You can push a resource non-interactively by using the `--force` option to skip all warnings and specify which resources you want to deploy. To push all available resources: ```sh appwrite push all --all --force ``` To push a single function by ID: ```sh appwrite push functions --function-id [FUNCTION ID] --force ``` Push all functions: ```sh appwrite push functions --all --force ``` You can push databases, tables, teams, and buckets non-interactively in a similar way by using the `--all` and `--force` option. Push all databases and tables: ```sh appwrite push tables --all --force ``` Push all teams: ```sh appwrite push teams --all --force ``` Push all buckets: ```sh appwrite push buckets --all --force ``` ### CI/CD integrations {% #ci-cd-integrations %} Use providers like Github actions to create continuous integrations and continuous delivery or deployment (CI/CD). #### Github You can use [Github actions](https://github.com/appwrite/setup-for-appwrite?tab=readme-ov-file#introduction) to automate your Appwrite CLI commands, allowing you to use them even in non-interactive mode. --- ## Sites https://appwrite.io/docs/tooling/command-line/sites {% partial file="cli-disclaimer.md" /%} The CLI handles the creation, deployment, and execution of Appwrite Sites, as well as the configuration of the variables. ### Initialize site {% #initialize-site %} Create a new site using the following command: ```sh appwrite init sites ``` ### Pull site {% #pull-site %} You can also pull your existing Appwrite Sites from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull sites ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing sites, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "sites": [ { "$id": "", "name": "Documentation template", "enabled": true, "logging": true, "framework": "astro", "timeout": 30, "installCommand": "npm install", "buildCommand": "npm run build", "outputDirectory": "./dist", "specification": "s-1vcpu-512mb", "buildRuntime": "node-22", "adapter": "ssr", "fallbackFile": "", "path": "sites/documentation-template" } ] } ``` ### Push site {% #push-site %} {% partial file="cli-push-command.md" /%} ```sh appwrite push sites ``` ### Commands {% #commands %} The functions command lets you view, create, and manage your Appwrite Sites. Appwrite Sites CLI commands generally follow the following syntax: ```sh appwrite sites [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list [options]` * Get a list of all the project's sites. You can use the query params to filter your results. --- * `create [options]` * Create a new site. --- * `list-frameworks [options]` * Get a list of all frameworks that are currently available on the server instance. --- * `list-specifications [options]` * List allowed site specifications for this instance. --- * `list-templates [options]` * List available site templates. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method. --- * `get-template [options]` * Get a site template using ID. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method. --- * `list-usage [options]` * Get usage metrics and statistics for all sites in the project. View statistics including total deployments, builds, logs, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days. --- * `get [options]` * Get a site by its unique ID. --- * `update [options]` * Update site by its unique ID. --- * `delete [options]` * Delete a site by its unique ID. --- * `update-site-deployment [options]` * Update the site active deployment. Use this endpoint to switch the code deployment that should be used when visitor opens your site. --- * `list-deployments [options]` * Get a list of all the site's code deployments. You can use the query params to filter your results. --- * `create-deployment [options]` * Create a new site code deployment. Use this endpoint to upload a new version of your site code. To activate your newly uploaded code, you'll need to update the function's deployment to use your new deployment ID. --- * `create-duplicate-deployment [options]` * Create a new build for an existing site deployment. This endpoint allows you to rebuild a deployment with the updated site configuration, including its commands and output directory if they have been modified. The build process will be queued and executed asynchronously. The original deployment's code will be preserved and used for the new build. --- * `create-template-deployment [options]` * Create a deployment based on a template. Use this endpoint with combination of [listTemplates](https://appwrite.io/docs/server/sites#listTemplates) to find the template details. --- * `create-vcs-deployment [options]` * Create a deployment when a site is connected to VCS. This endpoint lets you create deployment from a branch, commit, or a tag. --- * `get-deployment [options]` * Get a site deployment by its unique ID. --- * `delete-deployment [options]` * Delete a site deployment by its unique ID. --- * `get-deployment-download [options]` * Get a site deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory. --- * `update-deployment-status [options]` * Cancel an ongoing site deployment build. If the build is already in progress, it will be stopped and marked as canceled. If the build hasn't started yet, it will be marked as canceled without executing. You cannot cancel builds that have already completed (status 'ready') or failed. The response includes the final build status and details. --- * `list-logs [options]` * Get a list of all site logs. You can use the query params to filter your results. --- * `get-log [options]` * Get a site request log by its unique ID. --- * `delete-log [options]` * Delete a site log by its unique ID. --- * `get-usage [options]` * Get usage metrics and statistics for a for a specific site. View statistics including total deployments, builds, executions, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days. --- * `list-variables [options]` * Get a list of all variables of a specific site. --- * `create-variable [options]` * Create a new site variable. These variables can be accessed during build and runtime (server-side rendering) as environment variables. --- * `get-variable [options]` * Get a variable by its unique ID. --- * `update-variable [options]` * Update variable by its unique ID. --- * `delete-variable [options]` * Delete a variable by its unique ID. --- {% /table %} --- ## Tables https://appwrite.io/docs/tooling/command-line/tables {% partial file="cli-disclaimer.md" /%} Create and manage your tables using the CLI commands. The Appwrite CLI also helps you push your project's databases and tables schema from one project to another. ### Initialize table {% #initialize-table %} Create a new table using the following command: ```sh appwrite init tables ``` ### Pull table {% #pull-table %} You can also pull your existing Appwrite tables and databases from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull tables ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing tables, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "tablesDB": [ { "$id": "", "name": "songs", "$createdAt": "2023-07-01T18:35:27.802+00:00", "$updatedAt": "2023-08-01T21:41:41.663+00:00", "enabled": true } ], "tables": [ { "$id": "", "$permissions": [ "create(\"any\")", "read(\"any\")", "update(\"any\")", "delete(\"any\")" ], "databaseId": "", "name": "music", "enabled": true, "rowSecurity": false, "columns": [ { "key": "userID", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 100, "default": null }, { "key": "name", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 100, "default": null }, { "key": "cloudinaryId", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 100, "default": null }, { "key": "user", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 100, "default": null }, { "key": "audio", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 200, "default": null }, { "key": "genre", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 500, "default": null }, { "key": "artist", "type": "string", "status": "available", "error": "", "required": false, "array": false, "size": 500, "default": null } ], "indexes": [] } ] } ``` ### Push table {% #push-table %} {% partial file="cli-push-command.md" /%} ```sh appwrite push tables ``` ### Commands {% #commands %} The databases command allows you to create structured tables of rows, queries, and filter lists of rows. Appwrite database CLI commands generally follow the following syntax: ```sh appwrite databases [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list-tables [options]` * Get a list of all tables that belong to the provided databaseId. You can use the search parameter to filter your results. --- * `create-table [options]` * Create a new Table. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateTable) API or directly from your database console. --- * `get-table [options]` * Get a table by its unique ID. This endpoint response returns a JSON object with the table metadata. --- * `update-table [options]` * Update a table by its unique ID. --- * `delete-table [options]` * Delete a table by its unique ID. Only users with write permissions have access to delete this resource. --- * `list-columns [options]` * List columns in the table. --- * `create-boolean-column [options]` * Create a boolean column. --- * `update-boolean-column [options]` * Update a boolean column. Changing the 'default' value will not update already existing rows. --- * `create-datetime-column [options]` * Create a date time column according to the ISO 8601 standard. --- * `update-datetime-column [options]` * Update a date time column. Changing the 'default' value will not update already existing rows. --- * `create-email-column [options]` * Create an email column. --- * `update-email-column [options]` * Update an email column. Changing the 'default' value will not update already existing rows. --- * `create-enum-column [options]` * Create an enumeration column. The 'elements' param acts as a white-list of accepted values for this column. --- * `update-enum-column [options]` * Update an enum column. Changing the 'default' value will not update already existing rows. --- * `create-float-column [options]` * Create a float column. Optionally, minimum and maximum values can be provided. --- * `update-float-column [options]` * Update a float column. Changing the 'default' value will not update already existing rows. --- * `create-integer-column [options]` * Create an integer column. Optionally, minimum and maximum values can be provided. --- * `update-integer-column [options]` * Update an integer column. Changing the 'default' value will not update already existing rows. --- * `create-ip-column [options]` * Create IP address column. --- * `update-ip-column [options]` * Update an ip column. Changing the 'default' value will not update already existing rows. --- * `create-relationship-column [options]` * Create relationship column. [Learn more about relationship columns](https://appwrite.io/docs/databases-relationships#relationship-columns). --- * `create-string-column [options]` * Create a string column. --- * `update-string-column [options]` * Update a string column. Changing the 'default' value will not update already existing rows. --- * `create-url-column [options]` * Create a URL column. --- * `update-url-column [options]` * Update an url column. Changing the 'default' value will not update already existing rows. --- * `get-column [options]` * Get column by ID. --- * `delete-column [options]` * Deletes an column. --- * `update-relationship-column [options]` * Update relationship column. [Learn more about relationship columns](https://appwrite.io/docs/databases-relationships#relationship-columns). --- * `list-indexes [options]` * List indexes in the table. --- * `create-index [options]` * Creates an index on the columns listed. Your index should include all the columns you will query in a single request. Columns can be 'key', 'fulltext', and 'unique'. --- * `get-index [options]` * Get index by ID. --- * `delete-index [options]` * Delete an index. --- * `list-table-logs [options]` * Get the table activity logs list by its unique ID. --- {% /table %} --- ## Teams https://appwrite.io/docs/tooling/command-line/teams {% partial file="cli-disclaimer.md" /%} The Appwrite CLI can create teams to organize users. Teams can be used to configure [permissions](https://appwrite.io/docs/products/auth/teams#permissions) for a group of users. ### Initialize team {% #initialize-team %} Create a new team using the following command: ```sh appwrite init teams ``` ### Pull team {% #pull-team %} You can also pull your existing Appwrite teams from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull teams ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing teams, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "teams": [ { "$id": "eyJhbGciOiJIUzI1N", "name": "hat" }, { "$id": "N1IzUIJiOicGbhJye", "name": "sun" }, { "$id": "OicGbhJyeN1IzUIJi", "name": "emit" },{ "$id": "bhJyIJiON1IzUicGe", "name": "kue" } ] } ``` ### Push team {% #push-teams %} {% partial file="cli-push-command.md" /%} ```sh appwrite push teams ``` ### Commands {% #commands %} The team's command allows you to group users of your project and enable them to share read and write access to your project resources. Appwrite team CLI commands generally follow the following syntax: ```sh appwrite teams [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list [options]` * Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results. --- * `create [options]` * Create a new team. The user who creates the team will automatically be assigned as the owner of the team. Only the users with the owner role can invite new members, add new owners, and delete or update the team. --- * `get [options]` * Get a team by its ID. All team members have read access to this resource. --- * `update-name [options]` * Update the team's name by its unique ID. --- * `delete [options]` * Delete a team using its ID. Only team members with the owner role can delete the team. --- * `list-logs [options]` * Get the team activity logs list by its unique ID. --- * `list-memberships [options]` * Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. --- * `create-membership [options]` * Invite a new member to join your team. Provide an ID for existing users or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team. You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters. Use the 'url' parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https://appwrite.io/docs/references/cloud/client-web/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md). Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console. --- * `get-membership [options]` * Get a team member by using the membership unique ID. All team members have read access to this resource. --- * `update-membership [options]` * Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). --- * `delete-membership [options]` * This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if it is not accepted. --- * `update-membership-status [options]` * Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user. If the request is successful, a session for the user is automatically created. --- * `get-prefs [options]` * Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, we prefer storing them in [user preferences](https://appwrite.io/docs/references/cloud/client-web/account#getPrefs). --- * `update-prefs [options]` * Update the team's preferences by its unique ID. The object you pass is stored as is and replaces any previous value. The maximum allowed prefs size is 64kB and throws an error if exceeded. --- {% /table %} --- ## Topics https://appwrite.io/docs/tooling/command-line/topics {% partial file="cli-disclaimer.md" /%} The Appwrite CLI can create, update, delete, and get topics, as well as configure the provider and the subscribers. ### Initialize topic {% #initialize-topic %} Create a new topic using the following command: ``` appwrite init topics ``` ### Pull topics {% #pull-topics %} You can also pull your existing Appwrite topics from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file. ```sh appwrite pull topics ``` ### appwrite.config.json {% #appwritejson %} After [initializing](/docs/tooling/command-line/installation#initialization) your Appwrite project and pulling your existing topics, your `appwrite.config.json` file should look similar to the following: ```json { "projectId": "", "endpoint": "https://.cloud.appwrite.io/v1", "topics": [ { "$id": "N1IzUIJiOicGbhJye", "$createdAt": "2024-07-01T14:40:43.381+00:00", "$updatedAt": "2024-07-01T14:40:43.381+00:00", "name": "Anime", "emailTotal": 3, "smsTotal": 0, "pushTotal": 0, "subscribe": [ "users" ] }, { "$id": "eyJhbGciOiJIUzI1N", "$createdAt": "2024-07-01T14:41:19.029+00:00", "$updatedAt": "2024-07-01T14:41:28.751+00:00", "name": "Music", "emailTotal": 2, "smsTotal": 0, "pushTotal": 0, "subscribe": [ "users", "any" ] } ] } ``` ### Push topics {% #push-topics %} {% partial file="cli-push-command.md" /%} ```sh appwrite push topics ``` ### Commands {% #commands %} The messaging command allows you to send, get, update, and delete push notifications, SMS text messages, and emails. You can create a new provider like Mailgun and SendGrid and create, update, get, and delete topics and subscribers. Appwrite messaging CLI commands generally follow the following syntax: ```sh appwrite messaging [COMMAND] [OPTIONS] ``` {% table %} * Command * Description --- * `list-messages [options]` * Get a list of all messages from the current Appwrite project. --- * `create-email [options]` * Create a new email message. --- * `update-email [options]` * Update an email message by its unique ID. --- * `create-push [options]` * Create a new push notification. --- * `update-push [options]` * Update a push notification by its unique ID. --- * `create-sms [options]` * Create a new SMS message. --- * `updateSms [options]` * Update an email message by its unique ID. --- * `get-message [options]` * Get a message by its unique ID. --- * `delete [options]` * Delete a message. If the message is not a draft or scheduled, but has been sent, this will not recall the message. --- * `list-message-logs [options]` * Get the message activity logs listed by its unique ID. --- * `list-targets [options]` * Get a list of the targets associated with a message. --- * `list-providers [options]` * Get a list of all providers from the current Appwrite project. --- * `create-apns-provider [options]` * Create a new Apple Push Notification service provider. --- * `update-apns-provider [options]` * Update an Apple Push Notification service provider by its unique ID. --- * `create-fcm-provider [options]` * Create a new Firebase Cloud Messaging provider. --- * `update-fcm-provider [options]` * Update a Firebase Cloud Messaging provider by its unique ID. --- * `create-mailgun-provider [options]` * Create a new Mailgun provider. --- * `update-mailgun-provider [options]` * Update a Mailgun provider by its unique ID. --- * `create-msg91-provider [options]` * Create a new MSG91 provider. --- * `update-msg91-provider [options]` * Update a MSG91 provider by its unique ID. --- * `create-sendgrid-provider [options]` * Create a new Sendgrid provider. --- * `update-sendgrid-provider [options]` * Update a Sendgrid provider by its unique ID. --- * `create-smtp-provider [options]` * Create a new SMTP provider. --- * `update-smtp-provider [options]` * Update a SMTP provider by its unique ID. --- * `create-telesign-provider [options]` * Create a new Telesign provider. --- * `update-telesign-provider [options]` * Update a Telesign provider by its unique ID. --- * `create-textmagic-provider [options]` * Create a new Textmagic provider. --- * `update-textmagic-provider [options]` * Update a Textmagic provider by its unique ID. --- * `create-twilio-provider [options]` * Create a new Twilio provider. --- * `update-twilio-provider [options]` * Update a Twilio provider by its unique ID. --- * `create-vonage-provider [options]` * Create a new Vonage provider. --- * `update-vonage-provider [options]` * Update a Vonage provider by its unique ID. --- * `get-provider [options]` * Get a provider by its unique ID. --- * `delete-provider [options]` * Delete a provider by its unique ID. --- * `list-provider-logs [options]` * Get the provider activity logs listed by its unique ID. --- * `list-subscriber-logs [options]` * Get the subscriber activity logs listed by its unique ID. --- * `list-topics [options]` * Get a list of all topics from the current Appwrite project. --- * `create-topic [options]` * Create a new topic. --- * `get-topic [options]` * Get a topic by its unique ID. --- * `update-topic [options]` * Update a topic by its unique ID. --- * `delete-topic [options]` * Delete a topic by its unique ID. --- * `list-topic-logs [options]` * Get the topic activity logs listed by its unique ID. --- * `list-subscribers [options]` * Get a list of all subscribers from the current Appwrite project. --- * `create-subscriber [options]` * Create a new subscriber. --- * `get-subscriber [options]` * Get a subscriber by its unique ID. --- * `delete-subscriber [options]` * Delete a subscriber by its unique ID. --- {% /table %} --- ## Model Context Protocol https://appwrite.io/docs/tooling/mcp Appwrite offers [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers that allow LLMs to directly interact with Appwrite's API and docs. Using MCP servers, you can use applications such as Claude Desktop, Cursor, Windsurf Editor, etc. to operate on your Appwrite project as well as gain context about the latest updates to Appwrite's SDKs, APIs, and CLI. ### What is MCP? The Model Context Protocol (MCP) is an open standard that enables Large Language Models (LLMs) and AI code-generation tools to interact with APIs and documentation in a structured manner. MCP servers provide a bridge between LLMs and external services, allowing them to perform actions such as querying databases, managing users, and accessing files. The key benefits of using MCP servers include: - **Enhanced capabilities**: LLMs can perform complex tasks by interacting with APIs, going beyond simple text generation. - **Improved context**: By accessing up-to-date documentation and API definitions, LLMs can provide more accurate and relevant responses. - **Seamless integration**: MCP servers can be easily integrated with popular AI tools and code editors, enhancing their functionality. ### Available MCP servers Appwrite currently offers the following MCP servers: {% cards %} {% cards_item href="/docs/tooling/mcp/api" title="MCP server for Appwrite API" icon="icon-globe-alt"%} {% /cards_item %} {% cards_item href="/docs/tooling/mcp/docs" title="MCP server for Appwrite docs" icon="icon-document-text" %} {% /cards_item %} {% /cards %} #### Why use Appwrite's MCP servers? Some **popular use cases** for Appwrite's MCP servers include: - **Code generation**: Automatically generate code snippets or entire files based on user input and context. - **Documentation lookup**: Quickly find relevant documentation for specific API endpoints or SDK features. - **Project management**: Create, update, or delete resources in your Appwrite project using natural language commands. - **Debugging assistance**: Get help with debugging issues by providing context about your project and recent changes. - **Learning and exploration**: Explore Appwrite's features and capabilities through interactive conversations with LLMs. --- ## Appwrite MCP and Google Antigravity https://appwrite.io/docs/tooling/mcp/antigravity Learn how you can add the Appwrite MCP servers to Agent Manager in Google Antigravity to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} To add the Appwrite MCP server, open Antigravity and go to the drop-down (...) menu in the Agent window . From there, navigate to Manage MCP Servers in the MCP Store, and then click View raw config in the main panel to add your custom MCP server. {% tabs %} {% tabsitem #api-only title="API server" %} Update the `mcp_config.json` file to include the API server: ```json { "mcpServers": { "appwrite-api": { "command": "uvx", "args": [ "mcp-server-appwrite", "--users" ], "env": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} Update the `mcp_config.json` file to include the docs server: ```json { "mcpServers": { "appwrite-docs": { "command": "npx", "args": [ "mcp-remote", "https://mcp-for-docs.appwrite.io" ] } } } ``` {% /tabsitem %} {% /tabs %} Head back to the Managed MCP Server page and click refresh. {% /section %} {% section #step-2 step=2 title="Test the integration" %} Open **Agent Manager** in Antigravity to test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![Search for portfolio site in Appwrite project](/images/docs/mcp/antigravity/agent-chat.png) {% /section %} --- ## MCP server for Appwrite API https://appwrite.io/docs/tooling/mcp/api The MCP server for Appwrite API allows LLMs and code-generation tools to interact with the Appwrite platform and perform various operations on your Appwrite resources, such as creating users, managing databases, and more, using natural language commands. Here are some of the key benefits of using the MCP server: - **Direct API interaction**: Enables LLMs to perform actions directly on your Appwrite project - **Real-time data access**: Allows LLMs to fetch and manipulate live data from your Appwrite instance - **Simplified workflows**: Facilitates complex operations through simple natural language prompts - **Customizable tools**: Offers a range of tools for different Appwrite services, which can be enabled as needed ### Pre-requisites {% #pre-requisites %} #### Appwrite API key Before launching the MCP server, you must [set up an Appwrite project](https://cloud.appwrite.io) and create an **API key** with the necessary scopes enabled. {% only_light %} ![Appwrite API key](/images/docs/mcp/appwrite/appwrite-api-secret.png) {% /only_light %} {% only_dark %} ![Appwrite API key](/images/docs/mcp/appwrite/dark/appwrite-api-secret.png) {% /only_dark %} Ensure you save the **API key** along with the **project ID**, **region** and **endpoint URL** from the Settings page of your project as you'll need them later. #### Install uv Install [uv](https://docs.astral.sh/uv/getting-started/installation/) on your system with: {% tabs %} {% tabsitem #uv-linux-macos title="Linux and MacOS" %} ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` {% /tabsitem %} {% tabsitem #uv-windows title="Windows" %} ```powershell powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` {% /tabsitem %} {% /tabs %} You can verify the installation by running the following command in your terminal: ```bash uv ``` ### Installation {% #installation %} {% partial file="mcp-add-ides-tools.md" /%} #### Command-line arguments Database tools are enabled by default. In addition you can pass arguments to `uvx mcp-server-appwrite [args]` to enable other MCP tools for various Appwrite APIs. | Argument | Description | | --- | --- | | `--tables-db` | Enables the TablesDB API | | `--users` | Enables the Users API | | `--teams` | Enables the Teams API | | `--storage` | Enables the Storage API | | `--functions` | Enables the Functions API | | `--messaging` | Enables the Messaging API | | `--locale` | Enables the Locale API | | `--avatars` | Enables the Avatars API | | `--databases` | Enables the legacy Databases API | | `--all` | Enables all Appwrite APIs | {% info title="Enable only necessary MCP tools" %} When an MCP tool is enabled, the tool's definition is passed to the LLM, using up tokens from the model's available context window. As a result, the effective context window is reduced. Some IDEs may return errors if too many tools are enabled for the same reason. The default Appwrite MCP server ships with only the Databases tools (our most commonly used API) enabled to stay within these limits. Additional tools can be enabled using the flags above. {% /info %} ### Usage {% #usage %} Once configured, your AI assistant will have access to your Appwrite project. You can ask questions like: #### Example 1: List users Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` List users in my Appwrite project ``` ![List users in Appwrite project](/images/docs/mcp/claude-desktop/claude-list-users.png) #### Example 2: Search a site Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` Get the details of my portfolio site from Appwrite ``` ![Search for portfolio site in Appwrite project](/images/docs/mcp/vscode/copilot-chat.png) #### Example 3: Create a user Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` Add a user john.doe@example.com to the Appwrite project ``` ![Create user in Appwrite project](/images/docs/mcp/cursor/cursor-create-user.png) --- ## Appwrite MCP and Claude Code https://appwrite.io/docs/tooling/mcp/claude-code Learn how you can add the Appwrite MCP servers to Claude Code to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} Run the following commands in your terminal to add the MCP servers: {% tabs %} {% tabsitem #api-only title="API server" %} ```bash claude mcp add-json appwrite-api '{"command":"uvx","args":["mcp-server-appwrite","--users"],"env":{"APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1"}}' ``` {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} ```bash claude mcp add appwrite-docs https://mcp-for-docs.appwrite.io -t http ``` {% /tabsitem %} {% /tabs %} {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Verify MCP tools" %} Run the following command in your terminal (where Claude Code is running). ```bash /mcp ``` You should see the added MCP servers listed there. ![Verify MCP tools](/images/docs/mcp/claude-code/verify-mcp-tools.png) {% /section %} {% section #step-3 step=3 title="Test the integration" %} Try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![Implement file uploads with Appwrite Storage](/images/docs/mcp/claude-code/implement-file-uploads.png) {% /section %} --- ## Appwrite MCP and Claude Desktop https://appwrite.io/docs/tooling/mcp/claude-desktop Learn how you can add the Appwrite MCP servers to Claude Desktop to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} In the Claude Desktop app, open the app's **Settings** page (press `CTRL + ,` on Windows or `CMD + ,` on MacOS) and head to the **Developer** tab. ![Claude Settings](/images/docs/mcp/claude-desktop/claude-settings.png) Clicking on the **Edit Config** button will take you to the `claude_desktop_config.json` file. In case the file is missing, please visit the [Model Context Protocol](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server) docs. Choose which MCP server you want to configure: {% tabs %} {% tabsitem #api-only title="API server" %} Add the API server to your configuration: ```json { "mcpServers": { "appwrite-api": { "command": "uvx", "args": [ "mcp-server-appwrite", "--users" ], "env": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} Add the docs server to your configuration: ```json { "mcpServers": { "appwrite-docs": { "command": "npx", "args": [ "mcp-remote", "https://mcp-for-docs.appwrite.io" ] } } } ``` **Why do we use the `mcp-remote` package?** Unlike other IDEs, Claude Desktop only supports local (stdio) MCP servers and not remote servers. The `mcp-remote` package acts as a proxy to connect to the docs MCP server. {% /tabsitem %} {% /tabs %} {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Verify MCP tools" %} Restart the Claude Desktop app, click on the MCP tools button (at the bottom right section of the prompt input) and click on it to view available Appwrite MCP tools. ![Appwrite MCP tools](/images/docs/mcp/claude-desktop/claude-mcp-tools.png) {% info title="uvx ENOENT error" %} In case you see a `uvx ENOENT` error, ensure that you either add `uvx` to the `PATH` environment variable on your system or use the full path to your `uvx` installation in the config file. {% /info %} {% /section %} {% section #step-3 step=3 title="Test the integration" %} Try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![List users in Appwrite project](/images/docs/mcp/claude-desktop/claude-list-users.png) {% /section %} --- ## Appwrite MCP and Cursor https://appwrite.io/docs/tooling/mcp/cursor Learn how you can add the Appwrite MCP servers to Cursor to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} Open the **Cursor Settings** page, head to the **MCP** tab, and click on the **Add new global MCP server** button. This will open an `mcp.json` file in your editor. Choose which MCP server you want to configure: {% tabs %} {% tabsitem #api-only title="API server" %} Update the `mcp.json` file to include the API server: ```json { "mcpServers": { "appwrite-api": { "command": "uvx", "args": [ "mcp-server-appwrite", "--users" ], "env": { "APPWRITE_API_KEY": "your-api-key", "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} Update the `mcp.json` file to include the docs server: ```json { "mcpServers": { "appwrite-docs": { "command": "npx", "args": [ "mcp-remote", "https://mcp-for-docs.appwrite.io" ] } } } ``` {% /tabsitem %} {% /tabs %} You can also **directly add the MCP servers to Cursor** using the following links: {% only_light %} {% cards %} {% cards_item href="https://apwr.dev/api-mcp-cursor?ref=docs" title="API server" image="/images/docs/mcp/logos/cursor-ai.svg" %} {% /cards_item %} {% cards_item href="https://apwr.dev/docs-mcp-cursor?ref=docs" title="Docs server" image="/images/docs/mcp/logos/cursor-ai.svg" %} {% /cards_item %} {% /cards %} {% /only_light %} {% only_dark %} {% cards %} {% cards_item href="https://apwr.dev/api-mcp-cursor?ref=docs" title="API server" image="/images/docs/mcp/logos/dark/cursor-ai.svg" %} {% /cards_item %} {% cards_item href="https://apwr.dev/docs-mcp-cursor?ref=docs" title="Docs server" image="/images/docs/mcp/logos/dark/cursor-ai.svg" %} {% /cards_item %} {% /cards %} {% /only_dark %} Once you save the details, Cursor will connect with the MCP server(s) and load all available tools. You may need to restart Cursor if it is unable to start the MCP server. {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Test the integration" %} Open Cursor Agent and test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![Create user in Appwrite project](/images/docs/mcp/cursor/cursor-create-user.png) {% /section %} --- ## MCP server for Appwrite docs https://appwrite.io/docs/tooling/mcp/docs The MCP server for Appwrite documentation allows LLMs and code-generation tools to interact with comprehensive Appwrite documentation, enabling intelligent code generation for Appwrite's APIs and SDKs, troubleshooting assistance, and implementation guidance using natural language commands. Here are some of the key benefits of using the MCP server: - **Complete documentation access**: Provides AI assistants with access to all Appwrite documentation - **Real-time context**: Ensures AI responses are based on the latest documentation - **Intelligent search**: Enables semantic search across documentation content - **Code examples**: Includes access to code samples and implementation guides - **Best practices**: Shares recommended patterns and practices from official documentation ### Pre-requisites {% #pre-requisites %} Install [Node.js](https://nodejs.org/en/download) and npm on your system. You can verify the installation by running the following commands in your terminal: ```bash node -v npm -v ``` ### Installation {% #installation %} {% partial file="mcp-add-ides-tools.md" /%} ### Usage {% #usage %} Once configured, your AI assistant will have access to Appwrite documentation context. You can ask questions like: #### Example 1: Code generation Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` Show me how to set up real-time subscriptions that trigger on creation of a user ``` ![Code generation example](/images/docs/mcp/mcp-for-docs/code-generation.png) #### Example 2: Troubleshooting Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` I'm getting a 401 error when trying to delete a user. What could be wrong? ``` ![Troubleshooting example](/images/docs/mcp/mcp-for-docs/troubleshooting.png) #### Example 3: Best practices Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` What are some of the best security practices for Appwrite Auth in a web app with SSR? ``` ![Best practices example](/images/docs/mcp/mcp-for-docs/best-practices.png) #### Example 4: API reference Run the following prompt in your preferred code editor/LLM after enabling the MCP server: ``` I want an example of how I can list all users in a Python app ``` ![API reference example](/images/docs/mcp/mcp-for-docs/api-reference.png) --- ## Appwrite MCP and OpenCode https://appwrite.io/docs/tooling/mcp/opencode Learn how you can add the Appwrite MCP servers to OpenCode to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} Use the following configuration in your `opencode.json` file to use the Appwrite MCP servers. {% tabs %} {% tabsitem #api-only title="API server" %} ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "appwrite": { "type": "local", "command": [ "uvx", "mcp-server-appwrite", "--sites" ], "enabled": true, "environment": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "appwrite-docs": { "type": "remote", "enabled": true, "url": "https://mcp-for-docs.appwrite.io" } } } ``` {% /tabsitem %} {% /tabs %} {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Test the integration" %} Try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![Implement OAuth authentication in Appwrite](/images/docs/mcp/opencode/oauth-question.png) {% /section %} --- ## Appwrite MCP and VS Code https://appwrite.io/docs/tooling/mcp/vscode Learn how you can add the Appwrite MCP servers to GitHub Copilot Chat in VS Code to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} In VS Code, open the **Command Palette** (press `CTRL + Shift + P` on Windows or `CMD + Shift + P` on MacOS) and run the `MCP: Open User Configuration` command. Choose which MCP servers you want to configure: {% tabs %} {% tabsitem #api-only title="API server" %} Update the `mcp.json` file to include the API server: ```json { "servers": { "appwrite-api": { "command": "uvx", "args": [ "mcp-server-appwrite", "--sites" ], "env": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} Update the `mcp.json` file to include the docs server: ```json { "servers": { "appwrite-docs": { "url": "https://mcp-for-docs.appwrite.io", "type": "http" } } } ``` {% /tabsitem %} {% /tabs %} Once you save the configuration, Copilot Chat will connect with the MCP server(s) and load all available tools. {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Test the integration" %} Open **Copilot Chat** in VS Code and switch to **Agent Mode** to test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![Search for portfolio site in Appwrite project](/images/docs/mcp/vscode/copilot-chat.png) {% /section %} --- ## Appwrite MCP and Windsurf Editor https://appwrite.io/docs/tooling/mcp/windsurf Learn how you can add the Appwrite MCP servers to Windsurf Editor to interact with both the Appwrite API and documentation. Before you begin, ensure you have the following **pre-requisites** installed on your system: {% tabs %} {% tabsitem #api-server-prerequisites title="API server" %} [uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system. {% /tabsitem %} {% tabsitem #docs-server-prerequisites title="Docs server" %} [Node.js](https://nodejs.org/en/download) and npm must be installed on your system. {% /tabsitem %} {% /tabs %} {% section #step-1 step=1 title="Add MCP servers" %} Open the **Windsurf Settings** page, head to the **Cascade** tab, find the **Model Context Protocol (MCP) Servers** section, and click on the **View raw config** button. ![Add MCP server](/images/docs/mcp/windsurf/windsurf-add-mcp-server.png) Choose which MCP server you want to configure: {% tabs %} {% tabsitem #api-only title="API server" %} Update the `mcp_config.json` file to include the API server: ```json { "mcpServers": { "appwrite-api": { "command": "uvx", "args": [ "mcp-server-appwrite", "--databases", "--users" ], "env": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` **Configuration:** - Replace `your-project-id` with your actual Appwrite project ID - Replace `your-api-key` with your Appwrite API key - Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`) {% /tabsitem %} {% tabsitem #docs-only title="Docs server" %} Update the `mcp_config.json` file to include the docs server: ```json { "mcpServers": { "appwrite-docs": { "serverUrl": "https://mcp-for-docs.appwrite.io" } } } ``` {% /tabsitem %} {% /tabs %} Once you save the details, head back to the MCP Servers section in the Windsurf Settings and click on the **Refresh** button. {% info title="Enable other API MCP tools" %} To enable additional API tools, learn more about [command-line arguments](/docs/tooling/mcp/api#command-line-arguments). {% /info %} {% /section %} {% section #step-2 step=2 title="Test the integration" %} Open Cascade chat in the Windsurf Editor and test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured: {% tabs %} {% tabsitem #test-api title="API server" %} **Example prompts:** - `Create a new user in my Appwrite project` - `List all databases in my project` - `Show me the collections in my database` - `Create a new document in my collection` - `Delete a specific user by ID` {% /tabsitem %} {% tabsitem #test-docs title="Docs server" %} **Example prompts:** - `How do I set up real-time subscriptions in Appwrite?` - `Show me how to authenticate users with OAuth` - `What are the best practices for database queries?` - `How do I implement file uploads with Appwrite Storage?` - `Show me an example of using Appwrite Functions` {% /tabsitem %} {% /tabs %} ![List data from an Appwrite database](/images/docs/mcp/windsurf/windsurf-cascade-chat.png) {% /section %} --- ## Build an ideas tracker with Android https://appwrite.io/docs/tutorials/android/step-1 **Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish. In this tutorial, you will build Idea tracker with Appwrite and Android. #### Concepts {% #concepts %} This tutorial will introduce the following concepts: 1. Setting up your first project 2. Authentication 3. Databases and tables 4. Queries and pagination #### Prerequisites {% #prerequisites %} 1. Basic knowledge of Kotlin and Android development. 2. Have [Android Studio](https://developer.android.com/studio) installed on your computer. --- ## Create app https://appwrite.io/docs/tutorials/android/step-2 #### Create Android project {% #create-android-project %} Create a Android app with the Android Studio **New Project** wizard. Select **Empty Activity** as the template. #### Add dependencies {% #add-dependencies %} Install the Android Appwrite SDK. Add the following to your dependencies in the `app/build.gradle` file: ```groovy implementation("io.appwrite:sdk-for-android:8.1.0") ``` In case you need to create OAuth 2 sessions in the future, the following activity needs to be added inside the `` tag, along side the existing `` tags in your [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro). Be sure to replace the **** string with your actual Appwrite project ID. You can find your Appwrite project ID in you project settings screen in your Appwrite Console. ```xml ... ... ``` #### Using the emulator {% #using-the-emulator %} Run your app on the [Android emulator](https://developer.android.com/studio/run/emulator). You can use the emulator to test your app on different versions of the Android platform and different screen sizes without the need to use physical devices. For now, you should see a blank screen. --- ## Set up Appwrite https://appwrite.io/docs/tutorials/android/step-3 #### Create project {% #create-project %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add an **Android app**. The **Package Name** should be the same as the one you used when creating your app. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip optional steps. #### Initialize Appwrite SDK {% #init-sdk %} To use Appwrite in our Android app, we'll need to find our project ID. Find your project's ID in the **Settings** page. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Create a new file `services/Appwrite.kt` to hold our Appwrite related code. Only one instance of the `Client` class should be created per app. Add the following code to it, replacing `` with your project ID. ```kotlin package .services import android.content.Context import io.appwrite.Client object Appwrite { private const val ENDPOINT = "https://.cloud.appwrite.io/v1" private const val PROJECT_ID = "" private lateinit var client: Client fun init(context: Context) { client = Client(context) .setEndpoint(ENDPOINT) .setProject(PROJECT_ID) } } ``` --- ## Add authentication https://appwrite.io/docs/tutorials/android/step-4 #### Creating an account service {% #creating-an-account-service %} We can use services to abstract business logic from our views. Create a service to handle user authentication with a new file `services/AccountService.kt`. Add the following code to it. ```kotlin package .services import io.appwrite.Client import io.appwrite.ID import io.appwrite.models.User import io.appwrite.exceptions.AppwriteException import io.appwrite.services.Account class AccountService(client: Client) { private val account = Account(client) suspend fun getLoggedIn(): User>? { return try { account.get() } catch (e: AppwriteException) { null } } suspend fun login(email: String, password: String): User>? { return try { account.createEmailPasswordSession( email = email, password = password ) getLoggedIn() } catch (e: AppwriteException) { null } } suspend fun register(email: String, password: String): User>? { return try { account.create( userId = ID.unique(), email = email, password = password ) login(email, password) } catch (e: AppwriteException) { null } } suspend fun logout() { account.deleteSession("current") } } ``` We can now use this service to login, register and logout a user. Integrate the service to the `/services/Appwrite.kt` file. Look for `// Add this line 👇` to find where the changes made here. ```kotlin object Appwrite { private const val ENDPOINT = "https://.cloud.appwrite.io/v1" private const val PROJECT_ID = "" private lateinit var client: Client // Add this line 👇 internal lateinit var account: AccountService fun init(context: Context) { client = Client(context) .setEndpoint(ENDPOINT) .setProject(PROJECT_ID) // Add this line 👇 account = AccountService(client) } } ``` #### Login screen {% #login-screen %} Using this service, we can now create a screen to login or register a user. Create a new file `ui/screens/UserScreen.kt` and add the following code to it. ```kotlin package .ui.screens import .services.AccountService import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import io.appwrite.models.User import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun UserScreen( user: MutableState>?>, accountService: AccountService ) { val coroutineScope = rememberCoroutineScope() var error by remember { mutableStateOf(null) } fun onLogin(email: String, password: String) { coroutineScope.launch { user.value = accountService.login(email, password) } error = if (user.value === null) { "Unable to login" } else { null } } fun onRegister(email: String, password: String) { coroutineScope.launch { user.value = accountService.register(email, password) } error = if (user.value === null) { "Unable to register" } else { null } } fun onLogout() { coroutineScope.launch { accountService.logout() user.value = null } } var username by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } if (user.value !== null) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "Logged in as ${user.value!!.email}") Button(onClick = { onLogout() }) { Text("Logout") } } } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { TextField( value = username, onValueChange = { username = it }, label = { Text("Email") } ) TextField( value = password, onValueChange = { password = it }, label = { Text("Password") }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) ) Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Button(onClick = { onLogin(username, password) }) { Text("Login") } Button(onClick = { onRegister(username, password) }) { Text("Register") } } if (error !== null) { Text( text = error!!, modifier = Modifier.padding(16.dp), color = androidx.compose.ui.graphics.Color.Red ) } } } ``` --- ## Add MainActivity https://appwrite.io/docs/tutorials/android/step-5 #### Creating the MainActivity {% #creating-the-mainactivity %} To show the new screen, we need to set up our `MainActivity` class. Open `MainActivity.kt` and update it with following code. This code sets up our `MainActivity` with a bottom navigation bar, including a **User** screen. ```kotlin package import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.* import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Person import androidx.compose.material3.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.appwrite.models.User import .services.Appwrite import .ui.screens.UserScreen import .services.AccountService enum class Screen { User } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Appwrite.init(applicationContext) setContent { AppContent(Appwrite.account) } } } @Composable private fun AppBottomBar(screen: MutableState) { BottomAppBar { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { IconButton(onClick = { screen.value = Screen.User }) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Icon(Icons.Default.Person, contentDescription = "User") Text("User") } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun AppContent(accountService: AccountService) { val user = remember { mutableStateOf>?>(null) } val screen = remember { mutableStateOf(Screen.User) } LaunchedEffect(screen) { user.value = accountService.getLoggedIn() } Scaffold(bottomBar = { AppBottomBar(screen) }) { padding -> Column(modifier = Modifier.padding(padding)) { when (screen.value) { Screen.User -> UserScreen(user, accountService) else -> Text("Ideas screen") } } } } ``` #### Test the MainActivity {% #test-the-mainactivity %} Launch the app and you should be able to use the Login screen to register, login, and logout. Confirm your email address is displayed once you are logged in. --- ## Add database https://appwrite.io/docs/tutorials/android/step-6 In Appwrite, data is stored as a table of rows. Create a table in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas. {% only_dark %} ![Create project screen](/images/docs/tutorials/dark/idea-tracker-table.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/tutorials/idea-tracker-table.png) {% /only_light %} Create a new table with the following columns: | Column | Type | Required | Size | |-------------|--------|----------|----------| | userId | String | Yes | 200 | | title | String | Yes | 200 | | description | String | No | 500 | {% only_dark %} ![Table permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.png) {% /only_dark %} {% only_light %} ![Table permissions screen](/images/docs/tutorials/idea-tracker-permissions.png) {% /only_light %} Navigate to the **Settings** tab of your table, add the role **Any** and check the **Read** box. Next, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes. #### Add and remove methods {% #add-add-remove-methods %} Now that you have a table to hold ideas, we can read and write to it from our app. Create a new file `services/IdeasService.kt` and add the following code to it. Replace the values for `ideaDatabaseId` and `ideaTableId` with the IDs of the database and table you created in the previous step. ```kotlin package .services import io.appwrite.Client import io.appwrite.ID import io.appwrite.Query import io.appwrite.models.Row import io.appwrite.services.TablesDB class IdeaService(client: Client) { companion object { private const val ideaDatabaseId = "" private const val ideaTableId = "" } private val tablesDB = TablesDB(client) suspend fun fetch(): List>> { return tablesDB.listRows( ideaDatabaseId, ideaTableId, listOf(Query.orderDesc("\$createdAt"), Query.limit(10)) ).rows } suspend fun add( userId: String, title: String, description: String ): Row> { return tablesDB.createRow( ideaDatabaseId, ideaTableId, ID.unique(), mapOf( "userId" to userId, "title" to title, "description" to description ) ) } suspend fun remove(id: String) { tablesDB.deleteRow( ideaDatabaseId, ideaTableId, id ) } } ``` Update the `services/Appwrite.kt` file to add a new property for the `IdeaService` class. Look for `// Add this line 👇` to find where the changes made here. ```kotlin package io.appwrite.tutorialforandroid.services import android.content.Context import io.appwrite.Client object Appwrite { private const val ENDPOINT = "https://.cloud.appwrite.io/v1" private const val PROJECT_ID = "" private lateinit var client: Client // Add this line 👇 internal lateinit var ideas: IdeaService internal lateinit var account: AccountService fun init(context: Context) { client = Client(context) .setEndpoint(ENDPOINT) .setProject(PROJECT_ID) // Add this line 👇 ideas = IdeaService(client) account = AccountService(client) } } ``` --- ## Create ideas page https://appwrite.io/docs/tutorials/android/step-7 Using the `IdeasService`, we can build a screen to submit and view ideas. Overwrite the contents of `ui/screens/IdeasScreen.kt` with the following code. ```kotlin package .screens import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import io.appwrite.models.Row import io.appwrite.models.User import kotlinx.coroutines.launch import services.IdeaService @OptIn(ExperimentalMaterial3Api::class) @Composable fun IdeasScreen( user: User>?, ideasService: IdeaService ) { var ideas by remember { mutableStateOf>>>(listOf()) } val coroutineScope = rememberCoroutineScope() LaunchedEffect(ideasService) { coroutineScope.launch { ideas = ideasService.fetch() } } fun onSubmit(title: String, description: String) { if (user === null) return coroutineScope.launch { ideas = listOf(ideasService.add(user.id, title, description)).plus(ideas) } } fun onRemove(ideaId: String) { coroutineScope.launch { ideas = ideas.filter { idea -> idea.id !== ideaId } ideasService.remove(ideaId) } } var title by remember { mutableStateOf("") } var description by remember { mutableStateOf("") } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { if (user != null) { TextField( value = title, onValueChange = { title = it }, label = { Text("Title") }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) TextField( value = description, onValueChange = { description = it }, label = { Text("Description") }, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) Button(onClick = { onSubmit(title, description) title = "" description = "" }) { Text("Submit") } } LazyColumn(modifier = Modifier.fillMaxSize()) { items(ideas) { idea -> Column(modifier = Modifier.padding(16.dp)) { Text(text = idea.data["title"]?.toString() ?: "", fontWeight = FontWeight(700)) Text(text = idea.data["description"]?.toString() ?: "") if (user?.id == idea.data["userId"]) Button(onClick = { onRemove(idea.id) }) { Text("Remove") } } } } } } ``` #### Update navigation {% #update-navigation %} Update `MainActivity.kt` to add the `IdeasScreen` to the navigation bar. Look for `// Add this line 👇` to find where the changes made here. ```kotlin package import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.* import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons // Add this line 👇 import androidx.compose.material.icons.filled.List import androidx.compose.material.icons.filled.Person import androidx.compose.material3.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.appwrite.models.User import .services.Appwrite import .services.AccountService import .ui.screens.UserScreen // Add this line 👇 import .services.IdeaService // Add this line 👇 import .ui.screens.IdeasScreen enum class Screen { User, // Add this line 👇 Ideas } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Appwrite.init(applicationContext) setContent { // Update this line 👇 AppContent(Appwrite.account, Appwrite.ideas) } } } // Add navigation with this composable 👇 @Composable private fun AppBottomBar(screen: MutableState) { BottomAppBar { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { IconButton(onClick = { screen.value = Screen.Ideas }) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Icon(Icons.Default.List, contentDescription = "Ideas") Text("Ideas") } } IconButton(onClick = { screen.value = Screen.User }) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Icon(Icons.Default.Person, contentDescription = "User") Text("User") } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable // Update this line 👇 private fun AppContent(accountService: AccountService, ideasService: IdeaService) { val user = remember { mutableStateOf>?>(null) } // Update this line 👇 val screen = remember { mutableStateOf(Screen.Ideas) } LaunchedEffect(screen) { user.value = accountService.getLoggedIn() } Scaffold(bottomBar = { AppBottomBar(screen) }) { padding -> Column(modifier = Modifier.padding(padding)) { when (screen.value) { Screen.User -> UserScreen(user, accountService) // Update this line 👇 else -> IdeasScreen(user.value, ideasService) } } } } ``` --- ## Next steps https://appwrite.io/docs/tutorials/android/step-8 #### Test your project {% #test-project %} You can now run your project and test it on your Android device or emulator. --- ## Coming soon https://appwrite.io/docs/tutorials/apple/step-1 Improve the docs, add this guide. We still don't have this guide in place, but we do have some great news. The Appwrite docs, just like Appwrite, is completely open sourced. This means, anyone can help improve them and add new guides and tutorials. If you see this page, **we're actively looking for contributions to this page**. Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. --- ## Server-side authentication with Astro https://appwrite.io/docs/tutorials/astro-ssr-auth/step-1 Appwrite takes away the stress of building and maintaining a backend. Appwrite helps implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box. If you're a Astro developer, the examples in this guide show you how Appwrite can help you add authentication to Astro apps faster. #### Before you start {% #before-you-start %} Before following this tutorial, have the following prepared: - A recent version of [Node.js](https://nodejs.org/en/download/) installed on your system. - A basic knowledge of Astro. Clone the [demos-for-astro](https://github.com/appwrite/demos-for-astro) examples and follow along with the source code. --- ## Create project https://appwrite.io/docs/tutorials/astro-ssr-auth/step-2 Create an [Astro project](https://docs.astro.build/en/install/auto/#1-run-the-setup-wizard) using: ```sh npm create astro@latest ``` The command prompt will be something similar to this. ```sh Where should we create your new project? ./my-astro-project How would you like to start your new project? Empty Do you plan to write TypeScript? No Install dependencies? Yes Initialize a new git repository? Yes ``` After the prompt is finished, you can head over to the newly created project. ```sh cd my-astro-project ``` #### Install Appwrite {% #install-appwrite %} Appwrite provides a Node SDK that can be used in your Astro apps. You can use Appwrite by installing the Node SDK as an NPM package. The Node SDK is intended for server-side use. If you want to use Appwrite in a client-side application, you should use the Web SDK instead. ```sh npm install node-appwrite ``` #### Add Node adapter to Astro {% #add-node-to-astro %} To use Astro as an SSR framework, you need to add the Node adapter to your project. Run the following command: ```sh npx astro add node ``` --- ## Initialize SDK https://appwrite.io/docs/tutorials/astro-ssr-auth/step-3 Before you can use Appwrite, you need to create the Appwrite `Client` and set the project ID and endpoint. The client is then used to create services like `Databases` and `Account`, so they all point to the same Appwrite project. Create a function to build services you need in a file like `src/server/appwrite.js` and **exporting the instances**. As part of the function, set the current user's session if they are logged in. This is done by accessing the session cookie from the request and calling the `setSession(session)` with the cookie value. {% info title="Appwrite client security" %} Notice that `createAdminClient` and `createSessionClient` returns **a new instance** of the Appwrite Client. When using Appwrite in server-integrations, it's important to **never share a `Client` instance** between two requests. Doing so could create security vulnerabilities. {% /info %} ```js // src/server/appwrite.js import { Client, Account } from "node-appwrite"; // The name of your cookie that will store the session export const SESSION_COOKIE = "my-custom-session"; // Admin client, used to create new accounts export function createAdminClient() { const client = new Client() .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT) .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT) .setKey(import.meta.env.APPWRITE_KEY); // Set the API key here! // Return the services you need return { get account() { return new Account(client); }, }; } // Session client, used to make requests on behalf of the logged in user export function createSessionClient(request) { const client = new Client() .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT) .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT); // Get the session cookie from the request and set the session const cookies = parseCookies(request.headers.get("cookie") ?? ""); const session = cookies.get(SESSION_COOKIE); if (!session) { throw new Error("Session cookie not found"); } client.setSession(session); // Return the services you need return { get account() { return new Account(client); }, }; } // Helper function to parse cookies function parseCookies(cookies) { const map = new Map(); for (const cookie of cookies.split(";")) { const [name, value] = cookie.split("="); map.set(name.trim(), value ?? null); } return map; } ``` #### Environment variables {% #environment-variables %} `import.meta.env.APPWRITE_KEY`, `import.meta.env.PUBLIC_APPWRITE_ENDPOINT` and `import.meta.env.PUBLIC_APPWRITE_PROJECT` are environment variables that are exported in your project's [.env file](https://kit.Astro.dev/docs/modules#$env-dynamic-public). For example, your `.env` might look something similar to this. ```env APPWRITE_KEY= PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 PUBLIC_APPWRITE_PROJECT= ``` The `PUBLIC_APPWRITE_ENDPOINT` is the endpoint of your Appwrite project, and the `PUBLIC_APPWRITE_PROJECT` is the ID of the project you want to use. You can get the values for these variables from the Appwrite console. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} The `APPWRITE_KEY` is an Appwrite API key with the necessary permissions to create new sessions. For this tutorial you'll need an API key with the following scopes: | Category {% width=120 %} | Required scopes | Purpose | | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | | Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. | {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} --- ## Add a server hook https://appwrite.io/docs/tutorials/astro-ssr-auth/step-4 Astro middleware are functions that run on the server before a page is displayed to the user. Astro locals are a way to store data that is specific to the current request. We can use these features to store the user's account data, so that it is available to all pages. Create a new file in the `src/` directory called `middleware.js`: ```js // src/middleware.js import { defineMiddleware } from "astro:middleware"; import { createSessionClient } from "./server/appwrite"; export const onRequest = defineMiddleware(async ({ request, locals }, next) => { try { const { account } = createSessionClient(request); locals.user = await account.get(); } catch {} return next(); }); ``` To ensure the `locals` object is typed correctly, we can add a type definition for it in a `env.d.ts` file at the root of the project: ```ts /// type Models = import("node-appwrite").Models; declare namespace App { interface Locals { user?: Models.User; } } interface ImportMetaEnv { readonly PUBLIC_APPWRITE_ENDPOINT: string; readonly PUBLIC_APPWRITE_PROJECT: string; readonly APPWRITE_KEY: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` Now, use the `locals` object in the home page to redirect based on the user's login status. Overwrite the file in the `src/pages` directory called `index.astro`: ```js --- const { user } = Astro.locals; if (user) { return Astro.redirect("/account"); } return Astro.redirect("/signup"); --- ``` If the user is logged in, they will be redirected to the account page. If they are not logged in, they will be redirected to the sign up page. --- ## Create sign up page https://appwrite.io/docs/tutorials/astro-ssr-auth/step-5 We can now implement our sign up page. Create a `signin.astro` file in the `src/pages` directory: ```js --- const { user } = Astro.locals; if (user) { return Astro.redirect("/account"); } ---
``` This is an HTML form with an email and password input. When the form is submitted, we want to send the email and password to Appwrite to authenticate the user. To use Astro form actions, add an `if (Astro.request.method === "POST")` statement to the server-side javascript. In the same file, implement the following. ```js --- import { SESSION_COOKIE, createAdminClient } from "../server/appwrite"; import { ID } from "node-appwrite"; // ... existing javascript if (Astro.request.method === "POST") { // Extract the form data const data = await Astro.request.formData(); const email = data.get("email"); const password = data.get("password"); const name = data.get("name"); // Create the admin client const { account } = createAdminClient(); // Create the email password session await account.create({ userId: ID.unique(), email, password, name }); const session = await account.createEmailPasswordSession({ email, password }); // Set the session cookie Astro.cookies.set(SESSION_COOKIE, session.secret, { path: "/", expires: new Date(session.expire), sameSite: "strict", secure: true, httpOnly: true, }); // Redirect to the account page return Astro.redirect("/account"); } --- ``` --- ## Create account page https://appwrite.io/docs/tutorials/astro-ssr-auth/step-6 Now the end-user is able to sign up, we can create the account page. This page will display basic information about the user, and allow the user to log out. Create a new file in the `src/pages` directory called `account.astro` and add the following code: ```js --- import { SESSION_COOKIE, createSessionClient } from "../server/appwrite"; // Redirect the user if not signed in const { user } = Astro.locals; if (!user) { return Astro.redirect("/signup"); } // Handle form action if (Astro.request.method === "POST") { // Create session client const { account } = createSessionClient(Astro.request); // Delete the Appwrite session await account.deleteSession({ sessionId: 'current' }); // Delete the session cookie Astro.cookies.delete(SESSION_COOKIE); // Redirect the user to sign up page return Astro.redirect("/signup"); } ---
  • Email: {user.email}
  • Name: {user.name}
  • ID: {user.$id}
``` --- ## OAuth authentication with SSR https://appwrite.io/docs/tutorials/astro-ssr-auth/step-7 To support the OAuth flow, we first redirect the user to the OAuth provider, and then handle the callback from the OAuth provider. To redirect, add a button to our sign up page that redirects the user to the OAuth provider. ```js
``` Add a new `POST` route to handle the redirect. ```js // src/pages/oauth.js import { createAdminClient } from "../server/appwrite"; import { OAuthProvider } from "node-appwrite"; export const POST = async ({ redirect, url }) => { // Create the Appwrite client const { account } = createAdminClient(); // Create an OAuth token const redirectUrl = await account.createOAuth2Token({ provider: OAuthProvider.Github, success: `${url.origin}/oauth`, failure: `${url.origin}/signup` }); // Redirect the end-user to the OAuth2 provider authentication return redirect(redirectUrl); }; ``` The `createOAuth2Token` method returns a URL to the OAuth provider. After authentication the OAuth provider redirects the user back to the `/oauth` route with the `userId` and `secret` URL query parameters. Create a new `GET` route to handle the callback and create a session for the user. ```js // src/pages/oauth.js import { createAdminClient, SESSION_COOKIE } from "../server/appwrite"; import { OAuthProvider } from "node-appwrite"; // ... existing POST handler export const GET = async ({ redirect, cookies, url }) => { // Get the user ID and secret from the URL const userId = url.searchParams.get("userId"); const secret = url.searchParams.get("secret"); // Create the Appwrite client const { account } = createAdminClient(); // Exchange the token for a session const session = await account.createSession({ userId, secret }); // Set the session cookie cookies.set(SESSION_COOKIE, session.secret, { sameSite: "lax", expires: new Date(session.expire), secure: true, httpOnly: true, path: "/", }); // Redirect the logged in user to the account page return redirect("/account"); }; ``` --- ## All set https://appwrite.io/docs/tutorials/astro-ssr-auth/step-8 Start a preview of your app by running `npm run dev`. If you want to see the complete source code with styling, see the [demos-for-astro](https://github.com/appwrite/demos-for-astro/tree/main/server-side-rendering) repository. #### Other authentication methods {% #other-authentication-methods %} Appwrite also supports OAuth, passwordless login, anonymous login, and phone login. Learn more about them in the [authentication guide](https://appwrite.io/docs/products/auth). --- ## Coming soon https://appwrite.io/docs/tutorials/flutter/step-1 Improve the docs, add this guide. We still don't have this guide in place, but we do have some great news. The Appwrite docs, just like Appwrite, is completely open sourced. This means, anyone can help improve them and add new guides and tutorials. If you see this page, **we're actively looking for contributions to this page**. Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. --- ## Server-side authentication with Next.js https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-1 Appwrite takes away the stress of building and maintaining a backend. Appwrite helps implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box. If you're a Next.js developer, the examples in this guide show you how Appwrite can help you add authentication to Next.js apps faster. #### Before you start {% #before-you-start %} Before following this tutorial, have the following prepared: - A recent version of [Node.js](https://nodejs.org/en/download/) installed on your system. - A basic knowledge of Next.js and React. If you're inspired and wish to follow along, make sure you've followed [Start with React](https://appwrite.io/docs/quick-starts/react) first. Clone the [demos-for-react](https://github.com/appwrite/demos-for-react/tree/main/nextjs) examples and follow along with the source code. --- ## Create project https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-2 Create a project using [Next.js](https://nextjs.org/docs/getting-started/installation#automatic-installation). ```sh npx create-next-app@latest ``` The command will give you a prompt with several project types. We'll be starting with a skeleton project. The prompt will be something similar to this. ```sh What is your project named? my-app Would you like to use TypeScript? No Would you like to use ESLint? No Would you like to use Tailwind CSS? No Would you like to use `src/` directory? Yes Would you like to use App Router? (recommended) Yes Would you like to customize the default import alias (@/*)? No What import alias would you like configured? [Enter] ``` After the prompt is finished, you can head over to the newly created project. ```sh cd my-app npm install ``` #### Install Appwrite {% #install-appwrite %} Appwrite provides a Node SDK that can be used in your Next.js apps. You can use Appwrite by installing the Node SDK as an NPM package. The Node SDK is intended for server-side use. If you want to use Appwrite in a client-side application, you should use the [Web SDK](/docs/quick-starts/web) instead. ```sh npm install node-appwrite@20.0.0 ``` --- ## Initialize SDK https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-3 Before you can use Appwrite, you need to create the Appwrite `Client` and set the project ID and endpoint. The client is then used to create services like `Databases` and `Account`, so they all point to the same Appwrite project. Create a function to build services you need in a file like `src/lib/server/appwrite.js` and **exporting the instances**. ```js // src/lib/server/appwrite.js "use server"; import { Client, Account } from "node-appwrite"; import { cookies } from "next/headers"; export async function createSessionClient() { const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT); const session = await cookies().get("my-custom-session"); if (!session || !session.value) { throw new Error("No session"); } client.setSession(session.value); return { get account() { return new Account(client); }, }; } export async function createAdminClient() { const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT) .setKey(process.env.NEXT_APPWRITE_KEY); return { get account() { return new Account(client); }, }; } ``` As part of the function, set the current user's session if they are logged in. This is done by accessing the session cookie from the request and calling the `setSession(session)` with the cookie value. {% info title="Appwrite client security" %} Notice that `createAdminClient` and `createSessionClient` returns **a new instance** of the Appwrite Client. When using Appwrite in server-integrations, it's important to **never share a `Client` instance** between two requests. Doing so could create security vulnerabilities. {% /info %} #### Environment variables {% #environment-variables %} `NEXT_APPWRITE_KEY`, `NEXT_PUBLIC_APPWRITE_ENDPOINT` and `NEXT_PUBLIC_APPWRITE_PROJECT` are environment variables that are exported in your project's [.env file](https://kit.svelte.dev/docs/modules#$env-dynamic-public). For example, your `.env` might look something similar to this. ```env NEXT_APPWRITE_KEY= NEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 NEXT_PUBLIC_APPWRITE_PROJECT= ``` The `NEXT_PUBLIC_APPWRITE_ENDPOINT` is the endpoint of your appwrite instance , and the `NEXT_PUBLIC_APPWRITE_PROJECT` is the ID of the project you want to use. You can get the values for these variables from the Appwrite console. {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} The `NEXT_APPWRITE_KEY` is an Appwrite API key with the necessary permissions to create new sessions. For this tutorial you'll need an API key with the following scopes: | Category {% width=120 %} | Required scopes | Purpose | |-----------|---------------------|---------| | Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. | {% only_dark %} ![Server integrations](/images/docs/quick-starts/dark/integrate-server.png) {% /only_dark %} {% only_light %} ![Server integrations](/images/docs/quick-starts/integrate-server.png) {% /only_light %} --- ## Get the logged in user https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-4 Build a utility function to get the logged in user from Appwrite. This function will be used in our components and routes to check if a user is logged in, and access the user's details. Edit the `src/lib/server/appwrite.js` file to create a new function called `getLoggedInUser`. ```js // ... your initilization functions export async function getLoggedInUser() { try { const { account } = await createSessionClient(); return await account.get(); } catch (error) { return null; } } ``` Now, use the `getLoggedInUser` function in the home page to redirect based on the user's login status. Create a new file in the `app` directory called `page.jsx`. ```js // src/app/page.jsx import { getLoggedInUser } from "@/lib/server/appwrite"; import { redirect } from "next/navigation"; export default async function Home() { const user = await getLoggedInUser(); if (!user) redirect("/signup"); redirect("/account"); } ``` The user will be redirected to the sign up page if they are not logged in, or to the account page if they are logged in. --- ## Create sign up page https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-5 We can now implement our sign up page. Create a `page.jsx` file in the `src/app/signup` directory: ```jsx // src/app/signup/page.jsx import { getLoggedInUser } from "@/lib/server/appwrite"; export default async function SignUpPage() { const user = await getLoggedInUser(); if (user) redirect("/account"); return ( <>
); } ``` This is an HTML form with an email and password input. When the form is submitted, we want to send the email and password to Appwrite to authenticate the user. To use Next.js form actions we create the `signUpWithEmail` function in the same file: ```jsx // src/app/signup/page.jsx // previous imports ... import { ID } from "node-appwrite"; import { createAdminClient } from "@/lib/server/appwrite"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; async function signUpWithEmail(formData) { "use server"; const email = formData.get("email"); const password = formData.get("password"); const name = formData.get("name"); const { account } = await createAdminClient(); await account.create({ userId: ID.unique(), email, password, name }); const session = await account.createEmailPasswordSession({ email, password }); cookies().set("my-custom-session", session.secret, { path: "/", httpOnly: true, sameSite: "strict", secure: true, }); redirect("/account"); } // the SignUpPage component ... ``` The `signUpWithEmail` function is an async function that takes the form data as an argument. It uses the `createAdminClient` function to create an admin Appwrite client and then calls the `createEmailPasswordSession` method on the `account` object. This method takes the email and password as arguments and returns a session object. We then set the session secret in a cookie and redirect the user to the account page. --- ## Create account page https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-6 Now the end-user is able to sign up, we can create the account page. This page will display basic information about the user, and allow the user to log out. Create a new file in the `src/app/account` directory called `page.jsx` and add the following code: ```jsx // src/app/account/page.jsx import { createSessionClient, getLoggedInUser, } from "@/lib/server/appwrite"; import { redirect } from "next/navigation"; import { cookies } from "next/headers"; async function signOut() { "use server"; const { account } = await createSessionClient(); cookies().delete("my-custom-session"); await account.deleteSession({ sessionId: "current" }); redirect("/signup"); } export default async function HomePage() { const user = await getLoggedInUser(); if (!user) redirect("/signup"); return ( <>
  • Email: {user.email}
  • Name: {user.name}
  • ID: {user.$id}
); } ``` This code is similar to the `signup` page, but it uses the `getLoggedInUser` function to get the user's information. If the user is not logged in, the page will redirect to the sign-in page. Again, we use Next.js form actions to execute Appwrite code on the server. This time, the `signOut` function deletes the session cookie and redirect the user to the sign-in page. --- ## OAuth authentication with SSR https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-7 #### Enable OAuth provider {% #enable-oauth-provider %} To enable the GitHub OAuth provider, navigate to your Appwrite Console > Auth > Settings > OAuth2 Providers > GitHub To support the OAuth flow, we first redirect the user to the OAuth provider, and then handle the callback from the OAuth provider. #### OAuth server action {% #oauth-server-action %} Add a new server action. Navigate to `src/lib/server` and create a new file `oauth.js`: ```js // src/lib/server/oauth.js "use server"; import { createAdminClient } from "@/lib/server/appwrite"; import { redirect } from "next/navigation"; import { headers } from "next/headers"; import { OAuthProvider } from "node-appwrite"; export async function signUpWithGithub() { const { account } = await createAdminClient(); const origin = headers().get("origin"); const redirectUrl = await account.createOAuth2Token({ provider: OAuthProvider.Github, success: `${origin}/oauth`, failure: `${origin}/signup`, }); return redirect(redirectUrl); }; ``` The `createOAuth2Token` method redirects the user to the OAuth provider, and then the OAuth provider redirects the user back to the `/OAuth` route with the `userId` and `secret` URL query parameters. #### OAuth form {% #oauth-form %} To redirect, add a button to our sign up page that redirects the user to the OAuth provider. ```jsx // src/app/signup/page.jsx // ... existing imports import { signUpWithGithub } from "@/lib/server/oauth"; export default async function SignUpPage() { const user = await getLoggedInUser(); if (user) redirect("/account"); return ( <> {/* ... existing form */}
); } ``` #### OAuth callback {% #oauth-callback %} Handle the callback and create a session for the user. Create a new Next.js server route at `src/app/oauth/route.js`: ```js // src/app/oauth/route.js import { createAdminClient } from "@/lib/server/appwrite"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; export async function GET(request) { const userId = request.nextUrl.searchParams.get("userId"); const secret = request.nextUrl.searchParams.get("secret"); const { account } = await createAdminClient(); const session = await account.createSession({ userId, secret }); cookies().set("my-custom-session", session.secret, { path: "/", httpOnly: true, sameSite: "strict", secure: true, }); return NextResponse.redirect(`${request.nextUrl.origin}/account`); } ``` --- ## All set https://appwrite.io/docs/tutorials/nextjs-ssr-auth/step-8 If you want to see the complete source code with styling, see the [demos-for-react](https://github.com/appwrite/demos-for-react/tree/master/nextjs/server-side-rendering) repository. #### Other authentication methods {% #other-authentication-methods %} Appwrite also supports OAuth, passwordless login, anonymous login, and phone login. Learn more about them in the [authentication guide](https://appwrite.io/docs/products/auth). --- ## Build an idea tracker with Next.js https://appwrite.io/docs/tutorials/nextjs/step-1 **Idea Tracker**: an app to track all the side project ideas that you'll start, but probably never finish. In this tutorial, you will build an Idea Tracker with Appwrite and Next.js. {% only_dark %} ![Create project screen](/images/docs/tutorials/dark/idea-tracker.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/tutorials/idea-tracker.png) {% /only_light %} #### Concepts {% #concepts %} This tutorial will introduce the following concepts: 1. Setting up your first project 2. Authentication 3. Navigation 4. Databases and tables 5. Queries #### Prerequisites {% #prerequisites %} 1. Basic knowledge of JavaScript and React. 2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer. --- ## Create app https://appwrite.io/docs/tutorials/nextjs/step-2 #### Create Next.js project {% #create-nextjs-project %} Create a Next.js app with the `npx create-next-app` command. The command will install all the necessary dependencies for you. ```sh npx create-next-app@latest ideas-tracker --typescript --eslint --app --src-dir --import-alias "@/*" ``` #### Add dependencies {% #add-dependencies %} Once the project is created, change your current working directory and install the JavaScript Appwrite SDK. ```sh cd ideas-tracker npm install appwrite npm install "@appwrite.io/pink" ``` Open `src/app/globals.css` and replace the content with the following to import the relevant style files. ```css /* src/app/globals.css */ @import "@appwrite.io/pink"; @import "@appwrite.io/pink-icons"; * { box-sizing: border-box; } html, body { overflow-x: hidden; } body { background: hsl(var(--color-neutral-50)); } .dark body { background: hsl(var(--color-neutral-900)); } ``` You can start your development server to see your app in the browser. ```sh npm run dev ``` This will start a server at `http://localhost:3000/`. --- ## Set up Appwrite https://appwrite.io/docs/tutorials/nextjs/step-3 #### Create project {% #create-project %} Head to the [Appwrite Console](https://cloud.appwrite.io/console). {% only_dark %} ![Create project screen](/images/docs/quick-starts/dark/create-project.png) {% /only_dark %} {% only_light %} ![Create project screen](/images/docs/quick-starts/create-project.png) {% /only_light %} If this is your first time using Appwrite, create an account and create your first project. Then, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. {% only_dark %} ![Add a platform](/images/docs/quick-starts/dark/add-platform.png) {% /only_dark %} {% only_light %} ![Add a platform](/images/docs/quick-starts/add-platform.png) {% /only_light %} You can skip the optional steps. #### Environment variables {% #environment-variables %} To connect to Appwrite in our app, we'll need to configure our project endpoint and project ID. We keep the secrets by using environment variables for the endpoint and project ID. Your project ID is located in the **Settings** page in the Appwrite console. {% only_dark %} ![Project settings screen](/images/docs/quick-starts/dark/project-id.png) {% /only_dark %} {% only_light %} ![Project settings screen](/images/docs/quick-starts/project-id.png) {% /only_light %} Add a `.env.local` file to the root directory and add the following code to it, replacing `PROJECT_ID` with your project id. ``` NEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 NEXT_PUBLIC_APPWRITE_PROJECT=PROJECT_ID ``` #### Initialize Appwrite SDK {% #init-sdk %} Create a new file `lib/appwrite.ts` for the Appwrite related code. Only one instance of the `Client()` class should be created per app. Add the following code to it. ```ts // lib/appwrite.ts import { Client, Account, TablesDB } from "appwrite"; const client = new Client(); client .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!); export const account = new Account(client); export const tablesDB = new TablesDB(client); export { ID } from "appwrite"; ``` --- ## Add authentication https://appwrite.io/docs/tutorials/nextjs/step-4 For our ideas tracker app, we want any visitor to be able to read the ideas that are stored. On the other hand, we don't want the page spammed with just about anything from anyone just stopping by. To prevent that, or at least making it a bit more difficult, editing ideas will be available for logged in users only. With authentication, we can differentiate between users and decide which users have access to which content. We will build a page with a simple login form and store its related logic in a custom hook so it can be reused. #### User session hook {% #user-session-hook %} There are a few standard functions involved in handling a user session that are added to the hook. The user needs to be able to register to an account, login to the account and logout from it. We are using Appwrite as a backend to handle the user details, so we need to connect to Appwrite by importing the configurations from [step 3](/docs/tutorials/nextjs/step-3). The response from these interactions will be stored in state to get more information about the user in our app. Create a new file `hooks/useAuth.ts` and add the following code. ```ts // hooks/useAuth.ts import { useState, useEffect } from 'react'; import { account } from '../lib/appwrite'; import { ID } from 'appwrite'; import type { Models } from 'appwrite'; import { useRouter } from 'next/navigation'; export function useAuth() { const [current, setCurrent] = useState(null); const [loading, setLoading] = useState(true); const router = useRouter(); const register = async (email: string, password: string): Promise => { await account.create({ userId: ID.unique(), email, password }); await login(email, password); }; const login = async (email: string, password: string): Promise => { const session = await account.createEmailPasswordSession({ email, password }); setCurrent(session); router.push('/'); }; const logout = async (): Promise => { await account.deleteSession('current'); setCurrent(null); router.push('/'); }; const getCurrentUser = async () => { try { const user = await account.get(); setCurrent(user); } catch (error) { setCurrent(null); } finally { setLoading(false); } }; useEffect(() => { getCurrentUser(); }, []); return { current, loading, login, logout, register, }; } ``` #### Login page {% #login-page %} Create a new file `src/app/login/page.tsx`. This will create a new page accessible at `/login`. We will define functions to handle form submissions and show either a signup or a login form. ```tsx // src/app/login/page.tsx 'use client'; import { useState } from 'react'; import { useAuth } from '../../hooks/useAuth'; import AuthForm from '../../components/AuthForm'; export default function LoginPage() { const { login, register } = useAuth(); const [isSignUp, setIsSignUp] = useState(false); const handleLogin = async (event: React.FormEvent) => { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); await login( formData.get('email') as string, formData.get('password') as string ); form.reset(); }; const handleRegistration = async (event: React.FormEvent) => { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); await register( formData.get('email') as string, formData.get('password') as string ); form.reset(); }; return (

Login/Register

); } ``` This page renders a login or sign up form depending on `isSignUp`'s state. We will also show buttons to toggle between the two different types of forms. #### Authentication forms {% #auth-forms %} In the previous step, we defined an `AuthForm` to handle signup and login. Let's build this form now. Create a new file `src/components/AuthForm.tsx` and add the following code. ```tsx // src/components/AuthForm.tsx interface AuthFormProps { handleSubmit: (event: React.FormEvent) => void | Promise; submitType: string; } export default function AuthForm({ handleSubmit, submitType }: AuthFormProps) { return (
); } ``` You can now navigate to `/login` in your browser to check out the new page. --- ## Add navigation https://appwrite.io/docs/tutorials/nextjs/step-5 To help our users navigate the app we want it to have a navigation bar that's visible on all pages. We will use the `useAuth()` hook for information about the current user. With this piece of information we will show a login button when no user is logged in and a logout button when one is. We will also put the user's email address next to the logout button. Create a new file `src/components/Navbar.tsx` and add the code below. ```tsx // src/components/Navbar.tsx 'use client'; import Link from 'next/link'; import { useAuth } from '../hooks/useAuth'; export default function Navbar() { const { current, logout } = useAuth(); return ( ); } ``` Now we need to add the navigation bar to our app layout. Update `src/app/layout.tsx` to include the navbar. ```tsx // src/app/layout.tsx import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; import Navbar from '../components/Navbar'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: 'Ideas Tracker', description: 'Track your side project ideas', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` Have a look in the browser at both the main page and the login page to test the new functionality. --- ## Add database https://appwrite.io/docs/tutorials/nextjs/step-6 In Appwrite, data is stored as a table of rows. Create a new database and table in the [Appwrite Console](https://cloud.appwrite.io/) to store the ideas. {% only_dark %} ![Create table screen](/images/docs/tutorials/dark/idea-tracker-table.png) {% /only_dark %} {% only_light %} ![Create table screen](/images/docs/tutorials/idea-tracker-table.png) {% /only_light %} Create a new table with the following columns: | Field | Type | Required | Size | |-------------|--------|----------|----------| | userId | String | Yes | 200 | | title | String | Yes | 200 | | description | String | No | 500 | Change the table's permissions in the settings to give access. {% only_dark %} ![Table permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.png) {% /only_dark %} {% only_light %} ![Table permissions screen](/images/docs/tutorials/idea-tracker-permissions.png) {% /only_light %} Navigate to the **Settings** tab of your table, add the role **Any** and check the **Read** box. Next, add a **Users** role and give them access to **Create** by checking that box. For security, we won't grant table-level **Update** and **Delete** permissions to all users. Instead, we'll implement row-level permissions so that only the creator of each idea can update or delete their own ideas. #### Environment variables {% #environment-variables %} Just like when we set up the connection to Appwrite in [step 3](/docs/tutorials/nextjs/step-3), we need to keep the variables with the table id secret. Open the `.env.local` file and add your database id and your table id to it. ``` NEXT_PUBLIC_DATABASE_ID="YOUR_DATABASE_ID" NEXT_PUBLIC_TABLE_ID="YOUR_TABLE_ID" ``` #### Query methods {% #query-methods %} Now that we have a table in the database to hold ideas, we can connect to it from our app. Our users should be able to read, add and remove ideas. We will add a new hook, `useIdeas`, to handle this functionality. Create a new file `hooks/useIdeas.ts` and add the following code. ```ts // hooks/useIdeas.ts import { useState, useEffect } from 'react'; import { ID, Query, Permission, type Models } from 'appwrite'; import { tablesDB } from '../lib/appwrite'; const databaseId = process.env.NEXT_PUBLIC_DATABASE_ID!; const tableId = process.env.NEXT_PUBLIC_TABLE_ID!; const queryLimit = 10; interface Idea extends Models.Row { title: string; description: string; userId: string; } export function useIdeas() { const [current, setCurrent] = useState([]); const [loading, setLoading] = useState(true); // Fetch the 10 most recent ideas from the database const fetch = async (): Promise => { try { const response = await tablesDB.listRows( databaseId, tableId, [Query.orderDesc('$createdAt'), Query.limit(queryLimit)] ); setCurrent(response.rows as Idea[]); } catch (error) { console.error('Error fetching ideas:', error); } finally { setLoading(false); } }; // Add new idea to the database const add = async (idea: Omit): Promise => { try { const response = await tablesDB.createRow( databaseId, tableId, ID.unique(), idea, [ Permission.read('any'), Permission.update(`user:${idea.userId}`), Permission.delete(`user:${idea.userId}`) ] ); setCurrent(prev => [response as Idea, ...prev].slice(0, queryLimit)); } catch (error) { console.error('Error adding idea:', error); } }; const remove = async (id: string): Promise => { try { await tablesDB.deleteRow(databaseId, tableId, id); await fetch(); // Refetch ideas to ensure we have 10 items } catch (error) { console.error('Error removing idea:', error); } }; useEffect(() => { fetch(); }, []); return { current, loading, add, fetch, remove, }; } ``` Now we can call the `useIdeas()` hook from the home page. --- ## Ideas page https://appwrite.io/docs/tutorials/nextjs/step-7 With the methods in the `useIdeas()` hook we can get some ideas to the home page for the users to interact with. We will use it in a form component so the logged in users can add their ideas, and in a list component to render the ten most recent ideas. We start with building the form. #### Idea form {% #idea-form %} On the home page, the logged in users should be able to add their ideas to the Appwrite database. The form needs a text field for filling in the title, a textarea for the description and a submit button. Create a new file `src/components/IdeasForm.tsx` and add the following code. ```tsx // src/components/IdeasForm.tsx 'use client'; import { useIdeas } from '../hooks/useIdeas'; import { useAuth } from '../hooks/useAuth'; export default function IdeasForm() { const { add } = useIdeas(); const { current: user } = useAuth(); const handleAddIdea = async (event: React.FormEvent) => { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); if (!user) return; const postIdeaData = { userId: user.userId, title: formData.get('title') as string, description: formData.get('description') as string, }; await add(postIdeaData); form.reset(); }; if (!user) { return null; // Don't render form if user is not logged in } return (

Submit Idea

  • Please login to submit an idea.

    Latest Ideas

    • {{ idea.title }}

      {{ idea.description }}

    ``` --- ## Next steps https://appwrite.io/docs/tutorials/vue/step-8 #### Test your project {% #test-project %} Run your project with `npm run dev -- --open --port 3000` and open [http://localhost:3000](http://localhost:3000) in your browser. --- ## Integrations https://appwrite.io/integrations Connect your favorite apps to Appwrite for a unified tech stack. Explore the Appwrite catalog: a marketplace to find integrations for your projects. Browse integrations by category including AI, Deployments, Messaging, Auth, Payments, Logging, MCP, Databases, Search, Sites, and Storage. --- ## Text to speech with ElevenLabs https://appwrite.io/integrations/ai-elevenlabs-text-to-speech ElevenLabs provides developers with powerful tools to integrate realistic, human-like voice capabilities into their applications through APIs and SDKs. ElevenLabs’ Text to Speech API converts written text into spoken audio with very natural sounding voices, capturing realistic intonation, pacing, emotion and context awareness. ### How does the integration work? You can utilize a pre-built Appwrite Site template with text-to-speech capabilities implemented using ElevenLabs. This allows you to convert any piece of text to a human-like voice and download the generated audio. ### How to implement To implement the ElevenLabs text-to-speech integration, there are several steps you must complete: #### Step 1: Sign up for ElevenLabs First, you must [sign up for an ElevenLabs account](https://elevenlabs.io/app/sign-up). Once your account is set up, navigate to the [Developers](https://elevenlabs.io/app/developers) page from the sidebar, click on the **API Keys** tab, and generate an **API key** with the **Text to Speech** endpoint access enabled. Save this API key for further usage. ![Create API key](/images/integrations/ai-elevenlabs-text-to-speech/api-key.png) #### Step 2: Create the Appwrite Site For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](/docs/advanced/self-hosting/configuration/sites) to use Appwrite Sites templates. Head over to the Appwrite console, navigate to the **Sites** page, click on **Create site** and select the **Clone a template** option, and search for the **Text-to-speech with ElevenLabs** site template. ![Site template](/images/integrations/ai-elevenlabs-text-to-speech/template.png) During the setup process, add the **ElevenLabs API key** in the **Variables** section. ![Environment variables](/images/integrations/ai-elevenlabs-text-to-speech/variable.png) #### Step 3: Test the site Once all the steps are complete, it is time to test the site! You can enter any text you like (or use the pre-filled example), update the additional configuration options, and generate an audio file. ### Read more about ElevenLabs and Appwrite Sites If you would like to learn more about ElevenLabs and Appwrite Sites, we have some resources that you should visit: - [Sign up for ElevenLabs](https://elevenlabs.io/app/sign-up) - [ElevenLabs text to speech docs](https://elevenlabs.io/docs/capabilities/text-to-speech) - [Appwrite Sites docs](/docs/products/sites) - [Build with Appwrite Sites templates](/docs/products/sites/templates) - [Appwrite Sites API reference](/docs/references/cloud/server-nodejs/sites) --- ## Image classification with Hugging Face https://appwrite.io/integrations/ai-hugging-face-image-classification Image classification is a key application of machine learning that involves categorizing images into predefined classes. Hugging Face is a leading AI company that offers an extensive library of pre-trained models for various natural language processing and computer vision tasks, including image classification. By integrating Hugging Face's powerful models with your applications, you can leverage state-of-the-art technology to automate and enhance image analysis. ### How does the integration work? You can utilize a pre-built Appwrite function template to add image classification with Hugging Face to your app. This will allow you to upload an image to an Appwrite storage bucket and store labels from the image in an Appwrite collection. ### How to implement To implement the Hugging Face image classification function, there are several steps you must complete: #### Step 1: Sign up for Hugging Face First, you must [sign up for a Hugging Face account](https://huggingface.co/join). Once your account is set up, visit your profile settings, head to the [Access Tokens](https://huggingface.co/settings/tokens) page, and create an **access token** with the **Inference** permissions. Save this token for further usage. ![Create API token](/images/integrations/ai-hugging-face-image-classification/hugging-face-create-api-token.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click on the **Templates** tab, and search for the **Image Classification** function template. ![Function template](/images/integrations/ai-hugging-face-image-classification/appwrite-functions-image-classification-hugging-face.png) During the setup process, click on the checkbox to generate an Appwrite API key on completion and add the **Hugging Face access token** in the **Variables** step. If you are self-hosting Appwrite, then click on the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Environment variables](/images/integrations/ai-hugging-face-image-classification/appwrite-functions-env-variables-image-classification-hugging-face.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. #### Step 3: Test the function Once all the steps are complete, it is time to test the function! Use the Appwrite console or one of Appwrite’s SDKs to [upload an image](https://appwrite.io/docs/references/cloud/client-web/storage#createFile) to the `image_classification` storage bucket. If successful, you will find a response saved in the `image_classification` collection in the `ai` database in the following format: | image | labels | | --- | --- | | `66a13bf100318c752f1b` | `[{"label":"llama","score":0.9664694666862488},{"label":"comic book","score":0.019199883565306664},{"label":"suit, suit of clothes","score":0.005637330934405327},{"label":"television, television system","score":0.00037627643905580044},{"label":"muzzle","score":0.00019786457414738834}]` | The `image` attribute contains the ID of the image file uploaded to the `image_classification` storage bucket, and the `labels` attribute contains the response from Hugging Face, including all the labels and their confidence score. ### Read more about Hugging Face and Appwrite Functions If you would like to learn more about Hugging Face and Appwrite Functions, we have some resources that you should visit: - [Sign up for Hugging Face](https://huggingface.co/login) - [Set up the Hugging Face provider in Appwrite](https://appwrite.io/docs/products/ai/tutorials/image-classification) - [Building with Appwrite AI Function templates](https://appwrite.io/blog/post/building-with-ai-function-templates) - [Introducing the Python machine learning runtime](https://appwrite.io/blog/post/introducing-python-machine-learning-runtime) - [Find more function templates](https://appwrite.io/docs/products/functions/templates) - [Appwrite Functions API reference](https://appwrite.io/docs/references) --- ## Language translation with Hugging Face https://appwrite.io/integrations/ai-hugging-face-language-translation Language translation is a fundamental task in natural language processing that involves converting text from one language to another. Hugging Face offers a robust library of pre-trained models for various natural language processing tasks, including language translation. By integrating Hugging Face's advanced models with your applications, you can automate and enhance translating text across different languages. ### How does the integration work? You can utilize a pre-built Appwrite function template to add language translation with Hugging Face to your app. This will allow you to translate text from one language to another. ### How to implement To implement the Hugging Face language translation function, there are several steps you must complete: #### Step 1: Sign up for Hugging Face First, you must [sign up for a Hugging Face account](https://huggingface.co/join). Once your account is set up, visit your profile settings, head to the [Access Tokens](https://huggingface.co/settings/tokens) page, and create an **access token** with **Inference** permissions. Save this token for further usage. ![Create API token](/images/integrations/ai-hugging-face-language-translation/hugging-face-create-api-token.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Language Translation** function template. ![Function template](/images/integrations/ai-hugging-face-language-translation/language-translation-function-template.png) During the setup process, add the **Hugging Face access token** in the **Variables** step. ![Environment variables](/images/integrations/ai-hugging-face-language-translation/language-translation-env-variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. #### Step 3: Test the function Once the function is ready, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. You can open this URL in your browser to test with our pre-built interface. ![Demo](/images/integrations/ai-hugging-face-language-translation/language-translation-demo.png) You can also use tools like Postman, cURL, or your favorite programming language to send a POST (HTTP) request with a prompt and receive a response from the function. ```bash curl -X POST http://DEPLOYED_FUNCTION_DOMAIN \ -H "Content-Type: application/json" \ -d '{"source": "What is Appwrite?"}' ``` ### Read more about Hugging Face and Appwrite Functions If you would like to learn more about Hugging Face and Appwrite Functions, we have some resources that you should visit: - [Sign up for Hugging Face](https://huggingface.co/login) - [Set up the Hugging Face provider in Appwrite](https://appwrite.io/docs/products/ai/tutorials/language-translation) - [Find more function templates](https://appwrite.io/docs/products/functions/templates) - [Appwrite Functions API reference](https://appwrite.io/docs/references) --- ## Speech recognition with Hugging Face https://appwrite.io/integrations/ai-hugging-face-speech-recognition Speech recognition is a transformative technology that converts spoken language into text. Hugging Face, a prominent AI company offers a comprehensive library of pre-trained models for various natural language processing tasks, including speech recognition. By integrating Hugging Face's models with your applications, you can automate and enhance the transcription and analysis of audio content. ### How does the integration work? You can utilize a pre-built Appwrite function template to add speech recognition with Hugging Face to your app. This will allow you to upload an audio file to an Appwrite storage bucket and store the recognized speech in an Appwrite collection as text. ### How to implement To implement the Hugging Face speech recognition function, there are several steps you must complete: #### Step 1: Sign up for Hugging Face First, you must [sign up for a Hugging Face account](https://huggingface.co/join). Once your account is set up, visit your profile settings, head to the [Access Tokens](https://huggingface.co/settings/tokens) page, and create an **access token** with the **Inference** permissions. Save this token for further usage. ![Create API token](/images/integrations/ai-hugging-face-speech-recognition/token.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Speech Recognition** function template. ![Function template](/images/integrations/ai-hugging-face-speech-recognition/template.png) During the setup process, click the checkbox to generate an Appwrite API key on completion and add the **Hugging Face access token** in the **Variables** step. If you are self-hosting Appwrite, click the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/ai-hugging-face-speech-recognition/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. #### Step 3: Test the Function Once all the steps are complete, it is time to test the function! Use the Appwrite console or one of Appwrite’s SDKs to [upload an audio file](https://appwrite.io/docs/references/cloud/client-web/storage#createFile) to the `speech_recognition` storage bucket. If successful, you will find a response saved in the `speech_recognition` collection in the `ai` database in the following format: | audio | speech | | --- | --- | | 66a7b386000a1042305c | my thought i have nobody by a beauty and will as you've poured mr rochester is sub and that so don't find simpus and devoted about to at might in a | The `audio` attribute contains the ID of the audio file uploaded to the `speech_recognition` storage bucket, and the `speech` attribute contains the response from Hugging Face. ### Read more about Hugging Face and Appwrite Functions If you would like to learn more about Hugging Face and Appwrite Functions, we have some resources that you should visit: - [Sign up for Hugging Face](https://huggingface.co/login) - [Set up the Hugging Face provider in Appwrite](https://appwrite.io/docs/products/ai/tutorials/object-detection) - [Find more function templates](https://appwrite.io/docs/products/functions/templates) - [Appwrite Functions API reference](https://appwrite.io/docs/references) --- ## Prompt ChatGPT https://appwrite.io/integrations/ai-openai The OpenAI API allows you to integrate advanced AI models into your app. With the API, you can access powerful language models like GPT-3.5, GPT-4, and GPT-4o, which can perform a variety of tasks, including text generation, translation, summarization, and even coding assistance. The API is designed to be user-friendly, providing endpoints that can be easily accessed through HTTP requests. OpenAI also provides detailed documentation and examples to help users get started and make the most of the API. ### How does the integration work? Appwrite integrates OpenAI’s API to provide you with access to powerful text generation out-of-the-box with prospects to implement other features such as image creation and language translations. ### How to implement To implement the OpenAI, there are several steps you must complete: #### Step 1: Create an Open AI account First, [create an account on the OpenAI platform](https://platform.openai.com/) and set up your default project. Visit the [**API keys**](https://platform.openai.com/api-keys) page to create your secret key. Save this key for later usage. ![API key](/images/integrations/ai-openai/api-key.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In case you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click on the **Templates** tab, and search for the **ChatGPT** function template. ![Function template](/images/integrations/ai-openai/template.png) Add the **OpenAI API key** you saved in the **Variables** step. Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. ![Env variables](/images/integrations/ai-openai/variables.png) #### Step 3: Test the function Once the function is ready, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. You can open this URL in your browser to test with our pre-built interface. ![Demo](/images/integrations/ai-openai/demo.png) You can also use tools like Postman, cURL, or your favorite programming language to send a POST (HTTP) request with a prompt and receive a response from the Perplexity API. ```bash curl -X POST http://DEPLOYED_FUNCTION_DOMAIN \ -H "Content-Type: application/json" \ -d '{"prompt": "What is Appwrite?"}' ``` ### Read more about OpenAI and Appwrite Functions If you would like to learn more about Open AI, visit these resources: - [Create an OpenAI developer account](https://platform.openai.com/docs/quickstart) - [Build an intelligent chatbot with ChatGPT and Appwrite Functions](https://appwrite.io/blog/post/function-template-prompt-chatgpt) - [Integrating OpenAI - Appwrite Docs](https://appwrite.io/docs/products/ai/integrations/openai) - [Appwrite Functions docs](https://appwrite.io/docs/products/functions) --- ## Prompt Perplexity https://appwrite.io/integrations/ai-perplexity Perplexity is an advanced AI tool that generates high-quality text based on given prompts. It's designed to improve applications by providing sophisticated text analysis and generation capabilities. ### How does the integration work? You can utilize a pre-built Appwrite function template to create a chatbot using Perplexity. This will allow you to send text prompts to the Perplexity API and receive generated text responses, enriching your app's functionality. ### How to implement To implement the Perplexity integration, follow these simple steps: #### Step 1: Create a Perplexity account First, [create an account on Perplexity](https://perplexity.ai), head to the [API Settings](https://www.perplexity.ai/settings/api), configure your credit card for payments, and generate an API key. Save this API key for further usage. ![API key](/images/integrations/ai-perplexity/api-key.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In case you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click on the **Templates** tab, and search for the **Perplexity** function template. ![Function template](/images/integrations/ai-perplexity/template.png) Add the **Perplexity API key** you saved in the **Variables** step. Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. ![Env variables](/images/integrations/ai-perplexity/variables.png) #### Step 3: Test the function Once the function is ready, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. You can open this URL in your browser to test with our pre-built interface. ![Demo](/images/integrations/ai-perplexity/demo.png) You can also use tools like Postman, cURL, or your favorite programming language to send a POST (HTTP) request with a prompt and receive a response from the Perplexity API. ```bash curl -X POST http://DEPLOYED_FUNCTION_DOMAIN \ -H "Content-Type: application/json" \ -d '{"prompt": "What is Appwrite?"}' ``` ### Read more about Perplexity and Appwrite Functions If you would like to learn more about Perplexity and Appwrite Functions, we have some resources that you should visit: - [Integrating Perplexity - Appwrite Docs](https://appwrite.io/docs/products/ai/integrations/perplexity) - [More on Appwrite AI integrations](https://appwrite.io/blog/post/announcing-appwrite-new-ai-integrations) - [Appwrite Functions docs](https://appwrite.io/docs/products/functions) --- ## Deployments with GitHub https://appwrite.io/integrations/deployments-github GitHub is a web-based platform that facilitates version control and collaborative software development using Git. It enables developers to host, review code, manage projects, and work together on software development. GitHub offers tools for issue tracking, project management, and documentation, enhancing team collaboration. It hosts millions of open-source projects from developers across the world. ### How does the integration work? Appwrite supports a Git integration to enable the generation of repositories for function templates and manage automatic deployments for any Appwrite Functions through GitHub. > Note: For the automatic Git deployment to work, Appwrite needs to receive communication from GitHub, this means your Appwrite project must be accessible on the internet. If you're running on localhost, you need to run a proxy like [ngrok](https://ngrok.com/). > ### How to implement To implement the GitHub integration for Appwrite Functions, there are several steps you must complete: #### Step 1: Create a GitHub App Visit the [Developer Settings on GitHub](https://github.com/settings/apps) to create a new GitHub app. ![Create new GitHub app](/images/integrations/deployments-github/create.png) Add a GitHub name (keep in mind that this will be publicly displayed), a relevant description, and a homepage URL for your application. Then, add the following URLs: | Type of URL | URL | | --- | --- | | Callback URL | `https://[HOSTNAME_OR_IP]/v1/vcs/github/callback` | | Callback URL | `https://[HOSTNAME_OR_IP]/v1/account/sessions/oauth2/callback/github/console` | | Webhook URL | `https://[HOSTNAME_OR_IP]/v1/vcs/github/events` | Under the **Callback URLs** section, check the **Request user authentication (OAuth)** during **installation** box. After that, request the following permissions: {% tabs %} {% tabsitem #repository title="Repository permissions" %} | Permission | Access | | --- | --- | | Administration | Read and write | | Checks | Read and write | | Commit Statuses | Read and write | | Contents | Read and write | | Issues | Read and write | | Metadata | Read-only | | Pull Requests | Read and write | | Webhooks | Read and write | {% /tabsitem %} {% tabsitem #account title="Account permissions" %} | Permission | Access | | --- | --- | | Email address | Read-only | {% /tabsitem %} {% /tabs %} Under the **Subscribe to events** section, check the **Pull request** and **Push** boxes. Lastly, allow **Any account** to install the GitHub app. Once the app is created, create a new **client secret** and **private key**, and configure a **webhook secret**. #### Step 2: Add GitHub provider to the Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. Visit the `.env` file created for your Appwrite instance and update the following environment variables: | Variable | Description | | --- | --- | | _APP_DOMAIN | Your main Appwrite domain used to access the Appwrite Console. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. If you're using a proxy for localhost development, such as [ngrok](https://ngrok.com/), this will be the domain of your localhost proxy. | | _APP_DOMAIN_TARGET | A hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite _APP_DOMAIN variable. If you're using a proxy for localhost development, such as [ngrok](https://ngrok.com/), this will be the domain of your localhost proxy, such as `dd65-2405-201-4013-d8d7-b4c5-fb73-39f9-285c.ngrok.io`. | | _APP_DOMAIN_FUNCTIONS | This will be used for system-generated [function domains](https://appwrite.io/docs/products/functions/domains). When a function domain is generated, it will be `[UNIQUE_ID].[_APP_DOMAIN_FUNCTIONS]`. If `_APP_DOMAIN_FUNCTIONS` is set to example.com for example, the generated domain for functions will be something like `64d4d22db370ae41a32e.example.com`. You can use the same value as used for the Appwrite `_APP_DOMAIN` variable. | | _APP_VCS_GITHUB_APP_NAME | Name of your GitHub app. This is visible as the slug in your GitHub app's public link. For example, if the public link of your GitHub app is https://github.com/apps/test-appwrite-integration, then the app name will be test-appwrite-integration. | | _APP_VCS_GITHUB_PRIVATE_KEY | RSA private key from GitHub wrapped with double quotes and newlines replaced with `\n`. | | _APP_VCS_GITHUB_APP_ID | GitHub application ID from your GitHub app. | | _APP_VCS_GITHUB_CLIENT_ID | GitHub client ID from your GitHub app. | | _APP_VCS_GITHUB_CLIENT_SECRET | GitHub client secret from your GitHub app. | | _APP_VCS_GITHUB_WEBHOOK_SECRET | GitHub webhook secret from your GitHub app. | This is how the fields would look like in the `.env` file: ```bash _APP_DOMAIN=appwrite.example.com _APP_DOMAIN_TARGET=appwrite.example.com _APP_DOMAIN_FUNCTIONS=functions.example.com _APP_VCS_GITHUB_APP_NAME=my-github-app _APP_VCS_GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAuT8f3lo/X83hfvb0ZN/KD2pl86o/jl3ywKrkj/PQZBmtEv/z\nIugE//sfFoHWc4cizkcji+n3FNU+GEdvMioKuJlPBqPTY8hAbVn7R0geZLpDV/rs\n[...]\n-----END RSA PRIVATE KEY-----" _APP_VCS_GITHUB_APP_ID=12415 _APP_VCS_GITHUB_CLIENT_ID=Iv1.35asdf43asd _APP_VCS_GITHUB_CLIENT_SECRET=35rsdse532q13 _APP_VCS_GITHUB_WEBHOOK_SECRET=super-secret ``` After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider To test the provider, go to the **Settings** page in your Appwrite project, navigate to the **Git configuration** section, and add an installation. On successful installation, you should be able to find your app installation in the same section. ![Installed GitHub app](/images/integrations/deployments-github/installed.png) ### Read more about GitHub and Appwrite Functions If you would like to learn more about GitHub, we have some resources that you should visit: - [Create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) - [GitHub setup docs for Appwrite Functions (self-hosted)](https://appwrite.io/docs/advanced/self-hosting/functions) - [Appwrite Functions API reference](https://appwrite.io/docs/references/cloud/server-nodejs/functions) --- ## Email with SendGrid https://appwrite.io/integrations/email-sendgrid SendGrid is a cloud-based service that provides email delivery and marketing solutions. It helps businesses manage email communications, including transactional emails (like password resets and order confirmations) and marketing emails (newsletters and promotions). ### How does the integration work? You can use the SendGrid provider in Appwrite Messaging to send customized emails to your users for various purposes such as reminders, promotions, announcements, and even custom authentication flows. They can be sent immediately or scheduled for later. ### How to implement To implement the SendGrid provider in Appwrite Messaging, there are several steps you must complete: #### Step 1: Sign up on Sendgrid First, you must [sign up for a SendGrid account](https://signup.sendgrid.com/) and finish your account setup. ![SendGrid welcome guide](/images/integrations/email-sendgrid/welcome-guide.png) Next, [create an API key](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/api-keys) with full access and save the key for further usage. After, follow SendGrid's [Domain Authentication](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/how-to-set-up-domain-authentication) process to authenticate your domain name. If you don’t own a domain, you can follow their [Single Sender Verification](https://www.twilio.com/docs/sendgrid/ui/sending-email/sender-verification) process to verify your Sender identity. #### Step 2: Add SendGrid provider to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Messaging** page, click on the **Providers** tab, and **create a new Email provider**. ![Setup email provider](/images/integrations/email-sendgrid/provider.png) Fill in the following fields: | Field name | | | --- | --- | | API key | The full access API key you saved from the SendGrid dashboard. | | Sender email | The provider sends emails from this sender email. The sender email must either be an email under an authenticated domain or a verified sender identity. | | Sender name | The sender name that appears in the emails sent from this provider. | | Reply-to email | The reply-to email that appears in the emails sent from this provider. The reply-to email must either be an email under an authenticated domain or a verified sender identity. | | Reply-to name | The reply-to name that appears in the emails sent from this provider. | #### Step 3: Test the SendGrid provider Before you proceed, you must already have at least one [topic](https://appwrite.io/docs/products/messaging/topics) or [target](https://appwrite.io/docs/products/messaging/targets) set up. Once the provider is set up, you can go to the **Messages** tab on the **Messaging** page and **create an email message**. You can add the test message, configure the users to send the message to, and pick when the message should be sent out. ![Create email](/images/integrations/email-sendgrid/email.png) ### Read more about Sendgrid and Appwrite Messaging If you would like to learn more about Sendgrid and Appwrite Messaging, we have some resources that you should visit: - [Sign up for SendGrid](https://signup.sendgrid.com/) - [Set up the SendGrid provider in Appwrite Messaging](https://appwrite.io/docs/products/messaging/sendgrid) - [Send email messages through Appwrite Messaging](https://appwrite.io/docs/products/messaging/send-email-messages) - [Appwrite Messaging API reference](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) --- ## Auth Kit for FlutterFlow https://appwrite.io/integrations/flutterflow-auth-kit FlutterFlow is a powerful visual builder that lets you create beautiful Flutter apps without writing much code. It's perfect for both beginners and experienced developers who want to build apps quickly. With its drag-and-drop interface and pre-built components, you can focus on creating great user experiences. ### What you’ll build As you start creating your FlutterFlow app, you might want to add user accounts for things like social media features or task management. To help you with this, we've made it super easy to implement secure authentication using our official [Authentication library](https://marketplace.flutterflow.io/item/h1gn6StcXy6imjg7Ykr2). This guide will walk you through everything you need to know to get up and running with Appwrite authentication in your FlutterFlow project. By the end of this guide, you’ll have a fully functional authentication system with: - Email/password sign-up and login. - Secure session management. - User profile handling. - Error handling with user feedback. - Protected routes for authenticated users. Want to see the final result first? Check out our [Appwrite Flutterflow Demo App](https://app.flutterflow.io/project/appwrite-auth-yxmd9b) to experience all these features in action! Then follow along to build your own custom version. ![Welcome screen](/images/integrations/flutterflow-auth-kit/welcomescreen.png) ### Before you start Here’s what you’ll need to have ready: - [Appwrite account](https://cloud.appwrite.io/console/register). - [FlutterFlow account](https://app.flutterflow.io/create-account). - [FlutterFlow Marketplace account](https://marketplace.flutterflow.io/login) (must use the same email as your FlutterFlow account). - Basic understanding of FlutterFlow’s interface. #### Step 1: Setting up your Appwrite project Let’s start by setting up your Appwrite project (you can use a new or an existing one): 1. Head over to the [Appwrite Console](https://cloud.appwrite.io/). 2. Select your project (or create a new one). 3. Under **Add a platform**, add a **Flutter** platform. 4. In the platform setup, choose the platforms you want to support under Flutter App - Web, Android, and/or iOS - and enter the required values as shown below: | Platform | Required Info | |----------|----------------| | Web | Hostnames: `localhost`, `*.web.app`, `*.run.app` (Used for FlutterFlow Web Testing) | | Android | Package Name (e.g. `com.company.appname`) | | iOS | Bundle ID (e.g. `com.company.appname`) | 5. Go to **Settings > Overview** to copy the following values: - **API Endpoint** - **Project ID** 📌 You’ll need these in the next step to configure the authentication library. #### Step 2: Adding the authentication library The Authentication library provides essential core functionality: - Pre-configured Custom Actions for authentication. - App States for session management. - Built-in Error Handling. - User Session Management. - Custom Data Types for type-safe responses. ##### Here’s how to add it to your project: 1. Visit the [FlutterFlow Marketplace](https://marketplace.flutterflow.io/). 2. Search for “Appwrite Authentication Kit” or visit [Appwrite Authentication Kit](https://marketplace.flutterflow.io/item/h1gn6StcXy6imjg7Ykr2). 3. Click “Add” to add it to your marketplace account. 4. In your FlutterFlow project: - Navigate to `Project Settings > Project Dependencies`. - Find “Appwrite Authentication Kit” under FlutterFlow Libraries. - Click “Add Library”. ![Screenshot of login](/images/integrations/flutterflow-auth-kit/screenshot.png) ##### Configure the Authentication Library Once the library is added, you can configure it without writing any code: 1. Navigate to `Settings > Project Dependencies` in your FlutterFlow project. 2. Under the **Appwrite Authentication Kit** entry, click on **View Details**. 3. Update the environment values directly in the panel that opens: - API Endpoint: `Your Appwrite Project Endpoint` - Project ID: `Your Appwrite Project ID` Note: This view opens automatically the first time a user adds the library - making it super easy to update values right away. #### Step 3: Understanding the components The library sets up two essential app states: - `appwriteConfig`: Stores configuration details securely. - `appwriteUser`: Manages user session information. Both states are automatically configured with: - String data type. - Persistence enabled. - Authentication flow readiness. ##### Custom Data Types: The library provides two custom data types for type-safe responses: 1. `AppwriteUser`: Represents user data. - Fields: id, email, name, emailVerified, status. - Used for: Storing and passing user information. 2. `AppwriteUserResponse`: Standard response format. - Fields: success, error, errorCode, errorType, formattedError, user. - Used for: Consistent error handling and success responses. ##### Authentication actions: The library provides five essential custom actions: 1. `initialize` - Purpose: Sets up your Appwrite configuration. - Returns: `AppwriteUserResponse` with initialization status. - Must be called before any other authentication action. 2. `signUpWithEmailAndPassword` - Parameters: email, password. - Returns: `AppwriteUserResponse` with user data. - Handles: Account creation and session setup. 3. `signInWithEmail` - Parameters: email, password. - Returns: `AppwriteUserResponse` with session data. - Manages: User login process. 4. `signOut` - No parameters required. - Returns: `AppwriteUserResponse` with success status. - Handles: Complete session cleanup. 5. `getCurrentUser` - No parameters required. - Returns: `AppwriteUserResponse` with current user data. - Perfect for: Authentication state checks. ##### Understanding action flows: Each authentication action follows this consistent pattern: 1. Action execution. 2. Response handling using `AppwriteUserResponse`. 3. Success/failure conditions based on `response.success`. 4. Error handling using `response.formattedError`. ![Flow example](/images/integrations/flutterflow-auth-kit/flow.png) ##### Example: Sign Up flow 1. Use the `signUpWithEmailAndPassword` action. 2. Check `signUpResult.success`. 3. Success? → Dashboard. 4. Failure? → Show `signUpResult.formattedError`. You can use `response.error` for internal logging and `response.formattedError` to display user-friendly messages. Similarly, you can implement flow patterns for other authentication actions (`signInWithEmail`, `signOut`, `getCurrentUser`), following the similar structure of checking results and handling success/failure scenarios appropriately. #### See it in action Check out our [Appwrite FlutterFlow Demo App](https://app.flutterflow.io/project/appwrite-auth-yxmd9b) to explore the complete auth flow in action - from sign-up to session management. #### Troubleshooting common issues 1. **Library Not Found** - Verify marketplace account email matches FlutterFlow account. - Check if library is properly added in Project Dependencies. 2. **Authentication Failures** - Confirm `initialize` action is called first. - Verify endpoint and projectId values. - Check Appwrite console for platform settings. 3. **Session Management Issues** - Ensure app states are properly configured. - Verify persistence settings. This completes our guide on setting up Appwrite authentication in your FlutterFlow app. You now have a solid foundation for managing user accounts and secure sessions. Now that you have the basics in place, you can enhance your app by implementing more advanced authentication features using Appwrite. If you run into any issues or have questions, the [Appwrite community on Discord](https://appwrite.io/discord) and the [FlutterFlow Community](https://community.flutterflow.io/) are always ready to help. Don't hesitate to reach out! #### More resources If you would like to learn more about Appwrite and FlutterFlow, we have some resources that you should visit: - [Appwrite Flutterflow Demo App](https://app.flutterflow.io/project/appwrite-auth-yxmd9b) - [Appwrite Documentation](https://appwrite.io/docs) - [FlutterFlow Marketplace](https://marketplace.flutterflow.io/item/h1gn6StcXy6imjg7Ykr2) --- ## Payments with Lemon Squeezy https://appwrite.io/integrations/lemon-squeezy-payments Lemon Squeezy is a platform designed to simplify the process of selling digital products online. Their product focuses on providing an all-in-one solution for creators to manage sales, subscriptions, and digital delivery. It includes features such as easy product creation, automated tax handling, customizable checkout pages, and robust analytics. This comprehensive tool is tailored for digital entrepreneurs, allowing them to focus on creating content while Lemon Squeezy handles the technical aspects of online sales. ### How does the integration work? You can utilize a pre-built Appwrite function template to enable payments via Lemon Squeezy in your app. You can then use this to sell software, send PDF books, give access to video courses, or any other digital services your business needs. ### How to implement To implement the Lemon Squeezy payments integration, there are several steps you must complete: #### Step 1: Setup Lemon Squeezy First, [sign up on Lemon Squeezy](https://www.lemonsqueezy.com/) and add a new store. ![Lemon Squeezy dashboard](/images/integrations/lemon-squeezy-payments/dashboard.png) From your Lemon Squeezy dashboard, save the following details for further usage: | Field name | | | --- | --- | | Variant ID | Head to **Store** > **Products** from the left sidebar. Create a new product with the pricing type as **Single payment** and add all other mandatory details such as name, price, and tax category. Click on the product dropdown menu (…) to get the variant ID. | | Store ID | Head to **Settings** > **Stores** from the left sidebar. The store ID will be a number visible next to your store’s URL. | | API key | Head to **Settings** > **API** from the left sidebar. Create a new API key. | | Webhook secret | Head to **Settings** > **Webhooks** from the left sidebar. Add a temporary endpoint `https://temporary-endpoint` (we will replace this with our final endpoint later), create a new webhook secret, and select the event `order_created`. | #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Payments with Lemon Squeezy** function template. ![Function template](/images/integrations/lemon-squeezy-payments/template.png) During the setup process, click the checkbox to generate an Appwrite API key on completion and add the **Lemon Squeezy API key**, **webhook secret**, **store ID**, and **variant ID** in the **Variables** step. If you are self-hosting Appwrite, click on the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/lemon-squeezy-payments/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. Once the function is deployed, go to the **Domains** tab on the **Function** page, copy the domain, and update it in your **Webhooks settings** in the following format: ```bash https://DEPLOYED_FUNCTION_DOMAIN/webhook ``` Additionally, edit the product you created and add your function URL as the **Button link** under the **Confirmation modal** section. #### Step 3: Test the function Once all the steps are complete, it is time to test the function! First, copy the function URL, go to the **Appwrite project dashboard**, and add it to the **Platforms** section as a web app. This will register your function URL as an authorized hostname to interact with Appwrite (to prevent CORS). ![Add web platform](/images/integrations/lemon-squeezy-payments/web-platform.png) Then, open it in your browser to test the function and access the pre-built interactive UI. You can anonymously log in through this UI and create a payment order using [Lemon Squeezy's test card number](https://docs.lemonsqueezy.com/help/getting-started/test-mode#test-card-numbers). As soon as this process is completed, the function UI will show you the complete order. ![Demo](/images/integrations/lemon-squeezy-payments/demo.png) You can visit the **Databases** page in your Appwrite project, enter the `orders` database and the `orders` collection within that, and find your orders. ![Paid orders in database](/images/integrations/lemon-squeezy-payments/database.png) ### Read more about Lemon Squeezy Payments and Appwrite Functions If you would like to learn more about Lemon Squeezy Payments and Appwrite Functions, we have some resources that you should visit: - [Sign up for Lemon Squeezy](https://lemonsqueezy.com) - [Add a new product to your Lemon Squeezy store](https://docs.lemonsqueezy.com/help/products/adding-products) - [Learn more about Functions templates in Appwrite docs](https://appwrite.io/docs/products/functions/templates) --- ## Subscriptions with Lemon Squeezy https://appwrite.io/integrations/lemon-squeezy-subscriptions Lemon Squeezy is a platform designed to simplify the process of selling digital products online. Their product focuses on providing an all-in-one solution for creators to manage sales, subscriptions, and digital delivery. It includes features such as easy product creation, automated tax handling, customizable checkout pages, and robust analytics. This comprehensive tool is tailored for digital entrepreneurs, allowing them to focus on creating content while Lemon Squeezy handles the technical aspects of online sales. ### How does the integration work? You can utilize a pre-built Appwrite function template to enable subscriptions via Lemon Squeezy in your app. This will allow you to accept recurring payments from your customers and grant them extra permissions. ### How to implement To implement the Lemon Squeezy subscriptions integration, there are several steps you must complete: #### Step 1: Setup Lemon Squeezy First, [sign up on Lemon Squeezy](https://www.lemonsqueezy.com/) and add a new store. ![Lemon Squeezy dashboard](/images/integrations/lemon-squeezy-subscriptions/dashboard.png) From your Lemon Squeezy dashboard, save the following details for further usage: | Field name | | | --- | --- | | Variant ID | Head to **Store** > **Products** from the left sidebar. Create a new product with the pricing type as **Subscription** and add all other mandatory details such as name, price, payment schedule, and tax category. Click on the product dropdown menu (…) to get the variant ID. | | Store ID | Head to **Settings** > **Stores** from the left sidebar. The store ID will be a number visible next to your store’s URL. | | API key | Head to **Settings** > **API** from the left sidebar. Create a new API key. | | Webhook secret | Head to **Settings** > **Webhooks** from the left sidebar. Add a temporary endpoint `https://temporary-endpoint` (we will replace this with our final endpoint later), create a new webhook secret, and select the events `subscription_created` and `subscription_expired`. | #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Subscriptions with Lemon Squeezy** function template. ![Function template](/images/integrations/lemon-squeezy-subscriptions/template.png) During the setup process, click the checkbox to generate an Appwrite API key on completion and add the **Lemon Squeezy API key**, **webhook secret**, **store ID**, and **variant ID** in the **Variables** step. If you are self-hosting Appwrite, click on the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/lemon-squeezy-subscriptions/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. Once the function is deployed, go to the **Domains** tab on the **Function** page, copy the domain, and update it in your **Webhooks settings** in the following format: ```bash https://DEPLOYED_FUNCTION_DOMAIN/webhook ``` Additionally, edit the product you created and add your function URL as the **Button link** under the **Confirmation modal** section. #### Step 3: Test the function Once all the steps are complete, it is time to test the function! First, copy the function URL, go to the **Appwrite project dashboard**, and add it to the **Platforms** section as a web app. This will register your function URL as an authorized hostname to interact with Appwrite (to prevent CORS). ![Add web platform](/images/integrations/lemon-squeezy-subscriptions/web-platform.png) Then, open it in your browser to test the function and access the pre-built interactive UI. You can anonymously log in through this UI and create a subscription using [Lemon Squeezy's test card number](https://docs.lemonsqueezy.com/help/getting-started/test-mode#test-card-numbers). As soon as this process is complete, the function UI will show that you are subscribed. ![Demo](/images/integrations/lemon-squeezy-subscriptions/demo.png) Additionally, your user on Appwrite will feature the `subscriber` label, which you can verify by visiting the **Auth** page in your Appwrite project and clicking on the user. ![Subscribed user](/images/integrations/lemon-squeezy-subscriptions/user.png) ### Read more about Lemon Squeezy Subscriptions and Appwrite Functions If you would like to learn more about Lemon Squeezy Subscriptions and Appwrite Functions, we have some resources that you should visit: - [Sign up for Lemon Squeezy](https://lemonsqueezy.com) - [Add a new product to your Lemon Squeezy store](https://docs.lemonsqueezy.com/help/products/adding-products) - [Learn more about Functions templates in Appwrite docs](https://appwrite.io/docs/products/functions/templates) --- ## Logging with AppSignal https://appwrite.io/integrations/logging-appsignal AppSignal is a comprehensive monitoring and error-tracking tool designed for developers to gain insights into the performance of their applications. It provides detailed metrics on application performance, error tracking, and server health, enabling developers to diagnose and resolve issues efficiently. AppSignal supports various programming languages and frameworks, offering seamless integration and an intuitive interface. It helps teams monitor application health in real time, receive alerts on critical issues, and analyze performance data to optimize their code and infrastructure, ultimately ensuring a smooth user experience and maintaining high application reliability. ### How does the integration work? If you are using self-hosting Appwrite, you can use the AppSignal provider for error-tracking and logging for your Appwrite instance. ### How to implement To implement the AppSignal provider for logging, there are several steps you must complete: #### Step 1: Sign up for AppSignal First, [sign up on AppSignal](https://appsignal.com/users/sign_up) and create your first organization (or join an existing one). Head to the **App settings** from the left sidebar and click on **Push & deploy**. Copy the **app-specific Push API key** for further usage. ![API key](/images/integrations/logging-appsignal/api-key.png) #### Step 2: Add AppSignal provider to your Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. The Appwrite logging configuration uses a DSN format that follows the pattern `appsignal://API_KEY/`. Use your AppSignal Push API key as the API_KEY in the DSN. Visit the `.env` file created for your Appwrite instance and update the following environment variable: ```bash _APP_LOGGING_CONFIG=appsignal://API_KEY/ ``` {% info title="Using Appwrite < 1.5.6?" %} For versions prior to 1.5.6, use the old syntax format: ```bash _APP_LOGGING_PROVIDER=appSignal _APP_LOGGING_CONFIG=APPSIGNAL_API_KEY ``` {% /info %} After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider Once the AppSignal provider is configured, run the following command in your terminal: ```bash docker compose exec appwrite ssl --domain="wrongdomain.com" ``` Doing so will show errors in your AppSignal **Errors** page. ![Errors list](/images/integrations/logging-appsignal/errors-list.png) You can also view the error summary for further details. ![Error](/images/integrations/logging-appsignal/error.png) ### Read more about AppSignal and Appwrite If you would like to learn more about AppSignal and Appwrite, we have some resources that you should visit: - [Sign up for AppSignal](https://appsignal.com/users/sign_up) - [Handling errors in Appwrite self-hosted instances](https://appwrite.io/docs/advanced/self-hosting/production#errors) --- ## Logging with Raygun https://appwrite.io/integrations/logging-raygun Raygun is an application performance monitoring (APM) and error tracking tool designed for software developers to identify and resolve issues in their applications quickly. It provides real-time insights into software performance, error diagnostics, and user experience, helping teams to detect, diagnose, and fix errors faster. With features like crash reporting, real user monitoring, and deployment tracking, Raygun helps ensure the stability and efficiency of applications, making it an essential tool for maintaining high-quality software and delivering a seamless user experience. ### How does the integration work? If you are using self-hosting Appwrite, you can use the Raygun provider for error-tracking and logging for your Appwrite instance. ### How to implement To implement the Raygun provider for logging, there are several steps you must complete: #### Step 1: Sign up for Raygun First, [sign up on Raygun](https://app.raygun.com/signup?product=cr) (pick **Crash Reporting** as the product you're most interested in) and create your first application. Head to **Application settings** from the left-side navbar and copy your **API Key**. ![API key](/images/integrations/logging-raygun/api-key.png) #### Step 2: Add Raygun provider to your Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. The Appwrite logging configuration uses a DSN format that follows the pattern `raygun://API_KEY/`. Use your Raygun API key in the DSN. Visit the `.env` file created for your Appwrite instance and update the following environment variable: ```bash _APP_LOGGING_CONFIG=raygun://API_KEY/ ``` {% info title="Using Appwrite < 1.5.6?" %} For versions prior to 1.5.6, use the old syntax format: ```bash _APP_LOGGING_PROVIDER=raygun _APP_LOGGING_CONFIG=RAYGUN_API_KEY ``` {% /info %} After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider Once the Raygun provider is configured, run the following command in your terminal: ```bash docker compose exec appwrite ssl --domain="wrongdomain.com" ``` Doing so will show errors in your Raygun Crash Reporting dashboard. ![Raygun Crash Reporting](/images/integrations/logging-raygun/crash-reporting.png) You can also view the error summary for further details. ![Error](/images/integrations/logging-raygun/error.png) ### Read more about Raygun and Appwrite If you would like to learn more about Raygun and Appwrite, we have some resources that you should visit: - [Sign up for Raygun](https://app.raygun.com/signup?product=cr) - [Handling errors in Appwrite self-hosted instances](https://appwrite.io/docs/advanced/self-hosting/production#errors) --- ## Logging with Sentry https://appwrite.io/integrations/logging-sentry Sentry is an open-source error-tracking and performance-monitoring tool designed to help developers identify, diagnose, and fix issues in real-time. It provides comprehensive insights into application health by capturing and analyzing error events, performance metrics, and user feedback. Sentry supports multiple programming languages and frameworks, making it versatile for various development environments. With features like stack traces, context data, and alerting, Sentry allows teams to quickly pinpoint the root cause of issues and improve the overall quality and reliability of their software applications. ### How does the integration work? If you are using self-hosting Appwrite, you can use the Sentry provider for error-tracking and logging for your Appwrite instance. ### How to implement To implement the Sentry provider for logging, there are several steps you must complete: #### Step 1: Set up Sentry First, [sign up on Sentry](https://sentry.io/signup/) and create a new project. ![Create project](/images/integrations/logging-sentry/project.png) Head to **Settings** > **Projects** > Select your Sentry project > **Client Keys (DSN)**. Save the DSN for further usage. #### Step 2: Add Sentry provider to your Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. The Appwrite logging configuration uses a DSN format that follows the pattern `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/`. For example, if your Sentry DSN is `https://abcdef@sentry.example.com/1`, then the Appwrite logging configuration will be `sentry://1:abcdef@sentry.example.com/`. Visit the `.env` file created for your Appwrite instance and update the following environment variable: ```bash _APP_LOGGING_CONFIG=sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/ ``` {% info title="Using Appwrite < 1.5.6?" %} For versions prior to 1.5.6, use the old syntax format: ```bash _APP_LOGGING_PROVIDER=sentry _APP_LOGGING_CONFIG=SENTRY_PROJECT_ID;SENTRY_PUBLIC_KEY@SENTRY_HOST ``` {% /info %} After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider Once the Sentry provider is configured, run the following command in your terminal: ```bash docker compose exec appwrite ssl --domain="your-domain.com" ``` Doing so will show errors in your Sentry **Issues** page. ![Issues](/images/integrations/logging-sentry/issues.png) You can also view the error details for more information. ![Issue details](/images/integrations/logging-sentry/issue-details.png) ### Read more about Sentry and Appwrite If you would like to learn more about Sentry and Appwrite, we have some resources that you should visit: - [Sign up for Sentry](https://sentry.io/signup/) - [Handling errors in Appwrite self-hosted instances](https://appwrite.io/docs/advanced/self-hosting/production#errors) --- ## MCP with Claude https://appwrite.io/integrations/mcp-claude Claude Desktop is a standalone application by Anthropic that allows users to interact with the Claude large language model directly from their Mac or Windows desktops. Designed for convenience and speed, it offers a distraction-free chat experience, supports multiple conversations, and runs natively for faster performance and system integration. Whether you're brainstorming, coding, summarizing documents, or automating workflows, Claude Desktop makes it easy to harness AI assistance without relying on a browser. ### How does the integration work? The Appwrite MCP server integrates with Claude Desktop using the Model Context Protocol (MCP), allowing you to connect your Appwrite project to Claude Desktop. This integration enables you to perform various operations on your Appwrite resources, such as creating users, managing databases, and more, directly from Claude Desktop using natural language commands. ### How to implement To implement the MCP with Claude Desktop integration, there are several steps you must complete: #### Step 1: Create an Appwrite API key First, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. Select your project (or create a new one), and head to the **Overview** page from the left sidebar. Under the **Integrations** section, click on the **API Keys** tab, and then click on **Create API Key**. Select the scopes for whichever Appwrite services you want to work with, set the duration, and create the API key. You must then copy it for future usage. ![Create API Key](/images/integrations/mcp-claude/appwrite-api-key.png) Then, head to the **Settings** page from the left sidebar, and copy the **Project ID** and **API Endpoint** for future usage. #### Step 2: Configure the MCP server on Cursor {% info title="Pre-requisite: Install uv" %} You must install [uv](https://docs.astral.sh/uv/getting-started/installation/) on your system to use the MCP server. {% /info %} To configure the MCP server on Claude Desktop, head to the app's **Settings** page (press `CTRL + ,` on Windows or `CMD + ,` on MacOS), navigate to the **Developer** tab, and click on **Edit Config**. This will open the `claude_desktop_config.json` file, where you must add the following: ```json { "mcpServers": { "appwrite": { "command": "uvx", "args": [ "mcp-server-appwrite", "--users" ], "env": { "APPWRITE_API_KEY": "", "APPWRITE_PROJECT_ID": "", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` This configuration will set up the MCP server to connect to your Appwrite project using the API key, project ID, and endpoint, which you must update before saving and exiting the file. You may have noticed the `--users` argument, which enables Claude Desktop to interact with the Appwrite Users API. To enable other Appwrite services, you can add their respective [command-line arguments](/docs/tooling/mcp#command-line-arguments). Once you have updated and saved the `claude_desktop_config.json` file, restart Claude Desktop and click on the MCP tools button (at the bottom right section of the prompt input) to view the available MCP tools. ![Claude MCP Tools](/images/docs/mcp/claude-desktop/claude-mcp-tools.png) {% info title="Claude Code" %} If you are using the Claude Code CLI, you can use the following command in your terminal to configure the MCP server in the exact same manner: ```bash claude mcp add-json appwrite '{"command":"uvx","args":["mcp-server-appwrite","--users"],"env":{"APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1"}}' ``` {% /info %} #### Step 3: Test the integration Finally, you can test the integration by asking Claude Desktop to list all the users in your Appwrite project. ![Claude Desktop](/images/docs/mcp/claude-desktop/claude-list-users.png) ### Read more about MCP with Claude Desktop If you would like to learn more about MCP with Claude Desktop, we have some resources that you should visit: - [Appwrite MCP documentation](/docs/tooling/mcp) - [What exactly is MCP, and why is it trending?](/blog/post/what-is-mcp) - [Download Claude Desktop](https://claude.ai/download) --- ## MCP with Cursor https://appwrite.io/integrations/mcp-cursor Cursor is an AI-powered code editor built on VS Code, designed to enhance developer workflows with intelligent, context-aware assistance. It offers features like inline code generation, natural language explanations, and project-aware chat, enabling developers to prototype, debug, and refactor code faster with the help of integrated large language models such as Anthropic's Claude 4.0 Sonnet, Google's Gemini 2.5 Pro, and OpenAI's GPT-4.1. ### How does the integration work? The Appwrite MCP server integrates with Cursor Agent using the Model Context Protocol (MCP), allowing you to connect your Appwrite project to Cursor. This integration enables you to perform various operations on your Appwrite resources, such as creating users, managing databases, and more, directly from the Cursor editor using natural language commands. ### How to implement To implement the MCP with Cursor integration, there are several steps you must complete: #### Step 1: Create an Appwrite API key First, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. Select your project (or create a new one), and head to the **Overview** page from the left sidebar. Under the **Integrations** section, click on the **API Keys** tab, and then click on **Create API Key**. Select the scopes for whichever Appwrite services you want to work with, set the duration, and create the API key. You must then copy it for future usage. ![Create API Key](/images/integrations/mcp-cursor/appwrite-api-key.png) Then, head to the **Settings** page from the left sidebar, and copy the **Project ID** and **API Endpoint** for future usage. #### Step 2: Configure the MCP server on Cursor {% info title="Pre-requisite: Install uv" %} You must install [uv](https://docs.astral.sh/uv/getting-started/installation/) on your system to use the MCP server. {% /info %} To configure the MCP server on Cursor, head to the **Cursor Settings** page, navigate to the **MCP** tab, and click on **Add new global MCP server**. This will open the `mcp.json` file, where you must add the following: ```json { "mcpServers": { "appwrite": { "command": "uvx", "args": [ "mcp-server-appwrite", "--users" ], "env": { "APPWRITE_API_KEY": "", "APPWRITE_PROJECT_ID": "", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` This configuration will set up the MCP server to connect to your Appwrite project using the API key, project ID, and endpoint, which you must update before saving and exiting the file. You may have noticed the `--users` argument, which enables Cursor to interact with the Appwrite Users API. To enable other Appwrite services, you can add their respective [command-line arguments](/docs/tooling/mcp#command-line-arguments). Once you have updated and saved the `mcp.json` file, Cursor will connect with the Appwrite MCP server and load all available tools. You may need to restart Cursor if it is unable to start the MCP server. ##### Skip this step, directly add the server to Cursor Once you have understood how the Appwrite MCP server is configured in Cursor, you can skip this step by directly adding the MCP server using the following link. {% only_light %} {% cards %} {% cards_item href="https://apwr.dev/api-mcp-cursor?ref=integrations-catalog" title="Add to Cursor" image="/images/docs/mcp/logos/cursor-ai.svg" %} {% /cards_item %} {% /cards %} {% /only_light %} {% only_dark %} {% cards %} {% cards_item href="https://apwr.dev/api-mcp-cursor?ref=integrations-catalog" title="Add to Cursor" image="/images/docs/mcp/logos/dark/cursor-ai.svg" %} {% /cards_item %} {% /cards %} {% /only_dark %} This will automatically configure the MCP server, and you will only need to update your Appwrite API key, project ID, and endpoint. #### Step 3: Test the integration Finally, you can test the integration by asking Cursor Agent to create a new user in your Appwrite project. ![Cursor Agent](/images/docs/mcp/cursor/cursor-create-user.png) ### Read more about MCP with Cursor If you would like to learn more about MCP with Cursor, we have some resources that you should visit: - [Appwrite MCP documentation](/docs/tooling/mcp) - [What exactly is MCP, and why is it trending?](/blog/post/what-is-mcp) - [Download Cursor](https://www.cursor.com/) --- ## MCP with Windsurf https://appwrite.io/integrations/mcp-windsurf Windsurf Editor is a next-gen IDE that embeds a powerful AI agent called Cascade directly into your coding workflow. Unlike tools limited to autocomplete or single-file assistance, Cascade understands your entire project context and can generate code across files, run terminal commands, debug issues, and even deploy applications, all triggered by natural language prompts. With features like Supercomplete, inline AI chat, memories, and live previews, Windsurf aims to keep you in a deep focus state by eliminating context switching and handling repetitive tasks automatically, while still giving you full control over what gets merged or executed. ### How does the integration work? The Appwrite MCP server integrates with Windsurf Editor's Cascade chat using the Model Context Protocol (MCP), allowing you to connect your Appwrite project to Windsurf Editor. This integration enables you to perform various operations on your Appwrite resources, such as creating users, managing databases, and more, directly from the Windsurf Editor using natural language commands. ### How to implement To implement the MCP with Windsurf Editor integration, there are several steps you must complete: #### Step 1: Create an Appwrite API key First, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven't already. Select your project (or create a new one), and head to the **Overview** page from the left sidebar. Under the **Integrations** section, click on the **API Keys** tab, and then click on **Create API Key**. Select the scopes for whichever Appwrite services you want to work with, set the duration, and create the API key. You must then copy it for future usage. ![Create API Key](/images/integrations/mcp-windsurf/appwrite-api-key.png) Then, head to the **Settings** page from the left sidebar, and copy the **Project ID** and **API Endpoint** for future usage. #### Step 2: Configure the MCP server on Windsurf Editor {% info title="Pre-requisite: Install uv" %} You must install [uv](https://docs.astral.sh/uv/getting-started/installation/) on your system to use the MCP server. {% /info %} To configure the MCP server on Windsurf Editor, head to the **Windsurf Editor Settings** page, navigate to the **Model Context Protocol (MCP) Servers** section, and click on **View raw config**. This will open the `mcp_config.json` file, where you must add the following: ```json { "mcpServers": { "appwrite": { "command": "uvx", "args": [ "mcp-server-appwrite", "--databases", "--users" ], "env": { "APPWRITE_API_KEY": "", "APPWRITE_PROJECT_ID": "", "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" } } } } ``` This configuration will set up the MCP server to connect to your Appwrite project using the API key, project ID, and endpoint, which you must update before saving and exiting the file. You may have noticed the `--databases` and `--users` arguments, which enables Windsurf Editor to interact with the Appwrite Users and Databases APIs. To enable other Appwrite services, you can add their respective [command-line arguments](/docs/tooling/mcp#command-line-arguments). Once you have updated and saved the `mcp_config.json` file, return to the MCP Servers section in the Windsurf Settings and click on **Refresh**. #### Step 3: Test the integration Finally, you can test the integration by asking the Cascade chat in the Windsurf Editor to query your database. ![Windsurf Editor Cascade Chat](/images/docs/mcp/windsurf/windsurf-cascade-chat.png) ### Read more about MCP with Windsurf Editor If you would like to learn more about MCP with Cursor, we have some resources that you should visit: - [Appwrite MCP documentation](/docs/tooling/mcp) - [What exactly is MCP, and why is it trending?](/blog/post/what-is-mcp) - [Download Windsurf Editor](https://windsurf.com/download) --- ## Native auth with Apple https://appwrite.io/integrations/native-auth-apple An Apple ID is a unique account that allows access to Apple services, including the App Store, iCloud, iTunes, Apple Music, and more. It serves as a key to the Apple ecosystem, allowing users to synchronize their data across Apple devices, download apps, purchase music, and access various features like FaceTime and iMessage. The Apple ID consists of an email address and a password, with options for added security through two-factor authentication. It is essential for managing personal information, subscriptions, and settings across all Apple products. ### How does the integration work? You can use the Appwrite Function template for native authentication on Apple devices. This template provides a seamless way to integrate native Apple sign-in APIs with Appwrite Auth, allowing users to authenticate using their Apple ID directly within your app. ### How to implement To implement the Appwrite Function template for native authentication on Apple devices, there are several steps you must complete: #### Step 1: Create an Apple Developer account First, [sign up for the Apple Developer program](https://developer.apple.com/programs). ![Apple Developer Program](/images/integrations/native-auth-apple/apple-developer-program.png) Before you configure any other details, you must enable the **Sign in with Apple capability** and **set a Bundle ID**. To do this, head to XCode and open your project directory. In the Project Navigator, select your project followed by the correct target. Then, navigate to the **Signing & Capabilities** tab, click the ➕ **Capability** button, search for `Sign in with Apple`, and double-click the result to add it. Then, modify the **Bundle Identifier** to a unique string by prefixing it with any letters of your choice and change the **Team** to your Apple Developer team. Save the **Bundle ID** for further usage. Head to your Apple Developer account and save the following details: | Field name | | | --- | --- | | Team ID | Head to Apple Developer account > Membership details > Team ID | | Encoded authentication key (.p8 file) | Head to Apple Developer account > Program resources > Certificates, Identifiers & Profiles > Keys. Create a key and give it a name. Enable Sign in with Apple and register your key. Download the key, open it in a text editor, use a Base64 encoder to convert the file content to a Base64 string, and copy the result. | | Authentication key ID | Head to Apple Developer account > Program resources > Certificates, Identifiers & Profiles > Keys. Click on your key to view details. | #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Sign in with Apple** function template. ![Function template](/images/integrations/native-auth-apple/template.png) During the setup process, add the **Bundle ID**, **Team ID**, **Authentication key ID**, and **Encoded authentication key** in the **Variables** step.. ![Env variables](/images/integrations/native-auth-apple/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. #### Step 3: Integrate the function in your app First, you must [implement the Sign In with Apple button](https://developer.apple.com/documentation/signinwithapple/displaying-sign-in-with-apple-buttons-in-your-app) in your app. Once implemented, you can handle the successful sign-in event and call the Appwrite Function to create and authenticate the user in Appwrite. Below is an example from [Appwrite's SwiftUI playground](https://github.com/appwrite/playground-for-apple-swiftui/blob/b036fb4313cc976f379ac10160a057ee5ae5c270/PlaygroundForApple/PlaygroundViewModel.swift#L334): ```swift func handleSignInWithAppleSuccess(with authorization: ASAuthorization) async throws { if let userCredential = authorization.credential as? ASAuthorizationAppleIDCredential { print(userCredential.authorizationCode) print(userCredential.user) if userCredential.authorizedScopes.contains(.fullName) { print(userCredential.fullName) } if userCredential.authorizedScopes.contains(.email) { print(userCredential.email) } do { // Create the request body with the authorization code and user information var body: [String: Any] = [ "code": String(data: userCredential.authorizationCode!, encoding: .utf8) ] if userCredential.fullName != nil { if let givenName = userCredential.fullName?.givenName { body["firstName"] = givenName } if let familyName = userCredential.fullName?.familyName { body["lastName"] = familyName } } let jsonData = try JSONSerialization.data(withJSONObject: body, options: []) let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) print(jsonString) // Call the Appwrite Function let functions: Functions = Functions(client); let execution = try await functions.createExecution( functionId: "sign-in-with-apple", body: jsonString, method: ExecutionMethod.pOST, headers: ["Content-Type": "application/json"] ); print(execution.responseStatusCode) if (execution.responseStatusCode != 200) { dialogText = "Error executing sign-in-with-apple" } else { print(execution.responseBody) // Decode the response body let responseBodyData = execution.responseBody.data(using: .utf8) let token = try! JSONDecoder().decode([String : String].self, from: responseBodyData!) let secret = token["secret"] let userId = token["userId"] print(userId) print(secret) // Create a session for the user using Appwrite Auth try await account.createSession(userId: userId!, secret: secret!) try await getAccount() } } catch { dialogText = error.localizedDescription } isShowingDialog = true } } ``` Once implemented, you can test the Sign in with Apple button in your app. ### Read more about Apple and Appwrite If you would like to learn more about Apple and Appwrite, we have some resources that you should visit: - [Sign up for the Apple Developer program](https://developer.apple.com/programs) - [Display Sign In with Apple buttons in your app](https://developer.apple.com/documentation/signinwithapple/displaying-sign-in-with-apple-buttons-in-your-app) - [Appwrite's SwiftUI playground](https://github.com/appwrite/playground-for-apple-swiftui) - [Appwrite's Flutter playground](https://github.com/appwrite/playground-for-flutter) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-apple/account) - [Appwrite Functions API reference](https://appwrite.io/docs/references/cloud/client-apple/functions) --- ## OAuth with Amazon https://appwrite.io/integrations/oauth-amazon Amazon is a global e-commerce platform that offers a wide selection of products, from electronics, books, and apparel to household goods, groceries, and digital media. Amazon offers an OAuth provider to simplify the process of integrating Amazon services into various applications, enhancing the authentication user experience while maintaining robust security standards. ### How does the integration work? You can use the Amazon OAuth adapter in Appwrite Auth for user authentication and management. This can be convenient for users because they can start using your app without creating a new account. It can also be more secure, because the user has one less password that could become vulnerable. ### How to implement To implement the Amazon OAuth adapter in Appwrite Auth, there are several steps you must complete: #### Step 1: Create an Amazon Developer account First, [sign up for an Amazon Developer account](https://www.amazon.com/ap/register?clientContext=130-6830764-7565500&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&siteState=clientContext%3D140-5240481-9393408%2CsourceUrl%3Dhttps%253A%252F%252Fdeveloper.amazon.com%252Fsettings%252Fconsole%252Fregistration%253Freturn_to%253D%252Fapps-and-games%2Csignature%3DR6AIXlj2Bj2Fwc7EBuqfl0CNCjUbh6Ej3D&marketPlaceId=ATVPDKIKX0DER&language=en_US&pageId=amzn_developer_portal&openid.return_to=https%3A%2F%2Fdeveloper.amazon.com%2Fsettings%2Fconsole%2Fregistration%3Freturn_to%3D%252Fapps-and-games&prevRID=93HMKYWW3JS4T3Q5EJ37&openid.assoc_handle=mas_dev_portal&openid.mode=checkid_setup&prepopulatedLoginId=&failedSignInCount=0&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0), head to the [Login With Amazon console](https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html), and create a new security profile with details of your app. ![New security profile](/images/integrations/oauth-amazon/security-profile.png) Save the generated **Client ID** and **Client Secret** for later usage. #### Step 2: Add Amazon OAuth adapter to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Auth** page, open the **Settings** tab, and click on Amazon under the **OAuth2 Providers** section. ![Amazon provider](/images/integrations/oauth-amazon/provider.png) Add the **Client ID** and **Client secret** you saved from the security profile you created. Copy the URI to add to the **Allowed Return URLs** field in the **Web Settings** of the security profile in the Amazon Developer console. ![New security profile](/images/integrations/oauth-amazon/allowed-return-urls.png) #### Step 3: Test the provider. Follow the [OAuth 2 login](https://appwrite.io/docs/products/auth/oauth2#init) flow to test your provider. ### Read more about Amazon and Appwrite Auth If you would like to learn more about Amazon and Appwrite Auth, we have some resources that you should visit: - [Sign up for an Amazon Developer account](https://www.amazon.com/ap/register?clientContext=130-6830764-7565500&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&siteState=clientContext%3D140-5240481-9393408%2CsourceUrl%3Dhttps%253A%252F%252Fdeveloper.amazon.com%252Fsettings%252Fconsole%252Fregistration%253Freturn_to%253D%252Fapps-and-games%2Csignature%3DR6AIXlj2Bj2Fwc7EBuqfl0CNCjUbh6Ej3D&marketPlaceId=ATVPDKIKX0DER&language=en_US&pageId=amzn_developer_portal&openid.return_to=https%3A%2F%2Fdeveloper.amazon.com%2Fsettings%2Fconsole%2Fregistration%3Freturn_to%3D%252Fapps-and-games&prevRID=93HMKYWW3JS4T3Q5EJ37&openid.assoc_handle=mas_dev_portal&openid.mode=checkid_setup&prepopulatedLoginId=&failedSignInCount=0&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0) - [Implement OAuth login in your apps using Appwrite Auth](https://appwrite.io/docs/products/auth/oauth2) - [Understanding OAuth and OpenID Connect](https://appwrite.io/blog/post/oauth-openid) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## OAuth with Apple https://appwrite.io/integrations/oauth-apple An Apple ID is a unique account that allows access to Apple services, including the App Store, iCloud, iTunes, Apple Music, and more. It serves as a key to the Apple ecosystem, allowing users to synchronize their data across Apple devices, download apps, purchase music, and access various features like FaceTime and iMessage. The Apple ID consists of an email address and a password, with options for added security through two-factor authentication. It is essential for managing personal information, subscriptions, and settings across all Apple products. ### How does the integration work? You can use the Apple OAuth adapter in Appwrite Auth for user authentication and management. This can be convenient for users because they can start using your app without creating a new account. It can also be more secure, because the user has one less password that could become vulnerable. ### How to implement To implement the Apple OAuth adapter in Appwrite Auth, there are several steps you must complete: #### Step 1: Create an Apple Developer account First, [sign up for the Apple Developer program](https://developer.apple.com/programs). ![Apple Developer Program](/images/integrations/oauth-apple/apple-developer-program.png) Before you configure any other details, you must create an **App ID** for our app. To do that, go to XCode and open the `ios` folder in your source code. Navigate to the **Signing & Capabilities** tab, click the ➕ **Capability** button, search for `Sign in with Apple`, and double-click the result to add it. Then, modify the **Bundle Identifier** to a unique string by prefixing it with any letters of your choice and change the **Team** to your Apple Developer team. This will automatically create an **App ID** for your app. After that, you will also need to create a **Services ID**. Visit your Apple Developer account, head to **Program resources** > **Certificates, Identifiers & Profiles** > **Identifiers**, and register a new **Service ID**, setting your Bundle ID as the unique identifier. View the service ID, enable **Sign in with Apple**, and in its configuration, select your primary App ID, input `appwrite.io` (or the domain of your self-hosted instance) under **Domains and Subdomains,** and temporarily add the URL `https://temporary-endpoint.com/` under **Return URLs**. This temporary URI will be replaced with the final URI once the OAuth2 adapter is configured on Appwrite. Head to your Apple Developer account and save the following details: | Field name | | | --- | --- | | Services ID Identifier | Head to Apple Developer account > Program resources > Certificates, Identifiers & Profiles > Identifiers and view the Services ID. | | Team ID | Head to Apple Developer account > Membership details > Team ID | | Authentication key (.p8 file) | Head to Apple Developer account > Program resources > Certificates, Identifiers & Profiles > Keys. Create a key and give it a name. Enable Sign in with Apple and register your key. Download the key, open it in a text editor, and copy the content. | | Authentication key ID | Head to Apple Developer account > Program resources > Certificates, Identifiers & Profiles > Keys. Click on your key to view details. | #### Step 2: Add Apple OAuth adapter to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Auth** page, open the **Settings** tab, and click on Apple under the **OAuth2 Providers** section. ![Apple provider](/images/integrations/oauth-apple/provider.png) Add the **Services ID Identifier** (in the Bundle ID field), **Key ID**, **Team ID**, and **P8 File** content that you saved from your Apple Developer account. Copy the URI and add it to the **Return URLs** in the configuration of the Services ID you created in your Apple Developer account. #### Step 3: Test the provider. Follow the [OAuth 2 login](https://appwrite.io/docs/products/auth/oauth2#init) flow to test your provider. ### Read more about Apple and Appwrite Auth If you would like to learn more about Apple and Appwrite Auth, we have some resources that you should visit: - [Sign up for the Apple Developer program](https://developer.apple.com/programs) - [Implement OAuth login in your apps using Appwrite Auth](https://appwrite.io/docs/products/auth/oauth2) - [Understanding OAuth and OpenID Connect](https://appwrite.io/blog/post/oauth-openid) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## OAuth with Discord https://appwrite.io/integrations/oauth-discord Discord is a popular communication platform designed for creating communities. It supports text, voice, and video communication, making it ideal for both casual conversations and organized group activities. Initially popular among gamers, Discord has expanded to various communities, including education, business, and hobbyist groups. ### How does the integration work? You can use the Discord OAuth adapter in Appwrite Auth for user authentication and management. This can be convenient for users because they can start using your app without creating a new account. It can also be more secure, because the user has one less password that could become vulnerable. ### How to implement To implement the Discord OAuth adapter in Appwrite Auth, there are several steps you must complete: #### Step 1: Register a Discord Developer application First, head to the Discord Developer portal and [register an application](https://discord.com/developers/applications). ![New Discord app](/images/integrations/oauth-discord/new-app.png) Head to the **OAuth2** tab under **Settings** and save the **client ID** and **client secret**. Once we create the adapter on Appwrite, we will return to add a redirect URI. #### Step 2: Add Discord OAuth adapter to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Auth** page, open the **Settings** tab, and click on **Discord** under the **OAuth2 Providers** section. ![OAuth2 settings](/images/integrations/oauth-discord/oauth2.png) Add the **client ID** and **client secret** you saved from your Discord app and copy the URI to add to the **Redirect** field in the **OAuth2** settings in your Discord app. ![Discord provider](/images/integrations/oauth-discord/provider.png) #### Step 3: Test the provider. Follow the [OAuth 2 login](https://appwrite.io/docs/products/auth/oauth2#init) flow to test your provider. ### Read more about Discord and Appwrite Auth If you would like to learn more about Discord and Appwrite Auth, we have some resources that you should visit: - [Register a Discord Developer application](https://discord.com/developers/applications) - [Implement OAuth login in your apps using Appwrite Auth](https://appwrite.io/docs/products/auth/oauth2) - [Understanding OAuth and OpenID Connect](https://appwrite.io/blog/post/oauth-openid) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## OAuth with Google https://appwrite.io/integrations/oauth-google Google Workspace, formerly known as G Suite, is a comprehensive suite of cloud-based productivity and collaboration tools developed by Google. It includes popular applications such as Gmail for email, Google Drive for cloud storage, Google Docs for document creation, Google Sheets for spreadsheets, and Google Slides for presentations. Additionally, it offers tools for communication and collaboration, including Google Meet for video conferencing and Google Chat for instant messaging. Google Workspace is designed for businesses, educational institutions, and individuals, providing seamless integration, real-time collaboration, and robust security features to enhance productivity and streamline workflows. ### How does the integration work? You can use the Google OAuth adapter in Appwrite Auth for user authentication and management. This approach is convenient for users, allowing them to start using your app without creating a new account. Additionally, it enhances security by eliminating the need for users to manage yet another password, reducing the risk of potential vulnerabilities. ### How to implement To implement the Google OAuth adapter in Appwrite Auth, there are several steps you must complete: #### Step 1: Create an OAuth 2.0 client ID on Google Cloud First, [log into the Google Cloud Console](https://console.cloud.google.com/), create a new project, click on **APIs and Services** in the left-side navbar, and open the **OAuth consent screen** page. Add all the required details about your app and select the scopes `.../auth/userinfo.email`, `.../auth/userinfo.profile`, and `openid`. ![OAuth consent screen](/images/integrations/oauth-google/consent-screen.png) Next, go to the **Credentials** page under **APIs and Services** in the left-side navbar and create a new **OAuth 2.0 client ID**. Select the **application type** as `Web application,` and in the **authorized redirect URIs** field, temporarily add the URL `https://temporary-endpoint.com/`. This temporary URI will be replaced with the final URI once the OAuth2 adapter is configured on Appwrite. ![OAuth client ID](/images/integrations/oauth-google/client-id.png) Save the generated **Client ID** and **Client secret** for later usage. #### Step 2: Add Google OAuth adapter to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Auth** page, open the **Settings** tab, and click on Google under the **OAuth2 Providers** section. ![Google provider](/images/integrations/oauth-google/provider.png) Add the **Client ID** and **Client secret** you saved from Google Cloud and copy the URI to add to the **authorized redirect URIs** field in the credential you created on Google Cloud. ![OAuth redirect URI](/images/integrations/oauth-google/redirect-uri.png) #### Step 3: Test the provider. Follow the [OAuth 2 login](https://appwrite.io/docs/products/auth/oauth2#init) flow to test your provider. ### Read more about Google and Appwrite Auth If you would like to learn more about Google and Appwrite Auth, we have some resources that you should visit: - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) - [Implement OAuth login in your apps using Appwrite Auth](https://appwrite.io/docs/products/auth/oauth2) - [Understanding OAuth and OpenID Connect](https://appwrite.io/blog/post/oauth-openid) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## OAuth with Notion https://appwrite.io/integrations/oauth-notion Notion is a versatile productivity tool that combines note-taking, task management, and collaboration features into a single application. It allows users to create customizable workspaces where they can organize their projects, documents, and databases. Notion's flexibility comes from its block-based system, which lets users add various types of content, such as text, images, lists, tables, and more, in a modular fashion. This makes it suitable for personal use, team collaboration, and project management. ### How does the integration work? You can use the Notion OAuth adapter in Appwrite Auth for user authentication and management. This can be convenient for users because they can start using your app without creating a new account. It can also be more secure, because the user has one less password that could become vulnerable. ### How to implement To implement the Notion OAuth adapter in Appwrite Auth, there are several steps you must complete: #### Step 1: Create a Notion integration First, head over to Notion and [create a new integration](https://www.notion.so/profile/integrations). Set the integration type as **Public** if you want your integration to be used by any Notion user, not just members of your workspace (otherwise, set it as **Internal**), and add all the mandatory info. In the **Redirect URIs** field, temporarily add the URL `https://temporary-endpoint/`. This temporary URI will be replaced with the final URI once the OAuth2 adapter is configured on Appwrite. ![New Notion integration](/images/integrations/oauth-notion/new-integration.png) From the **Configuration** tab, save the **OAuth Client ID** and **OAuth Client Secret** for later usage. #### Step 2: Add Notion OAuth adapter to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Auth** page, open the **Settings** tab, and click on **Notion** under the **OAuth2 Providers** section. ![Notion provider](/images/integrations/oauth-notion/provider.png) Add the **OAuth Client ID** and **OAuth Client Secret** you saved from your Notion integration and copy the URI to add to the **Redirect URIs** field on the **Basic Information** tab in your Notion integration. ![Redirect URI](/images/integrations/oauth-notion/redirect-uri.png) #### Step 3: Test the provider. Follow the [OAuth 2 login](https://appwrite.io/docs/products/auth/oauth2#init) flow to test your provider. ### Read more about Notion and Appwrite Auth If you would like to learn more about Notion and Appwrite Auth, we have some resources that you should visit: - [Create a Notion integration](https://www.notion.so/profile/integrations) - [Implement OAuth login in your apps using Appwrite Auth](https://appwrite.io/docs/products/auth/oauth2) - [Understanding OAuth and OpenID Connect](https://appwrite.io/blog/post/oauth-openid) - [Appwrite Auth API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## Phone auth with Twilio https://appwrite.io/integrations/phone-auth-twilio Twilio is a cloud communications platform that provides programmable communication tools for making and receiving phone calls, sending and receiving text messages, and performing other communication functions using web APIs and SDKs. ### How does the integration work? If you are using self-hosting Appwrite, you can use the Twilio provider to deliver OTPs (one-time passwords) for phone authentication in Appwrite Auth. This is a great passwordless alternative to standard authentication methods, especially for mobile apps, and has several benefits, such as enhanced security, higher user convenience, reduced password fatigue, and easier fraud prevention. Phone authentication via OTPs is also available on [Appwrite Cloud](https://cloud.appwrite.io) without the need to manually integrate a provider. ### How to implement To implement the Twilio provider for phone authentication, there are several steps you must complete: #### Step 1: Sign up for Twilio First, you must [sign up for a Twilio account](https://www.twilio.com/try-twilio), upgrade your account (in case you want to message numbers other than your own), and purchase a phone number with the SMS capability. ![Twilio console](/images/integrations/phone-auth-twilio/twilio-console.png) Ensure you save the following information for later use: | Field name | | | --- | --- | | Account SID | Head to **Twilio console** > **Account info** > **Account SID**. | | Auth token | Head to **Twilio console** > **Account info** > **Auth Token**. | | Phone number | You can access numbers by navigating to your **Twilio console** > **Develop** > **Phone Numbers** > **Manage** > **Active Numbers**. | #### Step 2: Add Twilio provider to your Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. Visit the `.env` file created for your Appwrite instance and update the following environment variables: ```bash _APP_SMS_PROVIDER=sms://[ACCOUNT_SID]:[AUTH_TOKEN]@twilio _APP_SMS_FROM=[TWILIO_PHONE_NUMBER] ``` After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider To test the provider, you must [implement the phone (SMS) authentication flow](https://appwrite.io/docs/products/auth/phone-sms) in a web or mobile app. On successful login, you should see a user on the **Auth** page of your Appwrite project with their phone number visible as the identifier. ![User](/images/integrations/phone-auth-twilio/user.png) ### Read more about Twilio and Appwrite Auth If you would like to learn more about phone authentication with Twilio and Appwrite Auth, we have some resources that you should visit: - [Sign up for Twilio](https://www.twilio.com/try-twilio) - [SMS delivery docs for Appwrite (self-hosted)](https://appwrite.io/docs/advanced/self-hosting/sms) - [Appwrite phone auth docs](https://appwrite.io/docs/products/auth/phone-sms) - [Appwrite Accounts API reference](https://appwrite.io/docs/references/cloud/client-web/account) --- ## Push notifications with APNs https://appwrite.io/integrations/push-apns Apple Push Notification Service (APNs) is an Apple platform that enables developers to send notifications to iOS devices, macOS computers, and other Apple devices. It allows app developers to send remote notifications, alerting users to new content, messages, updates, and app-specific information. ### How does the integration work? You can use the APNs provider in Appwrite Messaging to send push notifications to your users’ Apple devices. APNs is a best-effort service and will attempt to deliver messages to your device when it's online and available again. APNs will save the last message for 30 days or less and attempt delivery as soon as it's online. ### How to implement To implement the APNs provider in Appwrite Messaging, there are several steps you must complete: #### Step 1: Sign up on Apple Developer First, [sign up for the Apple Developer program](https://developer.apple.com/programs). ![Apple developer program](/images/integrations/push-apns/apple-developer-program.png) Before you configure any other details, you must create an **App ID** for our app. To do that, go to XCode and open the `ios` folder in your source code. Navigate to the **Signing & Capabilities** tab, click the **+ Capability** button, search for `Push Notifications`, and double-click the result to add it. Then, modify the **Bundle Identifier** to a unique string by prefixing it with any letters of your choice and then change the **Team** to your Apple Developer team. This will automatically create an **App ID** for your app. Head to the Apple Developer account and save the following details: | Field name | | | --- | --- | | Bundle ID | Head to **Apple Developer account** > **Program resources** > **Certificates, Identifiers & Profiles** > **Identifiers** | | Team ID | Head to **Apple Developer account** > **Membership details** > **Team ID** | | Authentication key (.p8 file) | Head to **Apple Developer account** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. Create a key and give it a name. Enable the **Apple Push Notifications service (APNs)**, and register your key. | | Authentication key ID | Head to **Apple Developer account** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. Click on your key to view details. | #### Step 2: Add APNs provider to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Messaging** page, click on the **Providers** tab, and **create a new push notifications provider**. Enter the **team ID**, **bundle ID**, **authentication key ID**, and **authentication key (.p8 file)** that you saved from your Apple Developer account. In case your app is signed with a development provisioning profile, enable **Sandbox** mode. ![Apple provider](/images/integrations/push-apns/provider.png) #### Step 3: Test the provider Follow the [Send push notifications](https://appwrite.io/docs/products/messaging/send-push-notifications) flow to test your provider. ### Read more about APNs and Appwrite Messaging If you would like to learn more about APNs and Appwrite Messaging, we have some resources that you should visit: - [Set up the APNs provider in Appwrite Messaging](https://appwrite.io/docs/products/messaging/apns) - [Send push notifications through Appwrite Messaging](https://appwrite.io/docs/products/messaging/send-push-notifications) - [Best practices for sending push notifications](https://appwrite.io/blog/post/push-notifications-best-practices) - [Appwrite Messaging API reference](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) --- ## Push notifications with FCM https://appwrite.io/integrations/push-fcm Firebase Cloud Messaging (FCM) is a cross-platform messaging solution from Google that enables you to send notifications and messages to users across Android, iOS, and web applications. It supports both notification messages displayed by the system UI and data messages handled by your app’s code. ### How does the integration work? You can use the FCM provider in Appwrite Messaging to send push notifications to your users’ iOS, Android, and web apps. These can be used to send app updates, special offers, instant notifications, reminders, and more. ### How to implement To implement the FCM provider in Appwrite Messaging, there are several steps you must complete: #### Step 1: Sign up on Firebase First, you must [sign up for a Firebase account](https://firebase.google.com/) and create a new project. Head to the **Project settings** page, click on the **Service accounts** tab, and **generate new private key**. Download the JSON file for later use. ![Firebase service account private key](/images/integrations/push-fcm/private-key.png) You must also verify that FCM has been enabled on your project by visiting the **Cloud Messaging** tab. If FCM is disabled, click the three-dots menu, open the link, and click **Enable**. #### Step 2: Add FCM provider to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Messaging** page, click on the **Providers** tab, and **create a new push notifications provider**. Select **FCM** and upload the JSON file you downloaded from Firebase. ![FCM provider](/images/integrations/push-fcm/provider.png) #### Step 3: Test the provider To test the provider on mobile apps, there are some additional configuration steps: {% tabs %} {% tabsitem #android title="Android with FCM" %} 1. Install the `com.google.firebase:firebase-messaging` Firebase SDK. 2. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **Android** app. 3. Register and download your `google-services.json` config file. 4. Add `google-services.json` at the root of your project. 5. Add Google Services class path to your app-level Gradle dependencies block `"com.google.gms:google-services:4.4.0"`. 6. Add the Google Services plugin to your app-level Gradle in the plugins block as `"com.google.gms.google-services"`. 7. Add notification handler service to `AndroidManifest.xml` inside the application tag, alongside other activities. Find an example of this service in the [Send push notification](https://appwrite.io/docs/products/messaging/send-push-notifications#add-targets) journey. ```xml ``` {% /tabsitem %} {% tabsitem #ios title="iOS with FCM" %} 1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **iOS** app. 2. Register and download your `GoogleService-Info.plist` and add it to the root of your project. 3. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled. 4. Create a new key, note down the key ID, and download your key. 5. In the Firebase console, go to *Settings*> **Cloud Messaging** > **APNs authentication key** > click **Upload**. Then, upload your key here. 6. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > + Capabilities > Search for **Push Notifications**. 7. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`. {% /tabsitem %} {% /tabs %} After that, you can follow the [Send push notifications](https://appwrite.io/docs/products/messaging/send-push-notifications) flow to test your provider. ### Read more about FCM and Appwrite Messaging If you would like to learn more about FCM and Appwrite Messaging, we have some resources that you should visit: - [Set up the FCM provider in Appwrite Messaging](https://appwrite.io/docs/products/messaging/fcm) - [Send push notifications through Appwrite Messaging](https://appwrite.io/docs/products/messaging/send-push-notifications) - [Best practices for sending push notifications](https://appwrite.io/blog/post/push-notifications-best-practices) - [Appwrite Messaging API reference](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) --- ## Query MongoDB Atlas https://appwrite.io/integrations/query-mongodb MongoDB is a popular NoSQL database known for its flexibility and scalability. It allows developers to store and manage large amounts of data with ease. ### How does the integration work? You can utilize a pre-built Appwrite function template to integrate a MongoDB Atlas cluster with your app. This will allow you to leverage MongoDB's powerful data management capabilities and NoSQL database and potentially extend it with Appwrite’s ecosystem. ### How to implement To implement the MongoDB Atlas integration, follow these simple steps: #### Step 1: Set up a MongoDB database First, [create an account on MongoDB Atlas](https://www.mongodb.com/cloud/atlas/register) and deploy your first cluster. ![MongoDB Atlas first cluster](/images/integrations/query-mongodb/first-cluster.png) Create your username and password and copy the **connection string** for your MongoDB Atlas cluster. This connection string will be used to connect the Appwrite Function to your MongoDB cluster. ![MongoDB Atlas connection string](/images/integrations/query-mongodb/connection-string.png) Next, head to the **Network Access** page from the left sidebar, click on the **Add IP Address** button, and allow access from anywhere. ![MongoDB Atlas network access](/images/integrations/query-mongodb/network-access.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In case you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click on the **Templates** tab, and search for the **MongoDB** function template. ![Function template](/images/integrations/query-mongodb/template.png) Add the **MongoDB connection string** you saved in the **Variables** step. Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. ![Env variables](/images/integrations/query-mongodb/variables.png) #### Step 3: Test the function Once the function is ready, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. Sending a GET (HTTP) Request to this endpoint will add a randomly generated mock document and list all the documents in the database. ```bash curl DEPLOYED_FUNCTION_DOMAIN \ -H 'accept: application/json' ``` You can add other database functionalities by editing the function generated for you on GitHub. ### Read more about MongoDB Atlas and Appwrite Functions If you would like to learn more about MongoDB Atlas and Appwrite Functions, we have some resources that you should visit: - [Integrate SQL, NoSQL, Vector, Graph, or any database into your Appwrite project](https://appwrite.io/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project) - [Understanding data queries in database management](https://appwrite.io/blog/post/understand-data-queries) - [Appwrite Functions docs](https://appwrite.io/docs/products/functions) --- ## Query Upstash https://appwrite.io/integrations/query-upstash Upstash Vector provides a powerful vector database designed to handle complex queries and search functionalities. It’s ideal for applications that require fast and efficient handling of vector data. ### How does the integration work? You can utilize a pre-built Appwrite function template to integrate an Upstash Vector database with your app. This setup enables you to store and query vector data efficiently, enhancing your application's search and recommendation features. ### How to implement To implement the Upstash Vector integration, follow these simple steps: ##### Step 1: Set up the Upstash Vector database First, [create an account on Upstash](https://console.upstash.com/login) and set up a **new Vector index** with your preferred region and embedding model. Once it is ready, copy your **endpoint** and **token** from the index’s dashboard for further usage ![Upstash vector index](/images/integrations/query-upstash/index.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Upstash** function template. ![Function template](/images/integrations/query-upstash/template.png) Add the **Upstash endpoint** and **token** you saved in the **Variables** step. Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. ![Env variables](/images/integrations/query-upstash/variables.png) #### Step 3: Test the function Once the function is ready, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. Sending a GET (HTTP) Request to this endpoint will add a randomly generated mock embedding and query for the one most similar entry in the database. ```bash curl DEPLOYED_FUNCTION_DOMAIN \ -H 'accept: application/json' ``` You can add other database functionalities by editing the function generated on GitHub. ### Read more about Upstash and Appwrite Functions If you would like to learn more about Upstash and Appwrite Functions, we have some resources that you should visit: - [Integrate SQL, NoSQL, Vector, Graph, or any database into your Appwrite project](https://appwrite.io/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project) - [Appwrite Functions docs](https://appwrite.io/docs/products/functions) --- ## Data replication with RxDB https://appwrite.io/integrations/replication-rxdb RxDB (Reactive Database) is a client-side, NoSQL database designed for JavaScript applications. It emphasizes real-time data synchronization, offline-first capabilities, and reactive programming paradigms. RxDB is particularly suited for applications that require seamless user experiences across various platforms, including web browsers, mobile devices, and desktop environments. ### How does the integration work? RxDB integrates with Appwrite through a replication plugin that enables two-way data synchronization between a client-side RxDB instance and an Appwrite Database. This setup allows apps to store data locally (using data stores such as LocalStorage, SQLite, or IndexedDB) and push/pull changes from the Appwrite backend. When offline, the app continues to function smoothly using RxDB's local-first model, and once connectivity is restored, it syncs changes with Appwrite. Conflict handling, soft deletion, and live replication are handled seamlessly using RxDB’s built-in tools and Appwrite’s Realtime API. ### How to implement To implement the RxDB integration, there are several steps you must complete: #### Step 1: Configure Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. Head over to the Appwrite console, go to the **Settings** page, and copy your project ID and API endpoint for further usage. Next, go to the **Databases** page from the left sidebar, create a new database with the ID `mydb`, and then a collection with the ID `humans` (save both IDs for further usage). Click on the **Attributes** tab and add the following attributes (schema used for demo purposes): | Key | Type | Size | Required | | --- | --- | --- | --- | | `name` | String | 100 | Yes | | `age` | Integer | | Yes | | `homeAddress` | String | 2000 | Yes | | `deleted` | Boolean | | Yes | > **Note:** The `deleted` attribute is necessary to add because RxDB only soft deletes to prevent data loss in offline scenarios (no hard deletion of data occurs). Then, head to the **Settings** tab of your collection, scroll down to the **Permissions** section, and the following: | Role | Create | Read | Update | Delete | | --- | --- | --- | --- | --- | | Any | Yes | Yes | Yes | Yes | > **Note:** While RxDB does not allow you to manually configure permissions for each document, if your app uses Appwrite Auth and document-level permissions are enabled for your collection, the Appwrite SDK will automatically assign read and write permissions to the logged-in user for each document created. #### Step 2: Install Appwrite Web SDK and RxDB library Open the terminal in your app’s working directory and run the following command: ```bash npm install appwrite rxdb ``` In your app’s `.env` file, add the following: ```bash APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 APPWRITE_PROJECT_ID=your-project-id APPWRITE_DATABASE_ID=your-database-id APPWRITE_COLLECTION_ID=your-collection-id ``` Then, import all necessary libraries in your code: ```js import { replicateAppwrite } from 'rxdb/plugins/replication-appwrite'; import { createRxDatabase, addRxPlugin, RxCollection } from 'rxdb/plugins/core'; import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage'; import { Client } from 'appwrite'; ``` #### Step 3: Setup Appwrite client To create an Appwrite client, add the following code: ```js export const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID); ``` #### Step 4: Create an RxDB database and collection To create an RxDB database and collection, first, you must prepare a database schema via the following code: ```js const mySchema = { title: 'my schema', version: 0, primaryKey: 'id', type: 'object', properties: { id: { type: 'string', maxLength: 100 }, name: { type: 'string' }, age: { type: 'number' }, homeAddress: { type: 'string' } }, required: ['id', 'name', 'age', 'homeAddress'] }; ``` Using this schema, you can create a database and collection via the following code: ```js const db = await createRxDatabase({ name: 'mydb', storage: getRxStorageLocalstorage() }); await db.addCollections({ humans: { schema: mySchema } }); const collection = db.humans; ``` > **Note:** Please ensure that the names of your RxDB database and collection match the IDs of your Appwrite database and collection. > #### Step 5: Start replication To start data replication, add the following code: ```js const replicationState = replicateAppwrite({ replicationIdentifier: 'my-appwrite-replication', client, databaseId: process.env.APPWRITE_DATABASE_ID, collectionId: process.env.APPWRITE_COLLECTION_ID, deletedField: 'deleted', // Field that represents deletion in Appwrite collection, pull: { batchSize: 10, }, push: { batchSize: 10 }, /* * ... * You can set all other options for RxDB replication states * like 'live' or 'retryTime' * ... */ }); ``` With that, your RxDB integration is configured and you can use other relevant RxDB functionalities and database operations in your application. ### Further resources If you would like to learn more about RxDB and Appwrite Databases, we have some resources that you should visit: - [RxDB docs for Appwrite](https://rxdb.info/replication-appwrite.html) - [Appwrite offline sync docs](/docs/products/databases/offline) - [Build an offline-first journal app with RxDB and Appwrite](/blog/post/offline-first-journal) - [Offline-first journal demo app on GitHub](https://github.com/appwrite-community/offline-journal) --- ## Search with Algolia https://appwrite.io/integrations/search-algolia Algolia is a search platform that helps you add fast and relevant search capabilities to your app. It makes it easy to find and explore data quickly. ### How does the integration work? By integrating Algolia with Appwrite, you can use Algolia's search features directly in your Appwrite database. This lets you leverage Algolia’s comprehensive search capabilities with your Appwrite backend. ### How to implement To implement the Algolia integration, follow these simple steps: #### Step 1: Set up an Algolia account First, [sign up on Algolia](https://dashboard.algolia.com/users/sign_up) and create a new application. Save the **Application ID**, **Search API Key**, and **Write API Key** for further usage. ![Algolia overview](/images/integrations/search-algolia/overview.png) Then, click on the **Search** page from the left sidebar and **create a new Index** to store your searchable data. Save your **Index ID** for further usage. ![New index](/images/integrations/search-algolia/index.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In case you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Additionally, for this template, you must have an Appwrite Database and Collection filled with data in advance. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Algolia** function template. ![Function template](/images/integrations/search-algolia/template.png) During the setup process, click on the checkbox to create an Appwrite API key on completion and add the Appwrite Database ID and Collection ID for the data you want to index and search as well as the **Algolia application ID**, **search API key**, **write API key** (under Admin API Key), and **index ID** in the **Variables** step. If you are self-hosting Appwrite, click on the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/search-algolia/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. #### Step 3: Test the function Once all the steps are complete, it is time to test the function! Before we go further, visit the **Domains** tab on the **Functions** page and copy the domain URL to test the function. Send a POST (HTTP) Request to the function URL to index the Appwrite Collection in Algolia. ```bash curl -XPOST https://DEPLOYED_FUNCTION_DOMAIN ``` In case you have a large collection of data and your function executions timeout, you can head to the **Settings** tab on the **Functions** page and increase the function timeout limit from **15 seconds** to **900 seconds** (maximum). Then, open your function URL in the browser to try the interactive search UI. ![Demo](/images/integrations/search-algolia/demo.png) ### Read more about Algolia If you would like to learn more about Algolia, we have some resources that you should visit: - [Sign up for Algolia](https://dashboard.algolia.com/users/sign_up) - [Learn more about Functions templates in Appwrite docs](https://appwrite.io/docs/products/functions/templates) - [Connect with other developers and the Appwrite team on Discord.](https://discord.com/invite/appwrite) --- ## Deploy Docusaurus with Sites https://appwrite.io/integrations/sites-docusaurus Docusaurus is a modern static website generator that makes it easy to create and maintain documentation websites. Built with React and powered by Markdown, Docusaurus helps you focus on writing content while taking care of the technical details. With Appwrite Sites, you can deploy your Docusaurus documentation in minutes using our pre-built template, with features like automatic builds, custom domains, and global CDN distribution. ### How does the integration work? [Appwrite Sites](https://appwrite.io/products/sites) provides a ready-to-use Docusaurus template that you can deploy instantly. The template comes with a pre-configured setup optimized for documentation sites, making it easy to get started without any complex configuration. To deploy the Docusaurus template with Appwrite Sites, follow these steps: #### Create site After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Create new site](/images/integrations/sites-docusaurus/create-site.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning the Docusaurus template, which lets you quickly set up a documentation site based on a pre-built project. ![Create new site](/images/integrations/sites-docusaurus/clone-template.png) #### Finding the Docusaurus template When you select **Clone a template**, you'll see a library of templates. To find the Docusaurus template: 1. Use the search bar at the top and type "Docusaurus" ![Search templates](/images/integrations/sites-docusaurus/search-site.png) After selecting the template, you'll need to choose how to deploy it, Clicking the Docusaurus template will open a new page where you can configure your documentation site. #### Configuring your site ##### Setting up site details Once you have selected the Docusaurus template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![Configure site details](/images/integrations/sites-docusaurus/site-details.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Additionally, you can define the domain for your site, using the Appwrite Sites domain. After reviewing your configuration, click **Deploy**. #### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Configure site details](/images/integrations/sites-docusaurus/deployment-screen.png) #### Access your site After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. ![Configure site details](/images/integrations/sites-docusaurus/success.png) #### Next step Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. #### Wrapping up And with that, the Docusaurus template is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. #### More Resources - [Appwrite Sites Docs](/docs/products/sites) - [Docusaurus Website](https://docusaurus.io/) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Deploy Magic Portfolio with Sites https://appwrite.io/integrations/sites-magic-portfolio Magic Portfolio is a modern portfolio template that makes it easy to create and maintain your personal or professional portfolio website. Make your portfolio uniquely yours with customizable themes, brand colors, and fine-tuned visual details like borders and transitions. With Appwrite Sites, you can deploy your portfolio in minutes using our pre-built template, with features like automatic builds, custom domains, and global CDN distribution. ### How does the integration work? [Appwrite Sites](https://appwrite.io/products/sites) provides a ready-to-use Magic Portfolio template that you can deploy instantly. The template comes with a pre-configured setup optimized for portfolio sites, making it easy to get started without any complex configuration. To deploy the Magic Portfolio template with Appwrite Sites, follow these steps: #### Create site After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Create new site](/images/integrations/sites-magic-portfolio/create-site.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning the Magic Portfolio template, which lets you quickly set up a portfolio site based on a pre-built project. ![Create new site](/images/integrations/sites-magic-portfolio/clone-template.png) #### Finding the Magic Portfolio template When you select **Clone a template**, you'll see a library of templates. To find the Magic Portfolio template: 1. Use the search bar at the top and type "Magic Portfolio" ![Search templates](/images/integrations/sites-magic-portfolio/search-site.png) After selecting the template, you'll need to choose how to deploy it, Clicking the Magic Portfolio template will open a new page where you can configure your portfolio site. #### Configuring your site ##### Setting up site details Once you have selected the Magic Portfolio template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![Configure site details](/images/integrations/sites-magic-portfolio/site-details.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Additionally, you can define the domain for your site, using the Appwrite Sites domain. After reviewing your configuration, click **Deploy**. #### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Configure site details](/images/integrations/sites-magic-portfolio/deployment-screen.png) #### Access your site After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. ![Configure site details](/images/integrations/sites-magic-portfolio/success.png) #### Next step Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. #### Wrapping up And with that, the Magic Portfolio template is deployed to Appwrite Sites. You can now connect your GitHub repository and customize your portfolio with themes, colors, and content. You can also explore other templates or deploy any other websites you'd like. #### More Resources - [Appwrite Sites Docs](/docs/products/sites) - [Magic Portfolio Website](https://magic-portfolio.com) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Deploy Nxtlnk with Sites https://appwrite.io/integrations/sites-nxtlnk Nxtlnk is a modern bio links template that makes it easy to create and maintain your personal links page. Made for creatives who love coding, this template lets you create a custom bio link page and self-host it on your own domain. With Appwrite Sites, you can deploy your bio links page in minutes using our pre-built template, with features like automatic builds, custom domains, and global CDN distribution. ### How does the integration work? [Appwrite Sites](https://appwrite.io/products/sites) provides a ready-to-use Nxtlnk template that you can deploy instantly. The template comes with a pre-configured setup optimized for bio link sites, making it easy to get started without any complex configuration. To deploy the Nxtlnk template with Appwrite Sites, follow these steps: #### Create site After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Create new site](/images/integrations/sites-nxtlnk/create-site.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning the Nxtlnk template, which lets you quickly set up a bio links site based on a pre-built project. ![Create new site](/images/integrations/sites-nxtlnk/clone-template.png) #### Finding the Nxtlnk template When you select **Clone a template**, you'll see a library of templates. To find the Nxtlnk template: 1. Use the search bar at the top and type "Nxtlnk" ![Search templates](/images/integrations/sites-nxtlnk/search-site.png) After selecting the template, you'll need to choose how to deploy it, Clicking the Nxtlnk template will open a new page where you can configure your bio links site. #### Configuring your site ##### Setting up site details Once you have selected the Nxtlnk template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![Configure site details](/images/integrations/sites-nxtlnk/site-details.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Additionally, you can define the domain for your site, using the Appwrite Sites domain. After reviewing your configuration, click **Deploy**. #### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Configure site details](/images/integrations/sites-nxtlnk/deployment-screen.png) #### Access your site After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. ![Configure site details](/images/integrations/sites-nxtlnk/success.png) #### Next step Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. #### Wrapping up And with that, the Nxtlnk template is deployed to Appwrite Sites. You can now connect your GitHub repository and customize your bio links page with your own content and styling. You can also explore other templates or deploy any other websites you'd like. #### More Resources - [Appwrite Sites Docs](/docs/products/sites) - [Nxtlnk Website](https://nxtlnk.xyz) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Deploy Starlight with Sites https://appwrite.io/integrations/sites-starlight Starlight is a modern documentation framework built on top of Astro that makes it easy to create beautiful, high-performance documentation websites. Built with Astro and powered by Markdown, Starlight helps you focus on writing content while taking care of the technical details. With Appwrite Sites, you can deploy your Starlight documentation in minutes using our pre-built template, with features like automatic builds, custom domains, and global CDN distribution. ### How does the integration work? [Appwrite Sites](https://appwrite.io/products/sites) provides a ready-to-use Starlight template that you can deploy instantly. The template comes with a pre-configured setup optimized for documentation sites, making it easy to get started without any complex configuration. To deploy the Starlight template with Appwrite Sites, follow these steps: #### Create site After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Create new site](/images/integrations/sites-starlight/create-site.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning the Starlight template, which lets you quickly set up a documentation site based on a pre-built project. ![Create new site](/images/integrations/sites-starlight/clone-template.png) #### Finding the Starlight template When you select **Clone a template**, you'll see a library of templates. To find the Starlight template: 1. Use the search bar at the top and type "Starlight" ![Search templates](/images/integrations/sites-starlight/search-site.png) After selecting the template, you'll need to choose how to deploy it, Clicking the Starlight template will open a new page where you can configure your documentation site. #### Configuring your site ##### Setting up site details Once you have selected the Starlight template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![Configure site details](/images/integrations/sites-starlight/site-details.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Additionally, you can define the domain for your site, using the Appwrite Sites domain. After reviewing your configuration, click **Deploy**. #### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Configure site details](/images/integrations/sites-starlight/deployment-screen.png) #### Access your site After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. ![Configure site details](/images/integrations/sites-starlight/success.png) #### Next step Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. #### Wrapping up And with that, the Starlight template is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. #### More Resources - [Appwrite Sites Docs](/docs/products/sites) - [Starlight Website](https://starlight.astro.build/) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Deploy VuePress with Sites https://appwrite.io/integrations/sites-vuepress VuePress is a modern static site generator that makes it easy to create and maintain documentation websites. Built with Vue.js and powered by Markdown, VuePress helps you focus on writing content while taking care of the technical details. With Appwrite Sites, you can deploy your VuePress documentation in minutes using our pre-built template, with features like automatic builds, custom domains, and global CDN distribution. ### How does the integration work? [Appwrite Sites](https://appwrite.io/products/sites) provides a ready-to-use VuePress template that you can deploy instantly. The template comes with a pre-configured setup optimized for documentation sites, making it easy to get started without any complex configuration. To deploy the VuePress template with Appwrite Sites, follow these steps: #### Create site After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Create new site](/images/integrations/sites-vuepress/create-site.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning the VuePress template, which lets you quickly set up a documentation site based on a pre-built project. ![Create new site](/images/integrations/sites-vuepress/clone-template.png) #### Finding the VuePress template When you select **Clone a template**, you'll see a library of templates. To find the VuePress template: 1. Use the search bar at the top and type "VuePress" ![Search templates](/images/integrations/sites-vuepress/search-site.png) After selecting the template, you'll need to choose how to deploy it, Clicking the VuePress template will open a new page where you can configure your documentation site. #### Configuring your site ##### Setting up site details Once you have selected the VuePress template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![Configure site details](/images/integrations/sites-vuepress/site-details.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Additionally, you can define the domain for your site, using the Appwrite Sites domain. After reviewing your configuration, click **Deploy**. #### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Configure site details](/images/integrations/sites-vuepress/deployment-screen.png) #### Access your site After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. ![Configure site details](/images/integrations/sites-vuepress/success.png) #### Next step Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. #### Wrapping up And with that, the VuePress template is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. #### More Resources - [Appwrite Sites Docs](/docs/products/sites) - [VuePress Website](https://vuepress.vuejs.org/) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## SMS with Twilio https://appwrite.io/integrations/sms-twilio Twilio is a cloud communications platform that provides programmable communication tools for making and receiving phone calls, sending and receiving text messages, and performing other communication functions using web APIs and SDKs. ### How does the integration work? You can use the Twilio provider in Appwrite Messaging to send customized SMS messages to your users. These SMS messages can be sent immediately or scheduled. You can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows. ### How to implement To implement the Twilio provider in Appwrite Messaging, there are several steps you must complete: #### Step 1: Sign up on Twilio First, you must [sign up for a Twilio account](https://www.twilio.com/try-twilio), upgrade your account (in case you want to message numbers other than your own), and purchase a phone number with the SMS capability. ![Twilio console](/images/integrations/sms-twilio/twilio-console.png) Ensure you save the following information for later use: | Field name | | | --- | --- | | Account SID | Head to **Twilio console** > **Account info** > **Account SID**. | | Auth token | Head to **Twilio console** > **Account info** > **Auth Token**. | | Phone number | You can access numbers by navigating to your **Twilio console** > **Develop** > **Phone Numbers** > **Manage** > **Active Numbers**. | #### Step 2: Add Twilio provider to your Appwrite project For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In your Appwrite project, head over to the **Messaging** page, click on the **Providers** tab, and **create a new SMS provider**. Select **Twilio** and add the **account SID**, **auth token**, and **phone number** that you saved from the Twilio console. ![Twilio provider config](/images/integrations/sms-twilio/config.png) #### Step 3: Test the Twilio provider Before you proceed, you must already have at least one [topic](https://appwrite.io/docs/products/messaging/topics) or [target](https://appwrite.io/docs/products/messaging/targets) set up. Once the provider is set up, you can go to the **Messages** tab on the **Messaging** page and **create an SMS message**. You can add the test message, configure the users to send the message to, and pick when the message should be sent out. ![SMS message console](/images/integrations/sms-twilio/sms.png) ### Read more about Twilio and Appwrite Messaging If you would like to learn more about Twilio and Appwrite Messaging, we have some resources that you should visit: - [Sign up for Twilio](https://www.twilio.com/try-twilio) - [Set up the Twilio provider in Appwrite Messaging](https://appwrite.io/docs/products/messaging/twilio) - [Send SMS messages through Appwrite Messaging](https://appwrite.io/docs/products/messaging/send-sms-messages) - [How tools like Twilio can simplify messaging for developers](https://appwrite.io/blog/post/simplify-messaging-twilio) - [Appwrite Messaging API reference](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) --- ## Storage with Amazon S3 https://appwrite.io/integrations/storage-s3 Amazon S3 (Simple Storage Service) is an object storage service that offers industry-leading scalability, data availability, security, and performance. You can use Amazon S3 to store and protect any amount of data for a range of use cases, such as data lakes, websites, mobile applications, backup and restore, archive, enterprise applications, IoT devices, and big data analytics. ### How does the integration work? If you are using self-hosting Appwrite, you can use the Amazon S3 integration for Appwrite Storage to store your files in an S3 bucket instead of on your local hard drive. This is beneficial if you expect large volumes of data or the need to have scalable data storage for your self-hosted Appwrite instance. ### How to implement To implement the Amazon S3 provider for Appwrite Storage, there are several steps you must complete: #### Step 1: Sign up on AWS First, you must [sign up for an AWS account](https://signin.aws.amazon.com/signup?request_type=register) and create an S3 bucket. Save the **Bucket name** and **AWS Region** mentioned here for later use. ![Create S3 bucket](/images/integrations/storage-s3/bucket.png) To access this bucket from Appwrite Storage, we also need to generate an access key within our AWS account. To do so, click on your username in the top-right corner, click on **Security credentials**, and scroll below to create an access key. Save your **Access key ID** and **Secret access key** for later usage. ![Create AWS access key](/images/integrations/storage-s3/access-key.png) #### Step 2: Add S3 provider to your Appwrite instance For this step, you must [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. Visit the `.env` file created for your Appwrite instance and update the following environment variables: ```bash _APP_STORAGE_DEVICE=s3 _APP_STORAGE_S3_BUCKET= _APP_STORAGE_S3_REGION= _APP_STORAGE_S3_ACCESS_KEY= _APP_STORAGE_S3_SECRET= ``` Starting from version 1.7.0, you can also use S3-compatible providers by configuring the endpoint: ```bash ### For path-style requests (bucket in the path) _APP_STORAGE_S3_ENDPOINT= # e.g., s3.amazonaws.com (without https://) _APP_STORAGE_S3_BUCKET= # bucket name required here ### OR for virtual-hosted-style paths (bucket in the endpoint) _APP_STORAGE_S3_ENDPOINT=. # e.g., bucket-name.s3.amazonaws.com _APP_STORAGE_S3_BUCKET= # leave this empty when bucket is in the endpoint ``` After that, run the following Docker Compose commands in your terminal to restart your Appwrite containers and verify if the changes have been successfully applied: ```bash docker compose up -d --force-recreate docker compose exec appwrite vars ``` #### Step 3: Test the provider To test your S3, open your Appwrite project, go to the Storage page, create a new bucket, and upload a file. ![File on Appwrite Storage](/images/integrations/storage-s3/appwrite-file.png) When you visit your S3 bucket via the AWS Management Console, you should see the same file uploaded there. ![File on Amazon S3](/images/integrations/storage-s3/s3-file.png) ### Read more about S3 and Appwrite Storage If you would like to learn more about Amazon S3 and Appwrite Auth, we have some resources that you should visit: - [What is Amazon S3?](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) - [How to create an Amazon S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/creating-bucket.html) - [Appwrite self-hosting docs](https://appwrite.io/docs/advanced/self-hosting) - [Storage environment variables for Appwrite (self-hosted)](https://appwrite.io/docs/advanced/self-hosting/environment-variables#storage) - [Appwrite Storage API reference](https://appwrite.io/docs/references/cloud/client-web/storage) --- ## Payments with Stripe https://appwrite.io/integrations/stripe-payments Stripe is an online payment processing platform that allows businesses to accept online payments securely and efficiently. Stripe supports a variety of payment methods, including credit cards, debit cards, and digital wallets, and offers additional services such as billing, invoicing, and fraud prevention. With robust APIs and extensive documentation, Stripe enables developers to integrate its payment solutions into websites and mobile applications seamlessly, making it a popular choice for businesses of all sizes. ### How does the integration work? You can utilize a pre-built Appwrite function template to enable payments via Stripe in your app. You can then use this to ship packages, send PDF books, give access to video courses, or anything else your business needs. ### How to implement To implement the Stripe payments integration, there are several steps you must complete: #### Step 1: Set up Stripe First, [sign up for Stripe](https://stripe.com/) and open the dashboard in test mode. ![Stripe dashboard](/images/integrations/stripe-payments/dashboard.png) Head to the **Developers** page from the navbar and click on the **API keys** tab. Save the **Secret key** for further usage. ![Stripe API keys](/images/integrations/stripe-payments/api-keys.png) Then, go to the **Webhooks** tab, select the event `checkout.session.completed`, and add a temporary endpoint `https://temporary-endpoint` (we will replace this with our final endpoint later). Save the **webhook signing secret** for later usage. ![Stripe webhooks](/images/integrations/stripe-payments/webhooks.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Payments with Stripe** function template. ![Function template](/images/integrations/stripe-payments/template.png) During the setup process, click the checkbox to generate an Appwrite API key on completion and add the **Stripe secret key** and **webhook secret** in the **Variables** step. If you are self-hosting Appwrite, click on the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/stripe-payments/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. Once the function is deployed, go to the **Settings** tab on the **Function** page, scroll down to your **Build settings**, and update the commands to the following (and then redeploy the function): ```bash npm install npm run setup ``` Then, go to the **Domains** tab, copy the domain, and update it in your **Stripe webhook details** in the following format: ```bash https://DEPLOYED_FUNCTION_DOMAIN/webhook ``` #### Step 3: Test the function Once all the steps are complete, it is time to test the function! First, copy the function URL, go to the **Appwrite project dashboard**, and add it to the **Platforms** section as a web app. This will register your function URL as an authorized hostname to interact with Appwrite (to prevent CORS). ![Add web platform](/images/integrations/stripe-payments/web-platform.png) Then, open it in your browser to test the function and access the pre-built interactive UI. You can anonymously log in through this UI and create a payment order using [Stripe’s test card number](https://docs.stripe.com/testing#cards). As soon as this process is completed, the function UI will show you the completed order. ![Demo](/images/integrations/stripe-payments/demo.png) You can visit the **Databases** page in your Appwrite project, enter the `orders` database and the `orders` collection within that, and find your orders. ![Appwrite database](/images/integrations/stripe-payments/database.png) ### Read more about Stripe Payments and Appwrite Functions If you would like to learn more about Stripe Payments and Appwrite Functions, we have some resources that you should visit: - [Sign up on Stripe](https://stripe.com) - [Learn more about Functions templates in Appwrite docs](https://appwrite.io/docs/products/functions/templates) --- ## Subscriptions with Stripe https://appwrite.io/integrations/stripe-subscriptions Stripe is an online payment processing platform that allows businesses to accept online payments securely and efficiently. Stripe supports a variety of payment methods, including credit cards, debit cards, and digital wallets, and offers additional services such as billing, invoicing, and fraud prevention. With robust APIs and extensive documentation, Stripe enables developers to integrate its payment solutions into websites and mobile applications with ease, making it a popular choice for businesses of all sizes. ### How does the integration work? You can utilize a pre-built Appwrite function template to enable paid subscriptions via Stripe in your app. This will allow you to accept recurring payments from your customers and grant them extra permissions. ### How to implement To implement the Stripe subscriptions integration, there are several steps you must complete: #### Step 1: Setup Stripe First, [sign up for Stripe](https://stripe.com/) and open the dashboard in test mode. ![Stripe dashboard](/images/integrations/stripe-subscriptions/dashboard.png) Head to the **Developers** page from the navbar and click on the **API keys** tab. Save the **Secret key** for further usage. ![Stripe API keys](/images/integrations/stripe-subscriptions/api-keys.png) Then, go to the **Webhooks** tab, select the events `customer.subscription.created` and `customer.subscription.deleted`, and add a temporary endpoint `https://temporary-endpoint` (we will replace this with our final endpoint later). Save the **webhook signing secret** for later usage. ![Stripe webhooks](/images/integrations/stripe-subscriptions/webhooks.png) #### Step 2: Create the Appwrite Function For this step, you must [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. If you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. Head over to the Appwrite console, navigate to the **Functions** page, click the **Templates** tab, and search for the **Subscriptions with Stripe** function template. ![Function template](/images/integrations/stripe-subscriptions/template.png) During the setup process, click the checkbox to generate an Appwrite API key on completion and add the **Stripe secret key** and **Webhook secret** in the **Variables** step. If you are self-hosting Appwrite, click the **optional variables** dropdown and update the Appwrite endpoint to your instance’s publicly accessible endpoint. ![Env variables](/images/integrations/stripe-subscriptions/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. Once the function is deployed, go to the **Domains** tab on the **Function** page, copy the domain, and update it in your **Stripe webhook details** in the following format: ```bash https://DEPLOYED_FUNCTION_DOMAIN/webhook ``` #### Step 3: Test the function Once all the steps are complete, it is time to test the function! First, copy the function URL, go to the **Appwrite project dashboard**, and add it to the **Platforms** section as a web app. This will register your function URL as an authorized hostname to interact with Appwrite (to prevent CORS). ![Add web platform](/images/integrations/stripe-subscriptions/web-platform.png) Then, open it in your browser to test the function and access the pre-built interactive UI. You can anonymously log in through this UI and create a subscription using [Stripe’s test card number](https://docs.stripe.com/testing#cards). As soon as this process is complete, the function UI will show that you are subscribed. ![Demo](/images/integrations/stripe-subscriptions/demo.png) Additionally, your user on Appwrite will feature the `subscriber` label, which you can verify by visiting the **Auth** page in your Appwrite project and clicking on the user. ![Appwrite user](/images/integrations/stripe-subscriptions/user.png) ### Read more about Stripe Subscriptions and Appwrite Functions If you would like to learn more about Stripe Subscriptions and Appwrite Functions, we have some resources that you should visit: - [Sign up on Stripe](https://stripe.com) - [Add app subscriptions with Stripe on Appwrite](https://appwrite.io/docs/tutorials/subscriptions-with-stripe/step-1) - [Learn more about Functions templates in Appwrite docs](https://appwrite.io/docs/products/functions/templates) --- ## WhatsApp with Vonage https://appwrite.io/integrations/whatsapp-vonage Vonage is a cloud communications provider that offers residential telecommunications services based on voice over Internet Protocol as well as programmable communications tools such as phone calls, SMSes, WhatsApp messages, etc. via web APIs. ### How does the integration work? You can use a pre-built Appwrite Function template that is integrated with Vonage to build a WhatsApp-based chatbot that can accept your messages and respond based on pre-defined logic. ### How to implement To implement the **WhatsApp with Vonage** Appwrite Function template, there are several steps you must complete: #### Step 1: Sign up on Vonage First, [sign up for a Vonage account](https://ui.idp.vonage.com/ui/auth/registration) to use the Vonage Communications APIs. This will create a trial account and give you access to the Vonage API dashboard. ![Vonage API settings](/images/integrations/whatsapp-vonage/settings.png) Ensure you save the following information for later use: | Field name | | | --- | --- | | API key | Head to the **Vonage API dashboard** > **API Settings** > **API key** | | API secret | Head to the **Vonage API dashboard** > **API Settings** > **API secret** | | Signature secret | Head to the **Vonage API dashboard** > **API Settings** > **Signature secret** | | WhatsApp phone number | Head to the **Vonage API dashboard** > **Developer Tools** > **Messages API Sandbox** > enable **WhatsApp** and copy phone number | Once the Appwrite Function is deployed, we will return here to add the Function URL as the **Inbound Webhook** link. #### Step 2: Create the Appwrite Function For this step, [create an account on Appwrite Cloud](https://cloud.appwrite.io/register) or [self-host Appwrite](https://appwrite.io/docs/advanced/self-hosting) if you haven’t already. In case you decide to self-host Appwrite, there are [additional setup steps](https://appwrite.io/docs/advanced/self-hosting/functions) to use Appwrite Function templates. In your Appwrite project, go to the **Functions** page, click on the **Templates** tab, search for ‘Vonage’, and select the **WhatsApp with Vonage** function template. ![Function template](/images/integrations/whatsapp-vonage/template.png) During the setup process, add the Vonage **API key**, **API secret, signature secret**, and **WhatsApp phone number** in the **Variables** step**.** ![Env variables](/images/integrations/whatsapp-vonage/variables.png) Then, create a new repository with the default branch and root directory settings. You can edit this repository later to update the function logic. Once the function is ready, visit the **Domains** tab on the function page and copy the domain to add to the **Inbound Webhook** link on the **Messages API Sandbox** page on your Vonage account. #### Step 3: Test the integration Open **WhatsApp** on your phone, join the Vonage WhatsApp Channel (via the steps mentioned on the *Messaging API Sandbox* on *Vonage*), and send any message to the WhatsApp number. You shall receive a message in the format: `Hi there! You sent me: ` ![demo](/images/integrations/whatsapp-vonage/demo.png) ### Read more about Vonage and Appwrite If you would like to learn more about Vonage, we have some resources that you should visit: - [Sign up for Vonage](https://ui.idp.vonage.com/ui/auth/registration) - [Send WhatsApp messages with Vonage and Appwrite Functions](https://appwrite.io/blog/post/function-template-whatsapp-vonage) - [Appwrite Functions products docs](https://appwrite.io/docs/products/functions) --- ## 10 awesome MCP servers and clients for developers https://appwrite.io/blog/post/10-best-mcp-server-client Imagine opening your IDE and asking, “Why did my last deployment fail?” and your assistant instantly pulls details from Vercel, checks Sentry for recent errors, and reviews your last commit on GitHub, all without you leaving your editor. That’s what [Model Context Protocol (MCP)](/blog/post/what-is-mcp) is enabling. It gives developer tools and AI systems a shared language so they can share context in real time. Instead of every integration being its own isolated setup, MCP makes your tools part of the same conversation. In this blog, we’ll look at 10 awesome MCP servers and clients shaping the developer experience in 2025. ### Difference between an MCP Server and an MCP Client? If you’re just starting to explore the **Model Context Protocol (MCP)** ecosystem, it helps to understand the two main building blocks: **MCP servers** and **MCP clients**. They work together, but their roles are very different. #### What is an MCP Server? An **MCP server** is where all the actual capabilities live. It connects tools, APIs, and data so your AI assistant can access them in a structured way. Think of it as the part that does the heavy lifting. For example, a GitHub MCP server can let your AI fetch repo data, review pull requests, or even open new issues. A Sentry MCP server can surface recent errors, and a Stripe MCP server can pull transaction info when you’re debugging a payments flow. Basically, if it has data or an API, there can be an MCP server for it. #### What is an MCP Client? The **MCP client** is the side you actually interact with. It’s the interface where your AI assistant lives, tools like VS Code, Cursor IDE, or Gemini CLI. The client talks to different MCP servers and turns that data into something useful while you’re coding. So instead of jumping between dashboards, your MCP client can pull logs from Sentry, check commits from GitHub, and push code to Appwrite, all without leaving your editor. **In short** - MCP servers provide the capabilities - MCP clients use them to get the work done ### 10 best MCP servers #### 1. Appwrite MCP server Appwrite’s MCP server lets your tools talk directly to your Appwrite project. It can create users, manage databases, handle storage, and more, all through simple prompts. It’s a clean way to connect your AI environment to your backend without extra setup. Link: [Appwrite MCP server](/docs/tooling/mcp/api) #### 2. GitHub MCP server Tired of hopping between tabs to check commits or review pull requests? The GitHub MCP server lets your AI assistant handle it. It reads repos, opens issues, manages PRs, and even inspects CI pipelines. Everything GitHub-related, inside your coding flow. Link: [GitHub MCP server](https://github.com/github/github-mcp-server) #### 3. Sentry MCP server The Sentry MCP server links your debugging data to your AI client. You can look up issues, search logs, analyze releases, or even ask for automated fixes. It gives you visibility into what’s breaking without digging through the Sentry dashboard. Link: [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) #### 4. Figma MCP server Figma’s MCP server brings design context into your development workflow. It lets your tools pull components, variables, and layout details directly from a Figma file so your code reflects the real design instead of relying on screenshots or exports. Link: [Figma MCP server](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server#h_01K25F7RBP00A3VH1733692QN2) #### 5. MongoDB MCP server The MongoDB MCP server turns your database into something you can actually talk to. Ask for schema details, run queries, or manage users, all in plain English. It’s a quick way to explore or modify data without opening the shell or console. Link: [MongoDB MCP server](https://github.com/mongodb-js/mongodb-mcp-server) #### 6. Apify MCP server Think of the Apify MCP server as a gateway to automation. It gives your assistant access to thousands of pre-built Actors for scraping, crawling, and data extraction. Need to collect product reviews or monitor prices? Your AI can spin up the right Actor instantly. Link: [Apify MCP server](https://mcp.apify.com/) #### 7. Stripe MCP server Stripe’s MCP server gives your AI client access to your billing and payment data. It can create customers, invoices, and subscriptions or look up transactions and balances. Everything runs securely through OAuth, keeping your financial operations simple and contained. Link: [Stripe MCP server](https://docs.stripe.com/mcp) #### 8. Vercel MCP server If you deploy on Vercel, this can be helpful. The Vercel MCP server lets your assistant view build logs, check recent deployments, or roll back a release with a single prompt. It keeps the deployment context close to your code. Link: [Vercel MCP server](https://vercel.com/docs/mcp/vercel-mcp) #### 9. Sequential Thinking MCP server This server helps AI tools reason through complex tasks. It can break a problem into smaller steps, explore different options, and refine answers along the way. It’s built for situations where careful, step-by-step thinking leads to better results. Link: [Sequential Thinking MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) #### 10. Neon MCP server Neon’s MCP server connects your AI client to your Postgres databases. It can create projects, run queries, manage branches, and handle migrations through natural language. It’s a straightforward way to work with Neon without switching between tools. Link: [Neon MCP server](https://neon.com/docs/ai/neon-mcp-server) ### 9 best MCP clients #### 1. VS Code VS Code remains one of the most widely adopted MCP clients. Its native integration allows developers to connect directly to multiple MCP servers within their workspace, enabling AI-assisted coding, debugging, and project management without leaving the editor. Link: [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) #### 2. Gemini CLI Gemini CLI extends Google’s AI ecosystem to the terminal. As an MCP client, it can interface with servers like Appwrite, Stripe, and GitHub to generate code, query APIs, and manage deployments through simple commands. Link: [Gemini CLI](https://cloud.google.com/gemini/docs/codeassist/gemini-cli) #### 3. Zed Zed combines low-latency performance with an integrated MCP layer. It provides an AI-assisted editing experience that supports real-time context exchange between code and connected tools, without the overhead of heavier IDEs. Link: [Zed](https://zed.dev/docs/ai/mcp) #### 4. Claude Desktop Claude Desktop supports connections to multiple MCP servers, allowing direct interaction with resources such as logs, repositories, and design assets. It acts as a central client for structured, context-aware AI tasks. Link: [Claude Desktop](https://modelcontextprotocol.io/docs/develop/connect-local-servers) #### 5. Sourcegraph Cody Cody integrates MCP to analyze large codebases efficiently. It can fetch, summarize, and reason over repository data, making it particularly effective for teams managing monorepos or complex dependency structures. Link: [Cody](https://sourcegraph.com/amp) #### 6. Cursor IDE Cursor IDE embeds MCP deeply into its workflow. It connects with servers like Appwrite and GitHub to provide real-time code understanding, context retrieval, and project-level automation during development. Link: [Cursor](https://cursor.com/docs/context/mcp) #### 7. Windsurf IDE Windsurf focuses on agent-based coding. Its MCP integration allows developers to link multiple servers, automate build and test processes, and execute tool-assisted operations across environments. Link: [Windsurf IDE](https://docs.windsurf.com/windsurf/cascade/mcp) #### 8. Open Code Open Code emphasizes transparency and control. As an MCP client, it allows developers to configure how data and commands are exchanged with connected servers, making it a strong fit for teams that prioritize self-hosting and security. Link: [Open Code](https://opencode.ai/docs/mcp-servers/) #### 9. Goose Goose is a lightweight MCP client built for experimentation. It’s commonly used for testing server responses, validating configurations, and debugging new MCP integrations in local or development environments. Link: [Goose](https://github.com/block/goose) ### Wrapping up The MCP ecosystem is becoming an important part of how developers connect tools, data, and AI systems. Servers expose useful capabilities, and clients make those capabilities accessible within the environments developers already use. Appwrite has added two servers that fit naturally into this ecosystem. [MCP server for Appwrite **API**](/docs/tooling/mcp/api) lets AI tools interact with Appwrite projects to create users, manage databases, and handle resources through simple prompts. The [MCP server for Appwrite docs](/docs/tooling/mcp/docs) complements it by allowing clients to access Appwrite’s documentation contextually, so information and references are always within reach. ### More resources - [What exactly is MCP, and why is it trending?](/blog/post/what-is-mcp) - [Appwrite MCP server practical examples](https://www.youtube.com/watch?v=83rNS6W2Nu8) - [Appwrite MCP integrations](https://appwrite.io/integrations#mcp) --- ## 10 new Git commands you should start using today https://appwrite.io/blog/post/10-git-commands-you-should-start-using If you've worked with Git long enough, you've probably hit some common frustrations like operations getting slower as repositories grow, accidentally overwriting changes when switching branches, or struggling with massive monorepos. Thankfully, just like every other tool, Git is constantly evolving and adding new features to make our lives easier. While some of these commands aren't particularly recent, they remain lesser-known gems that can significantly improve your workflow. If you're already familiar with some of the core tips and tricks—like those covered in [15 Git command line tips every developer should know](https://appwrite.io/blog/post/15-git-cli-tips?dofollow=true), this article will introduce you to ten additional commands that can take your Git skills to the next level. ### 1. git switch - A safer way to change branches Before Git 2.23, `git checkout` was the main command for switching branches, but it did much more than that. You could use it to restore files, create branches, or check out specific commits. This made it powerful but potentially confusing - especially when you just wanted to switch branches without touching your files. That's why Git 2.23 introduced `git switch` as a more focused alternative for branch operations. With `git switch`, you can focus solely on branch management: ```bash ### Move to another branch git switch feature-branch ### Create and switch to a new branch git switch -c new-branch ``` This clarity reduces the risk of accidentally overwriting files or making unintended changes. If you've ever hesitated to use `git checkout` for fear of doing something wrong, `git switch` simplifies the process. ### 2. git restore - Safely undo changes Undoing changes often involved using `git checkout` to revert files or `git reset` to move the branch HEAD. However, both commands had the potential to alter your branch state if used incorrectly: `git reset` could move your branch HEAD, while `git checkout` could switch branches or check out a different commit, disrupting the current branch. Git 2.23 introduced `git restore` to focus solely on undoing changes to files. It provides a safer and more straightforward way to revert changes in your working directory or staging area, clearly separating file operations from branch management tasks: ```bash ### Discard working directory changes git restore main.js ### Unstage changes from the index git restore --staged main.js ``` This is especially useful for beginners or in high-stakes situations where precision matters. You can undo changes without worrying about accidentally switching branches or resetting commits. ### 3. git maintenance - Automate repository health As repositories grow, performance can degrade. Operations like `git fetch`, `git status`, or `git log` may slow down, and unused data can clutter your repository. Before Git 2.29, you'd have to manually run commands like `git gc` (garbage collection) or `git repack` to keep your repository optimized. Git 2.29 introduced `git maintenance`, which automates these tasks for you: ```bash ### Enable automatic maintenance git maintenance start ### Run cleanup tasks immediately git maintenance run ``` **What's happening behind the scenes?** - **Garbage Collection:** Removes unreachable objects, such as commits discarded during rebases or branch deletions. - **Repacking:** Consolidates fragmented packfiles for better storage efficiency. - **Commit Graph Updates:** Optimizes commit history traversal, speeding up commands like `git log` and `git blame`. Using `git maintenance` will help you keep your repository healthy without manual effort. ### 4. git sparse-checkout - Efficiently handle large repositories Monorepos are great for managing multiple projects, but cloning an entire repository when you only need a specific directory can be inefficient. Git 2.25 introduced `git sparse-checkout` to solve this. ```bash ### Enable sparse-checkout mode git sparse-checkout init ### Fetch only specific directories ### You can specify multiple directories separated by spaces git sparse-checkout set services/ docs/ ``` With `git sparse-checkout`, you can include only the directories or files you need in your working directory, leaving the rest untouched. This is useful for large teams working on distinct parts of a monorepo, and will save you time and disk space. ### 5. git log --remerge-diff: Understand merges better Merge commits often show which branches were merged, but they don't always explain the specific changes introduced, especially when conflicts were resolved during the merge. Starting with Git 2.35, you can use: ```bash git log --remerge-diff ``` This option reconstructs the merge commit by replaying the recorded merge strategy and showing the exact changes it introduced. It's useful for debugging merge conflicts or reviewing a complicated merge history. ### 6. git blame --ignore-rev - Ignore noisy commits When your team makes a bulk formatting change, `git blame` can lose its utility, as every line ends up pointing to the formatting commit instead of the original author. Introduced in Git 2.23, the `--ignore-rev` option allows you to exclude such commits: ```bash git blame --ignore-rev commit-hash ``` To persist this exclusion, you can set up an ignore-revs file: ```bash ### Add the commit hash to the ignore-revs fileecho commit-hash >> .git-blame-ignore-revs ### Tell Git to use the file git config blame.ignoreRevsFile .git-blame-ignore-revs ``` This helps you focus on meaningful authorship and can be useful in codebases with frequent style updates. ### 7. git range-diff - Compare and track changes between commit ranges Rewriting history, whether through rebasing, cherry-picking, or interactive editing, can be tricky. After a rebase, you might wonder how the rewritten commits differ from the originals. `git range-diff` helps by comparing two commit ranges, showing how one evolved into the other and highlighting changes to individual commits: ```bash git range-diff ``` This command can be used to understand the evolution of a feature or bug fix across different branches. ### 8. git worktree - Work on multiple branches simultaneously Switching branches in a single working directory can disrupt your workflow, especially when you need to work across multiple branches. With `git worktree`, you can create additional working directories tied to the same repository. ```bash ### Add a new worktree for a specific branch git worktree add ../feature-branch feature-branch ### Remove a worktree when you're done git worktree remove ../feature-branch ``` `git worktree` allows you to work on different branches without switching or stashing. You can also create throwaway worktrees with detached HEADs for testing, or isolate builds and deployments in separate working directories. ### 9. git rebase --update-refs - Keep references in sync Rebasing rewrites history by replacing old commits with new ones, but this often leaves branch pointers or tags referencing outdated commits. Git 2.38 introduces the `--update-refs` option to handle this automatically: ```bash git rebase --update-refs ``` With this command, Git ensures that related branches and tags referencing rewritten commits are updated to match the new history. This eliminates the need for tedious manual updates and ensures consistency across your repository. For even more control, you can configure git rebase to always update specific refs by setting: ```bash git config rebase.updateRefs true ``` This is useful in collaborative workflows or when managing multiple refs tied to the same history. ### 10. git commit --fixup and git rebase --autosquash - Fixup commits While not a new feature (introduced in Git 1.7.4, back in 2011), `git commit --fixup` is often overlooked despite being a useful tool for maintaining clean commit histories. When working on a feature, you might realize that you need to fix or improve a previous commit. Manually editing your commit history to include these changes can lead to errors. Git provides `git commit --fixup` and `git rebase --autosquash` to automate this process. ```bash ### Create a fixup commit targeting a specific commit git commit --fixup= ### Later, during an interactive rebase, automatically squash fixup commits git rebase -i --autosquash ``` The `--fixup` option creates a commit that's marked to be automatically squashed into the target commit during an interactive rebase with `--autosquash`. This streamlines the process of cleaning up your commit history before merging changes, and will ensure that related changes are grouped together without manual effort. ### Conclusion The commands we've discussed in this article can help you solve real problems you might be facing every day as a Git user. Whether you're managing a monorepo, handling large histories, or trying to keep your repository clean, these practical solutions can make a difference. Start with one or two that fit your current workflow, and you might be surprised by the improvements in your productivity. If you liked this article, you might also enjoy [15 Git command line tips every developer should know](https://appwrite.io/blog/post/15-git-cli-tips?dofollow=true). ### More resources - [How to implement Sign in with GitHub](https://appwrite.io/blog/post/implement-sign-in-with-github) - [SQL vs NoSQL: Choosing the right database for your project](https://appwrite.io/blog/post/sql-vs-nosql) - [Deno 2 vs Bun: which JavaScript runtime is right for you?](https://appwrite.io/blog/post/deno-vs-bun-javascript-runtime) --- ## 15 Git command line tips every developer should know https://appwrite.io/blog/post/15-git-cli-tips While the command line interface can seem intimidating on the surface, it's actually a very useful tool that gives you control over your code in ways that GUIs often don't. If you can get comfortable with even a few git commands, you'll find yourself being more productive. In this guide, we'll cover 15 essential git commands you should know as a developer, however, if you're looking for more advanced commands, you can check out these [10 new Git commands you should start using today](https://appwrite.io/blog/post/10-git-commands-you-should-start-using?dofollow=true). ### 1. git init - start a new repository `git init` is where it all begins. This command initializes a new Git repository in the current directory, preparing it for version control. It's foundational and something you'll use every time you start a new project locally. To use it, run the following command in your terminal: ```bash git init ``` In my experience, it's helpful to run `git init` even for smaller or personal projects because having version control from the start keeps things organized, regardless of project size. ### 2. git clone - copy an existing repository When you're joining an existing project or working on something hosted remotely, `git clone` is the command you'll use to bring a copy of the repository to your local environment. This command connects you to the project's history and files right away. ```bash git clone https://github.com/user/repo.git ``` `git clone` has saved me time countless times by keeping the setup process simple. It's an easy way to jump into collaboration, letting you focus on coding rather than setup. ### 3. git add - stage your work Adding files to the staging area is one of the first steps in committing changes. `git add ` stages specific files, while `git add -A` stages all modified changes. Knowing how to stage changes properly is a habit that keeps commits clear and manageable. ```bash ### Add a specific file git add index.js ``` ```bash ### Add all files with changes git add -A ``` This is essential to avoid accidental commits or messy histories. The -A option has always been useful for quickly adding everything, though it's best to be selective in more complex projects. ### 4. git commit - create a snapshot of your changes Every Git user needs to get comfortable with `git commit -m`. This command creates a snapshot of your current staged changes with a message, making it easier to understand project history. ```bash git commit -m "Implement user login feature" ``` A clear commit message saves so much time in the long run. It's easy to forget to describe a commit accurately, but I've found that being clear here can prevent future headaches when you're trying to track down issues. ### 5. git add [-p] - stage changes in parts Sometimes you only want to commit specific changes from a file. `git add -p` (patch) lets you review and add individual changes in parts, making it easier to keep each commit focused on a single task. ```bash git add -p ``` This command changed my workflow by allowing me to keep commits clean and organized. I recommend getting comfortable with it because it's invaluable when working on multiple fixes or features simultaneously. ### 6. git status - check your workspace's current state `git status` gives you a quick look at your working directory. It shows what's staged, modified, and untracked. This command is essential to avoid committing changes you didn't intend to. ```bash git status ``` This is a command you might find yourself using often, as it always provides a clear snapshot of where things stand before making further commits or staging changes. It's the best way to ensure no accidental changes are committed. ### 7. git log - review commit history `git log` provides a detailed commit history, showing all commits, authors, and timestamps. Using `git log --oneline` is also helpful when you want a more concise view of the commit history, with each commit condensed to a single line. ```bash ### Full commit history git log ### Condensed history git log --oneline ``` This command helps track project history, and --oneline is great for quickly reviewing recent work. It's ideal for project tracking, and it's always there when you need a more thorough look. ### 8. git diff - view changes between commits or states `git diff` is invaluable for viewing changes between your working directory and the last commit. It helps you double-check modifications before committing, ensuring everything's in order. ```bash git diff ``` I often use this before making a commit. It's saved me from including incomplete code more times than I can count, especially on larger tasks. ### 9. git branch - list, create, and delete branches Branching is essential for working on separate features or tasks. `git branch` helps you list existing branches, create new ones, and delete old ones. Proper branch management keeps projects organized and prevents issues with parallel work. ```bash ### List all branches git branch ### Create a new branch git branch feature-login ### Delete a branch git branch -d feature-login ``` In my experience, naming branches clearly and using `git branch` frequently keeps everything manageable, especially in collaborative projects. ### 10. git checkout - switch or create new branches Switching branches is another everyday task in Git. `git checkout ` lets you move between branches, while `git checkout -b ` creates a new branch and switches to it immediately. ```bash ### Switch to an existing branch git checkout feature-login ### Create and switch to a new branch git checkout -b feature-signup ``` This is one of those commands I use almost daily, and it's crucial when handling multiple feature requests. It keeps development focused without overlapping work. ### 11. git remote add origin - link local and remote repositories When setting up a new repository, linking it to a remote is often one of the first tasks. `git remote add origin` connects your local repo to a remote, making it ready for collaborative work. ```bash git remote add origin https://github.com/user/repo.git ``` This command can seem trivial, but getting it right initially makes the rest of the remote operations straightforward. I always double-check the URL to avoid future push and pull errors. ### 12. git pull and git push - sync local and remote changes `git pull` and `git push` are core to any Git workflow that involves a remote repository. `git pull` brings in changes from the remote to your local branch, while `git push` sends your local commits to the remote branch. ```bash ### Pull changes from remote git pull origin main ### Push changes to remote git push origin main ``` Mastering pull and push is essential for collaboration. Knowing when and how to use each is key, especially in larger projects where syncing matters. ### 13. git reset - undo recent commits Whether due to mistakes or updates, there are times when you need to backtrack. `git reset` helps you undo commits by moving the HEAD pointer to a specific commit. It's a straightforward command that's useful when mistakes need correcting. ```bash ### Undo to the previous commit git reset HEAD~1 ``` The reset command is good for clearing out commits that you consider mistakes. Although, you should be careful when using it, because it alters the commit history. ### 14. git stash - temporarily save changes without committing In fast-paced work environments, you may need to switch tasks quickly. `git stash` lets you save your current work without committing, making it easy to come back and pick up where you left off. ```bash git stash ``` For additional flexibility, if you have untracked files you'd like to stash as well, use: ```bash git stash -u ``` The `git stash` command is practical in real-world scenarios. I've found it helpful for clearing the slate temporarily, especially when juggling multiple responsibilities. ### 15. git reflog - access historical changes and recover lost commits `git reflog` is often overlooked but very useful for accessing the full history of Git commands. It can be a lifesaver when trying to recover lost work or troubleshoot complex issues. ```bash git reflog ``` If something goes wrong, `reflog` can provide a trail to recover lost changes, making it worth knowing even if it's not used daily. ### Conclusion These 15 Git command line tips lay a strong foundation for both solo projects and team-based workflows. Mastering these basics ensures that you'll work efficiently, minimize errors, and maintain a clean project history. Over time, as you get comfortable with each command, your confidence in managing code will grow, and you'll be able to easily add these [10 new Git commands](https://appwrite.io/blog/post/10-git-commands-you-should-start-using?dofollow=true) to your workflow. Git's power is in its flexibility, and learning the command line lets you tap into that fully. Practice them, apply them, and let them become second nature for a smoother development experience. ### More resources - [How to implement Sign in with GitHub](https://appwrite.io/blog/post/implement-sign-in-with-github) - [10 new Git commands you should start using today](https://appwrite.io/blog/post/10-git-commands-you-should-start-using?dofollow=true) - [Building a currency converter API with Deno 2 and Appwrite](https://appwrite.io/blog/post/build-a-currency-converter-with-deno) --- ## 3 things you can build with the Go runtime https://appwrite.io/blog/post/3-things-you-can-build-with-go-runtime In the last few years, Golang (or Go) has grown to become one of the most popular programming languages for developers building cloud-native applications. With Appwrite 1.6, we have introduced a new runtime to let developers build Appwrite Functions with Go. ### How Appwrite Functions and Go complement each other There are several reasons why Appwrite Functions and Go form a rather handy option for your product development toolkit: #### Highly performant (due to compiled nature) In our internal benchmark, while the larger codebase and compiled nature of Go leads to slower builds, our Go runtime showed up to 3 times faster cold-start times compared to interpreted languages. Additionally, it demonstrated the highest performance in requests per second and 5 times lesser memory usage than several of our other runtimes, including Node.js, Deno, Ruby, and Python. #### Open-source runtime(s) Our Go runtime (just like our other runtimes) has been developed by our team and is [open-sourced](https://github.com/open-runtimes/open-runtimes/tree/main/runtimes/go), which allows a simpler feedback and contribution mechanism and enables improvement of the runtime at a much higher pace. #### Event-driven nature Appwrite Functions can be executed by various types of events, which allows you to integrate them into your applications in many different ways. These events include all HTTP actions (to consume like a REST API), CRON schedules (to run them on set time periods), and any events across the various Appwrite products in your project (for example, user creation, document deletion, or file upload). #### Global environment variables Aside from environment variables at the function level, Appwrite also allows you to environment variables at the project level so that they can be shared across multiple functions in a single project. #### Permissions system Appwrite’s permissions system for products such as Databases and Storage also extends to Functions, providing an additional layer of security to prevent unauthorized users from consuming your functions. #### Local development support With our latest release, Appwrite has released support for [local development](https://appwrite.io/blog/post/announcing-local-development), allowing users to test and debug their Appwrite Functions on their devices without deploying to an Appwrite instance. For the Go runtime, we have also released a module containing all the necessary types for the Functions runtime, making it even easier to develop Go-based Appwrite Functions in your preferred code editor. #### CI/CD with GitHub Appwrite offers CI/CD support for Appwrite Functions through GitHub, simplifying your developer experience by automating the process of pushing your function to your Appwrite project. ### Building Go functions To use Go in Appwrite, you need to use the latest version of Appwrite. You can either [sign up on Appwrite Cloud](https://cloud.appwrite.io/) or [self-host](https://appwrite.io/docs/advanced/self-hosting) Appwrite 1.6 with the `go-1.23` runtime added to your [environment](https://appwrite.io/docs/advanced/self-hosting/environment-variables#functions:~:text=preserve%20harddrive%20health.-,_APP_FUNCTIONS_RUNTIMES,-version%20%3E%3D%200.8.0%20This). The runtime will be available on Appwrite Cloud after Init. Next, go ahead and create a Go function using the [Appwrite CLI](https://appwrite.io/docs/tooling/command-line/installation#Installation) by running `appwrite init function`. Once your function is set up, we can try some examples: #### Example 1: AI Chatbot using GPT-3.5 The first example is a simple chatbot function that accepts a prompt in the request body and returns an answer in the response from the ChatGPT API. To do this, we must first add the `go-openai` dependency to our project’s `mod` file. ```bash go get github.com/sashabaranov/go-openai ``` Then, we can use it to create our chatbot function logic in the `main.go` file, where we get the prompt from our request body, send it to the OpenAI API (GPT-3.5 Turbo model), and return a response to the client. ```go package handler import ( "context" "os" "github.com/open-runtimes/types-for-go/v4/openruntimes" openai "github.com/sashabaranov/go-openai" ) type RequestBody struct { Prompt string `json:"prompt"` } // This Appwrite function will be executed every time your function is triggered func Main(Context openruntimes.Context) openruntimes.Response { openAiKey := os.Getenv("OPENAI_KEY") openAiClient := openai.NewClient(openAiKey) if Context.Req.Method == "GET" { return Context.Res.Text("Send a POST request to this endpoint with a prompt and get a response.") } if Context.Req.Method == "POST" { var requestBody RequestBody err := Context.Req.BodyJson(&requestBody) if err != nil { Context.Error(err) return Context.Res.Json(map[string]interface{}{ "ok": false, "error": "Missing request body", }, Context.Res.WithStatusCode(400)) } prompt := requestBody.Prompt completion, err := openAiClient.CreateChatCompletion( context.Background(), openai.ChatCompletionRequest{ Model: openai.GPT3Dot5Turbo, Messages: []openai.ChatCompletionMessage{ { Role: openai.ChatMessageRoleUser, Content: prompt, }, }, }, ) if err != nil { Context.Error(err) return Context.Res.Json(map[string]interface{}{ "ok": false, "error": err.Error(), }, Context.Res.WithStatusCode(500)) } return Context.Res.Json(map[string]interface{}{ "ok": true, "response": completion.Choices[0].Message.Content, }) } return Context.Res.Json(map[string]interface{}{ "ok": false, "error": "Bad request", }, Context.Res.WithStatusCode(400)) } ``` You can then deploy this function using the `appwrite deploy function` command. #### Example 2: HTML Resume The second example is an online HTML-based resume that you can deliver online through the function. For this, the first thing we do is create a `static` directory in the function folder and add a file, `resume.html` with the contents of our resume. You can [copy our template](https://github.com/appwrite-community/go-function-examples/blob/main/functions/go-resume/static/resume.html) if you’d like. Next, create our function logic in the `main.go` file, where we return this content with the appropriate headers so that a browser reads it as an HTML page. ```go package handler import ( "embed" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) //go:embed static/* var embedReader embed.FS func Main(Context openruntimes.Context) openruntimes.Response { if Context.Req.Method == "GET" { resumeHtml, _ := embedReader.ReadFile(("static/resume.html")) Context.Log(resumeHtml) return Context.Res.Text(string(resumeHtml), Context.Res.WithStatusCode(200), Context.Res.WithHeaders(map[string]string{ "Content-Type": "text/html", })) } return Context.Res.Text("Bad request", Context.Res.WithStatusCode(404)) } ``` You can then deploy this function using the `appwrite deploy function` command. #### Example 3: URL Shortener The third example is a personal URL shortener that stores your shortened URL path and long URL in an Appwrite Database and redirects the consumer to the appropriate long URL on pinging the shortened URL. To build this function, create a `services` directory in the function folder and add a file `setup.go`. Here, we will add the necessary functions to initialize our Appwrite database. ```go package services import ( "github.com/appwrite/sdk-for-go/databases" "github.com/appwrite/sdk-for-go/permission" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func DoesDatabaseExist(dbs databases.Databases, dbId string) bool { _, err := dbs.Get(dbId) if err != nil { return false } return true } func DoesCollectionExist(dbs databases.Databases, dbId string, collId string) bool { _, err := dbs.GetCollection(dbId, collId) if err != nil { return false } return true } func DoesAttributeExist(dbs databases.Databases, dbId string, collId string, attribId string) bool { _, err := dbs.GetAttribute(dbId, collId, attribId) if err != nil { return false } return true } func InitialiseDatabase(Context openruntimes.Context, dbs databases.Databases, dbId string, collId string) { doesDbExist := DoesDatabaseExist(dbs, dbId) if !doesDbExist { dbs.Create( dbId, "URL Databases", ) } doesCollExist := DoesCollectionExist(dbs, dbId, collId) if !doesCollExist { dbs.CreateCollection( dbId, collId, "URLs", dbs.WithCreateCollectionPermissions([]string{permission.Read("any")}), ) } doesAttribExist := DoesAttributeExist(dbs, dbId, collId, "longUrl") if !doesAttribExist { dbs.CreateUrlAttribute( dbId, collId, "longUrl", true, dbs.WithCreateUrlAttributeArray(false), ) } } ``` After that, we create the function logic in the `main.go` file, where each `POST` request stores the shortened URL path and the relevant long URL in the Appwrite database, and each `GET` request to the saved (shortened) URL path redirects the user to the relevant long URL. ```go package handler import ( "openruntimes/handler/services" "os" "github.com/appwrite/sdk-for-go/appwrite" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) type RequestBody struct { ShortId string `json:"shortId"` LongUrl string `json:"longUrl"` } type ResponseBody struct { LongUrl string `json:"longUrl"` } func Main(Context openruntimes.Context) openruntimes.Response { client := appwrite.NewClient( appwrite.WithEndpoint(os.Getenv("APPWRITE_FUNCTION_API_ENDPOINT")), appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")), appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]), ) databases := appwrite.NewDatabases(client) dbId := "urlDatabase" collId := "urlCollection" services.InitialiseDatabase(Context, *databases, dbId, collId) if Context.Req.Method == "POST" { var requestBody RequestBody err := Context.Req.BodyJson(&requestBody) if err != nil { Context.Error(err) return Context.Res.Json(map[string]interface{}{ "ok": false, "error": "Missing request body", }, Context.Res.WithStatusCode(400)) } _, err = databases.CreateDocument( dbId, collId, requestBody.ShortId, map[string]interface{}{ "longUrl": requestBody.LongUrl, }, ) if err != nil { Context.Error(err) return Context.Res.Json(map[string]interface{}{ "ok": false, "error": "Failed to create shortened URL", }, Context.Res.WithStatusCode(500)) } return Context.Res.Json(map[string]interface{}{ "ok": true, "shortId": requestBody.ShortId, "longUrl": requestBody.LongUrl, }) } if Context.Req.Method == "GET" { path := Context.Req.Path if path == "/" { return Context.Res.Text("Welcome to the URL shortener service\n\nAdd a short URL to the path to redirect to the long URL\n", Context.Res.WithStatusCode(200)) } shortId := path[1:] document, err := databases.GetDocument(dbId, collId, shortId) if err != nil { Context.Error(err) return Context.Res.Text("URL not found", Context.Res.WithStatusCode(404)) } var responseBody ResponseBody document.Decode(&responseBody) return Context.Res.Redirect(responseBody.LongUrl, Context.Res.WithStatusCode(302)) } return Context.Res.Empty() } ``` You can then deploy this function using the `appwrite deploy function` command. After deployment, go to the Settings tab on the Function page in your Appwrite project and enable the following scopes for the dynamic API key: `databases.read`, `databases.write`, `collections.read`, `collections.write`, `attributes.read`, `attributes.write`, `documents.read`, `documents.write`, ### More resources With that, you can see a few glimpses of what the Go runtime of Appwrite Functions can help you achieve. You can also find the function code for the examples shared above in our [GitHub repo](https://github.com/appwrite-community/go-function-examples). If you enjoyed reading this blog, here are some more resources to help you get started with Appwrite Functions and Go: - [Appwrite Functions docs](https://appwrite.io/docs/functions) - [Go docs](https://go.dev/doc/) - [Appwrite Discord](https://appwrite.io/discord) --- ## Top 30 software development tools for agencies and teams https://appwrite.io/blog/post/30-dev-tools-for-agencies The way software is built today is drastically different from even five years ago. Teams are more distributed. Deadlines are tighter. And expectations, be it from clients, product leads, or users, are higher than ever. Whether you're running a fast-paced development agency, juggling multiple client projects, or leading an internal team building and maintaining a product, the tools you use directly impact your delivery speed, code quality, and team effectiveness. In this blog, we’ve put together 30 high-leverage tools used by modern dev teams and agencies to ship faster, collaborate better, and operate at scale. Let’s dive in. To make this easier to navigate and more actionable, we’ve divided the tools into categories based on where they fit in the development workflow. ### The 5 best code editors A code editor is often the first and most frequently used tool in any developer’s workflow. It’s where [ideas](/blog/post/startups-ideas-for-developers-2024) take shape and entire products come to life. For agencies and dev teams, the right editor can boost focus, reduce friction, and speed up delivery. #### Quick overview of top code editors | **Editor** | **Best for** | **Pricing** | | --- | --- | --- | | **VS Code** | Daily dev work | Free | | **Sublime Text** | Fast, lightweight edits | Free or $99 for 3-year license | | **Zed** | Real-time collaboration | Free or Paid from $20/month | | **Notepad++** | Quick file tweaks | Free | | **CodeSandbox** | Web-based code editor | Free or Paid from $15/month | #### 1. Visual Studio Code ![VS Code](/images/blog/dev-tools-for-agencies/visual-code.png) [**Visual Studio Code**](https://code.visualstudio.com/) is a fast, lightweight, and extensible code editor developed by Microsoft. It's widely adopted across teams for its robust features, active extension ecosystem, and deep support for JavaScript, TypeScript, Python, and more. **Visual Studio Code pros:** - Huge extension marketplace for almost every workflow - Excellent Git and GitHub integration out of the box - Built-in terminal and debugger streamline development **Visual Studio Code cons:** - Can get sluggish with too many extensions - Not a full IDE, lacks some out-of-the-box tooling for larger projects - Default settings can require tweaking for team consistency **Visual Studio Code pricing:** Free and open-source #### 2. Sublime Text ![Sublime Text](/images/blog/dev-tools-for-agencies/sublime-text.png) [**Sublime Text**](https://www.sublimetext.com/) is a minimalist, high-performance text editor known for its speed and responsiveness. It's favored by developers who want a distraction-free, keyboard-driven coding experience without the overhead of heavier environments. **Sublime Text pros:** - Incredibly fast and lightweight - Powerful multi-cursor editing and search - Low memory usage, great for older machines **Sublime Text cons:** - Limited features compared to modern editors like VS Code - Smaller extension ecosystem - Not ideal for large team workflows or full-stack development **Sublime Text pricing:** $99 one-time license (per user); free trial available #### 3. Zed ![Zed](/images/blog/dev-tools-for-agencies/zed.png) [**Zed**](https://zed.dev/) is a blazing-fast, multiplayer-first code editor designed for high-performance local development and real-time collaboration, it targets modern teams looking for low-latency workflows and a clean interface. **Zed pros:** - Built-in multiplayer editing (like Google Docs for code) - Exceptionally fast and minimal interface - Native performance with low CPU usage **Zed cons:** - Still in early development, - Limited extensions and language support - Mac-only (as of now) **Zed pricing:** Free for personal use. $20 per month for pro plan with support to agentic coding out of the box. #### 4. Notepad++ ![Notepad](/images/blog/dev-tools-for-agencies/notepad.png) [**Notepad++**](https://notepad-plus-plus.org/) is a lightweight source code editor for Windows, popular for its simplicity and speed. While you may not use it for complex dev work, it remains a go-to tool for quick edits, note taking, and low-overhead tasks. **Notepad++ pros:** - Extremely lightweight and fast to launch - Supports syntax highlighting for 80+ languages - Ideal for quick file edits and scripting **Notepad++ cons:** - Windows-only - Lacks advanced features for modern development - Outdated UI and limited collaboration support **Notepad++ pricing:** Free and open-source. #### 5. CodeSandbox ![CodeSandbox](/images/blog/dev-tools-for-agencies/codesandbox.png) [**CodeSandbox**](https://codesandbox.io/?from-app=1) is a web-based development environment built for rapid prototyping, collaboration, and sharing. It’s especially popular among frontend teams and agencies that need to quickly spin up isolated environments without local setup. **Codesandbox pros:** - Instant dev environments in the browser - Offers a wide range of pre-configured templates - Real-time collaboration **Codesandbox cons:** - Requires internet access for full functionality - Limited support for complex backend workflows - Performance can vary depending on project size **CodeSandbox pricing:** Free for learning and experimenting. $170 per month per workspace for the Scale plan. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Open source" point2="Start for free" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/console/" /%} ### The 5 best IDEs While code editors are lightweight and flexible, Integrated Development Environments (IDEs) offer a full suite experience. IDEs are especially useful for large codebases, strongly typed languages, and teams that need deeper tooling out of the box. They’re heavier than code editors but often more powerful for complex projects that demand more structure and control. #### Quick overview of top IDEs | **IDE** | **Best for** | **Pricing** | | --- | --- | --- | | **IntelliJ IDEA** | Java, Kotlin, Spring projects | Free or paid from $169/year | | **Visual Studio** | .NET, C#, Windows applications | Free or paid from $45/month | | **Eclipse** | Java, C/C++, extensible needs | Free and open-source | | **PyCharm** | Python, Django, Flask workflows | Free or paid from $99/year | | **NetBeans** | Java, desktop apps, GUI builders | Free and open-source | #### 1. IntelliJ IDEA ![Intellij Idea](/images/blog/dev-tools-for-agencies/intellij.png) [**IntelliJ IDEA**](https://www.jetbrains.com/idea/#) by JetBrains is a powerful, full-featured IDE built for Java and JVM-based languages, but it also supports many others through plugins. Known for its deep code understanding and smart assistance, it's a go-to for backend-heavy teams and enterprise-grade projects. **IntelliJ IDEA pros:** - Exceptional code intelligence and refactoring tools - Built-in support for testing, version control, and build tools - Smooth integration with Spring, Gradle, and Maven **IntelliJ IDEA cons:** - Heavy on system resources - Steeper learning curve for new users - Best features locked behind the paid version **IntelliJ IDEA Pricing:** Free (Community Edition); Paid plans start at $169/year (Individual) or $599/year (Business) #### 2. Visual Studio ![Visual Studio](/images/blog/dev-tools-for-agencies/visual-studio.png) [**Visual Studio**](https://visualstudio.microsoft.com/) by Microsoft is a full-featured IDE built for developing on the .NET platform, but it also supports C++, Python, and more. It’s widely used by enterprise teams and agencies building desktop, web, and cloud-native applications on Windows. **Visual Studio pros:** - Deep .NET and Azure integration - Robust debugging and profiling tools - Strong support for enterprise workflows and large projects **Visual Studio cons:** - Windows-only (full version) - Can feel bloated for smaller or frontend projects - Slower startup and performance on older machines **Visual Studio Pricing:** Free (Community Edition); Professional starts at $99/month, Enterprise at $499/month. #### 3. Eclipse ![Eclipse](/images/blog/dev-tools-for-agencies/eclipse.png) [**Eclipse**](https://eclipseide.org/) is a long-standing, open-source IDE primarily known for Java development, but it also supports C/C++, PHP, and more via plugins. It’s widely used in academic, enterprise, and embedded development environments where customizability and extensibility matter. **Eclipse pros:** - Highly customizable with a rich plugin ecosystem - Good support for Java and enterprise-grade tools - Open-source and community-driven **Eclipse cons:** - Slower and heavier than modern alternatives - Outdated UI compared to newer IDEs - Plugins can conflict or break compatibility **Eclipse pricing:** Free and open-source #### 4. PyCharm ![PyCharm](/images/blog/dev-tools-for-agencies/pycharm.png) **PyCharm**, again developed by JetBrains, is a powerful IDE built specifically for Python development. It offers smart code assistance, robust testing tools, and deep integration with web frameworks like Django and Flask, making it a go-to for both web and data teams. **PyCharm pros:** - Excellent Python and Django support - Built-in testing, debugging, and virtualenv tools - Intelligent code completion and navigation **PyCharm cons:** - Heavier than lightweight editors like VS Code - Best features are in the paid version - Can feel overwhelming for simple scripts **PyCharm pricing:** Free (Community Edition); Professional starts at $99/year (Individual), $249/year (Business) #### 5. NetBeans ![NetBeans](/images/blog/dev-tools-for-agencies/netbeans.png) [**NetBeans**](https://netbeans.apache.org/front/main/index.html), maintained by the Apache Foundation, is an open-source IDE mainly used for Java development, though it also supports PHP, HTML5, and C/C++. It provides a simple, out-of-the-box experience for developers looking to build desktop, web, and mobile apps. **NetBeans pros:** - Built-in support for Java, Maven, and Gradle - Good GUI builder for desktop applications - Simple setup with minimal configuration needed **NetBeans cons:** - Slower and more resource-heavy than modern IDEs - Less polished UI and UX - Smaller plugin ecosystem compared to IntelliJ or VS **NetBeans pricing:** Free and open-source ### The 5 best version control tools Version control helps developers track changes, collaborate without conflicts, and safely manage code across features and releases. It helps developers work on features in parallel, review each other’s work, track who changed what and why, and roll back safely when needed. #### Quick overview of top version control tools | **Tool** | **When to Use** | **Pricing** | | --- | --- | --- | | **GitHub** | Collaborative coding and open-source | Free or paid from $4 per user/month | | **GitLab** | All-in-one DevOps and self-hosting | Free or paid from $29 per user/month | | **Bitbucket** | Jira-connected workflows | Free or paid from $3 per user/month | | **Beanstalk** | Simple version control + deployment | Starts at $15/month (5 users) | | **Helix Core** | Large files, game assets, binaries | Free for small teams or enterprise pricing | #### 1. GitHub ![GitHub](/images/blog/dev-tools-for-agencies/github.png) [**GitHub**](https://github.com/) is the most widely used Git-based platform for version control and code collaboration. It offers cloud-hosted repositories, powerful pull request workflows, integrated CI/CD via GitHub Actions, and a vast ecosystem of integrations. It’s a top choice for both open-source projects and private team development. **GitHub pros:** - Clean UI with powerful code review and branching tools - GitHub Actions for integrated CI/CD workflows - Strong community, ecosystem, and integration support **GitHub cons:** - No self-hosting option - Steeper learning curve for beginners unfamiliar with Git - Limited security features in free plans **GitHub pricing**: Free for individuals and public repos; Team plan starts at $4/user/month; Enterprise plans available. #### 2. GitLab ![GitLab](/images/blog/dev-tools-for-agencies/gitlab.png) [**GitLab**](https://about.gitlab.com/) is an all-in-one DevOps platform that combines Git-based version control with built-in CI/CD, issue tracking, and security tools. It’s popular among teams that prefer a unified toolchain and need the option to self-host their infrastructure. **GitLab pros:** - Built-in CI/CD and DevSecOps tools - Full-featured self-hosted option - Integrated project management (issues, epics, milestones) **GitLab cons:** - UI can feel cluttered compared to GitHub - Learning curve can be steep for non-technical stakeholders - Might be overwhelming for small projects **GitLab pricing:** Free for individuals and small teams; Premium starts at $29/user/month. #### 3. Beanstalk ![Beanstalk](/images/blog/dev-tools-for-agencies/beanstalk.png) [**Beanstalk**](https://beanstalkapp.com/) is a hosted version control service that supports both Git and SVN. It's designed for teams that want a simple, secure platform with built-in deployment and collaboration tools, without needing to manage their own servers or integrate third-party services. **Beanstalk pros:** - Built-in deployment to FTP, SFTP, and cloud servers - Supports Git and Subversion (SVN) - Simple, clean UI for non-technical stakeholders **Beanstalk cons:** - Lacks native CI/CD and modern DevOps features - Limited extensibility and ecosystem compared to GitHub/GitLab - Pricing can get expensive as team size grows **Beanstalk pricing:** Starts at $15/month for 5 users; plans scale with users and repo storage. #### 4. Helix Core ![Helix Core](/images/blog/dev-tools-for-agencies/helixcore.png) [**Helix Core**](https://www.perforce.com/products/helix-core) is a high-performance version control system built for handling large codebases, game assets, and binary files. It’s widely used by enterprise software teams, game studios, and engineering orgs that need strong control, speed, and scalability **Helix Core pros:** - Optimized for large-scale projects and big binary files - Fine-grained access control and branching workflows - Scalable for thousands of users and massive repos **Helix Core cons:** - Complex setup and admin overhead - Steeper learning curve compared to Git-based systems - Requires separate tools for code review and CI/CD **Helix Core pricing:** Free for up to 5 users and 20 workspaces; enterprise pricing available on request. #### 5. **Bitbucket** ![Bitbucket](/images/blog/dev-tools-for-agencies/bitbucket.png) [**Bitbucket**](https://bitbucket.org/product/) by Atlassian is a Git-based version control platform known for its tight integration with Jira, Trello, and other tools in the Atlassian ecosystem. It supports both cloud and self-hosted deployments, making it a popular choice for teams already using Atlassian products. **Bitbucket pros:** - Seamless integration with Jira and Trello - Built-in CI/CD with Bitbucket Pipelines - Supports both Git and Mercurial (legacy) **Bitbucket cons:** - Smaller community and plugin ecosystem compared to GitHub - UI and performance can lag on larger repos - Less ideal for open-source collaboration **Bitbucket pricing:** Free for up to 5 users; Standard starts at $3/user/month; Premium at $6/user/month ### The 5 best AI coding assistants AI coding assistants have quickly become essential in modern development workflows. They help teams write code faster, reduce repetitive tasks, and spot errors early. For teams working under tight deadlines or agencies handling multiple projects, AI assistants boost output without compromising quality. #### Quick overview of top AI coding assistants | **Tool** | **Best for** | **Pricing** | | --- | --- | --- | | **GitHub Copilot** | Real-time code suggestions across IDEs | Paid from $10/month (Individual) | | **Cursor** | Chat + code editing in natural language | Free plan or paid from $20/month | | **Cody (Sourcegraph)** | AI + code search across large projects | Paid from $19/user/month (Enterprise) | | **Replit Ghostwriter** | In-browser, fast prototyping | Free or paid from $25/month | | **Windsurf** | Full-context AI inside a purpose-built IDE | Free or paid from $15/user/month | #### 1. Github Copilot ![Github Copilot](/images/blog/dev-tools-for-agencies/copilot.png) **GitHub Copilot**, is an AI pair programmer developed by GitHub in collaboration with OpenAI, suggests code in real time as you type. Integrated into editors like VS Code, Neovim, and JetBrains IDEs, it’s designed to help developers write boilerplate, complete functions, and explore new libraries faster. **GitHub Copilot pros:** - Fast, inline code suggestions across multiple languages - Strong support for multiple languages - Seamlessly integrates into popular IDEs **GitHub Copilot cons:** - Can generate incorrect or insecure code without context - May struggle with highly domain-specific logic - Requires internet connection and GitHub account **GitHub Pricing:** #### 2. Cursor ![Cursor](/images/blog/dev-tools-for-agencies/cursor.png) **Cursor** is an AI code editor offering deep context awareness and chat-driven development inside the editor. It goes beyond autocomplete by letting developers refactor code, explain functions, and generate entire implementations through natural language prompts. **Cursor pros:** - Can reference your full codebase for better context - Edit code in natural language - Privacy mode for enhanced security **Cursor cons:** - Struggles with large codebases and complex requests - Limited offline functionality - Potential security risks **Cursor pricing:** Free Hobby plan with limited features. Pro plan starting at $20 per month. #### 3. Sourcegraph Cody ![Sourcegraph Cody](/images/blog/dev-tools-for-agencies/cody.png) [Sourcegraph Cody](https://sourcegraph.com/cody) is an AI coding assistant developed by Sourcegraph. It leverages advanced search and codebase context to help developers write, understand, and fix code more efficiently. **Cody pros:** - Integration with external tools like Notion, Linear and more - Access to the best and latest LLMs - Combination of search, AI chat, and prompts in a single interface **Cody cons:** - Initial setup and integration may require efforts. - Takes time to fully understand complex projects. **Cody pricing:** Enterprise Starter plan starting at $19 per user/month. #### 4. Replit ![Replit](/images/blog/dev-tools-for-agencies/replit.png) [**Replit Ghostwriter**](https://replit.com/) is an AI-powered coding assistant built into the Replit online IDE. Designed for instant prototyping and browser-based development, it offers code completion, generation, explanations, and debugging, all in a collaborative and cloud-native environment. **Replit pros:** - Works directly in the browser. No setup needed - Includes code explanation, completion, and test generation - Ideal for fast prototyping and learning environments **Replit cons:** - Tied to Replit’s IDE—limited portability - Not suited for large-scale production projects - Less control over context compared to desktop tools **Replit pricing:** Free plan to explore and paid plans starting from $25 per month. #### 5. Windsurf ![Windsurf](/images/blog/dev-tools-for-agencies/windsuf.png) [**Windsurf**](https://windsurf.com/) is an AI coding assistant optimized for full-context awareness across large codebases. Built to handle multi-repo setups and deep reasoning over complex projects, it’s designed for senior developers and teams working on serious production code. **Windsurf pros:** - Deep codebase understanding across multiple files and repos - Powerful natural language workflows with Cascade - Integrates with tools like Figma, Stripe, and Postgres **Cons:** - Mac-only (desktop editor); limited cross-platform support - Early-stage product, some workflows still evolving - May be overkill for simple or short tasks **Windsurf pricing:** Free plan to explore and pro plans starting from $15 per user per month. {% call_to_action title="Join the Appwrite Partners program" description="You'll get the support you need to provide your clients with the best solutions. Grow your business on top of Appwrite." point1="Access to the Appwrite team" point2="Volume discounts for clients" point3="Co-marketing opportunities" point4="Newest technology" cta="Become a partner" url="/partners" /%} ### The 5 best project management tools Project management tools help teams stay aligned, track progress, and deliver work on time. For software agencies and dev teams, they provide a central place to manage tasks, sprints, timelines, and client communication. Without them, it’s easy to lose visibility, miss deadlines, or duplicate work, especially as teams grow or projects scale. #### Quick overview of top project management tools | **Tool** | **Best For** | **Pricing** | | --- | --- | --- | | **Jira** | Engineering-heavy, Agile teams | Free (up to 10 users) or paid from $7.53/user/month | | **Asana** | Mixed teams needing flexibility | Free or paid from $10.99/user/month | | **ClickUp** | Customizable workflows and dev+non-dev teams | Free or paid from $7/user/month | | **Monday Dev** | Product and engineering collaboration | Paid from $9/seat/month | | **Basecamp** | Small agencies preferring simplicity | Paid from $299/month flat rate (unlimited users) | #### 1. Jira ![Jira](/images/blog/dev-tools-for-agencies/jira.png) [Jira](https://www.atlassian.com/software/jira), developed by Atlassian, is one of the most widely used project management tools for software development teams. It’s built with Agile workflows in mind, offering robust support for sprints, epics, backlog grooming, and detailed issue tracking. It integrates tightly with tools like Bitbucket, Confluence, and GitHub, making it a strong fit for engineering-heavy teams. **Jira pros:** - Highly customizable workflows, boards, and issue types - Excellent for managing Agile sprints and epics - Strong ecosystem with integrations across the Atlassian suite **Jira cons:** - Can be overwhelming for smaller teams or non-technical users - Setup and admin can get complex quickly - Interface feels cluttered at times **Jira Pricing:** Free for up to 10 users; paid plans start at $7.53/user/month #### 2. Asana ![Asana](/images/blog/dev-tools-for-agencies/asana.png) [Asana](https://asana.com/) is a flexible project and task management platform built for cross-functional teams. It’s widely used by both technical and non-technical teams to plan work, assign tasks, track progress, and manage deadlines. While not developer-specific, it offers strong collaboration features and a clean interface. **Asana pros:** - Easy to use with minimal onboarding - Great for cross-functional collaboration - Supports timelines, dependencies, and project templates **Asana cons:** - Lacks native Agile or sprint management features - Limited developer-focused integrations out of the box - Can feel too generic for technical workflows **Asana pricing:** Free for individuals and small teams; paid plans start at $10.99/user/month #### 3. ClickUp ![ClickUP](/images/blog/dev-tools-for-agencies/clickup.png) **ClickUp** is an all-in-one productivity and project management platform built to replace multiple tools like docs, tasks, sprints, and time tracking. It offers high customizability and supports everything from daily task management to complex dev workflows with Agile boards, goals, and automations. **ClickUp pros:** - Highly customizable views (list, board, Gantt, etc.) - Built-in docs, time tracking, and goal-setting - Suited for both dev teams and cross-functional collaboration **ClickUp cons:** - Feature overload can be overwhelming for new users - Slower performance with large workspaces **ClickUp pricing:** Free plan with generous features; paid plans start at $7/user/mo #### 4. Monday Dev ![Monday Dev](/images/blog/dev-tools-for-agencies/monday.png) [**Monday Dev**](https://monday.com/w/dev) is a version of the Monday.com work management platform tailored specifically for product and engineering teams. It combines project tracking, sprint planning, and collaboration in a visual, highly customizable interface. It’s especially useful for teams looking to manage both technical and non-technical work in one place. **Monday Dev pros:** - Clean UI with customizable boards and workflows - Built-in roadmap, sprint, and bug tracking views - Strong collaboration across technical and non-technical teams **Monday Dev cons:** - Not as feature-rich for deep Agile workflows as Jira - Limited native Git integrations compared to dev-first tools - Can get expensive as teams scale **Monday Dev pricing:** Paid plans start at $9/seat/month; no free tier for dev-specific features #### 5. Basecamp ![Basecamp](/images/blog/dev-tools-for-agencies/basecamp.png) [**Basecamp**](https://basecamp.com/#api) is a simple, opinionated project management tool built for teams that prefer minimalism and clarity over complex workflows. It combines to-dos, message boards, scheduling, file sharing, and chat in one place, making it a solid choice for small agencies and teams looking for structure without overkill. **Basecamp pros:** - All-in-one workspace with built-in messaging and docs - Extremely easy to onboard non-technical stakeholders - Flat-rate pricing ideal for growing teams **Basecamp cons:** - No native sprint planning or Agile tools - Limited integration with Git and dev toolchains - Less automation and reporting than modern alternatives **Basecamp pricing:** Flat $299/month for unlimited users; no per-seat pricing ### The 5 best development tools The right development tools can dramatically speed up how teams build, test, and deploy applications. For agencies and product teams, these tools reduce setup time, automate repetitive tasks, and simplify infrastructure, so you can ship faster and with fewer blockers. | **Tool** | **Best for** | **Pricing** | | --- | --- | --- | | **Appwrite** | Full-stack app development with unified backend | Free or paid from $15/month | | **Appsmith** | Quickly building internal tools and dashboards | Free for self-hosted or paid from $20/user/month | | **Docker** | Creating consistent dev and production environments | Free for individuals or paid from $11/user/month | | **Postman** | Testing and debugging APIs across environments | Free for individuals or paid from $14/user/month | | **BrowserStack** | Cross-browser testing on real devices | Free trial or paid from $19/month | #### 1. Appwrite ![Appwrite](/images/blog/dev-tools-for-agencies/appwrite.png) Appwrite is an open-source alternative to Firebase and Vercel**.** It’s an all-in-one development platform that offers authentication, databases, storage, serverless functions, and even hosting. You can build, deploy, and host your application, all from a single platform. Ideal for full-stack teams and agencies looking for a complete, unified solution without juggling multiple services or paying for multiple subscriptions. **Appwrite pros:** - Fully managed cloud service - Open source and self-hostable - All-in-one development platform **Appwrite cons:** - Initial self-hosting setup requires Docker knowledge (if not using cloud) - Not framework-opinionated, so teams must make architectural decisions themselves - Some features and integrations are evolving compared to older platforms **Appwrite pricing:** Free for small applications. Paid plans start at $15 per month for production applications. #### 2. Appsmith ![Appsmith](/images/blog/dev-tools-for-agencies/appsmith.png) **Appsmith** is an open-source platform for building full-stack internal tools with drag-and-drop UI components and API/database integrations. You can quickly build dashboards, admin panels, and CRUD apps without managing the frontend or backend from scratch. **Appsmith pros:** - Fast UI building with drag-and-drop components - Connects easily to databases and REST/GraphQL APIs - Self-hosted and cloud options available **Appsmith cons:** - Not ideal for highly custom or public-facing UIs - Limited offline support - Some advanced logic may still require coding **Appsmith pricing:** Free for self-hosted and cloud community use; Paid plan starts at $20/user/month. #### 3. Docker ![Docker](/images/blog/dev-tools-for-agencies/docker.png) **Docker** is a containerization tool that lets you package applications and their dependencies into isolated, portable containers. It helps developers ensure that software runs consistently across development, testing, and production environments, without the classic “it works on my machine” issue. **Docker pros:** - Ensures consistent environments across teams and stages - Speeds up onboarding by packaging everything in one container - Widely supported in CI/CD and deployment pipelines **Docker cons:** - Learning curve for beginners unfamiliar with container concepts - Can get complex when managing multi-container apps (requires Docker Compose or orchestration tools) - Requires system resources, especially when running multiple containers **Docker pricing:** Free for individual use; Docker Pro starts at $11/user/month; Team plans from $16/user/month #### 4. Postman ![Postman](/images/blog/dev-tools-for-agencies/postman.png) **Postman** is a collaborative API platform used to build, test, document, and share APIs. It allows developers to simulate requests, automate testing, and debug endpoints without writing custom scripts, making it a go-to tool for backend teams and API-first development. **Postman pros:** - Easy to test and debug REST, GraphQL, and WebSocket APIs - Supports automated test suites and mock servers - Great for sharing API collections across teams **Postman cons:** - Can feel heavy for very simple testing tasks - Requires manual organization for complex API projects - The interface can be overwhelming for new users at first **Postman pricing:** Free for individual use; paid plans start at $14/user/month #### 5. Browserstack ![Browserstack](/images/blog/dev-tools-for-agencies/browserstack.png) **BrowserStack** is a cloud-based testing platform that lets you test your websites and applications across thousands of real browsers, devices, and operating systems. It eliminates the need for maintaining your own device lab and makes cross-browser testing fast and reliable for frontend teams. **Browserstack pros:** - Access to 3,000+ real devices and browser combinations - No setup required, test instantly in the cloud - Supports manual, automated, and visual testing **Browserstack cons:** - Can get expensive for small teams or freelancers - Slight latency on interactive live testing sessions - Limited customization compared to local environments **Browserstack pricing:** Free trial available; paid plans start at $19/month for individuals and scale with features and usage ### Conclusion Picking the right tools is essential to how fast and effectively your team can build and ship software. From planning and collaboration to development, testing, and deployment, every stage benefits when your tooling is reliable, integrated, and easy to use. This list includes a mix of proven and modern tools that help you do exactly that. Whether it’s managing tasks, testing APIs, running containers, or building internal dashboards, the goal is the same: ship faster with fewer blockers. And if you're looking for a complete all-in-one development solution that covers everything from authentication to hosting, without the need for stitching together multiple services, then check out Appwrite and start building for [free](https://cloud.appwrite.io/console). --- ## 5 VS Code extensions that replace entire development tools https://appwrite.io/blog/post/5-vs-code-extensions-that-replace-entire-dev-tools Look at your system tray and count the development tools running. It adds up fast; each tool consumes memory, requires updates, and competes for your attention. Over the past year, I have replaced most of these tools with VS Code extensions. Not basic replacements, but sometimes, complete alternatives that made me uninstall the original applications. In this article, I'll show you five VS Code extensions that can serve as alternatives to standalone development tools, plus a bonus that might save you a subscription. Let's explore how they work and when they make sense as replacements. ### Thunder Client: An alternative to Postman So many people love Postman. It's a great tool for API development and testing. But it's also a separate application from our development environment, which might introduce unnecessary context switching. With the VS Code [Thunder Client extension](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client), you can replace Postman with a comparable experience directly within your development environment. Thunder Client is a lightweight alternative to Postman. It also has features like Collections, CI/CD integration, local storage and Git Sync. And for Postman users, the familiar interface makes the transition straightforward. ![Thunder Client](/images/blog/5-vs-code-extensions-that-replace-entire-dev-tools/thunder-client.png) It's worth noting that Thunder Client has paid plans and some limitations to the free version. For example, the free plan specifies "**non Commercial use**" and Collection Runs are limited to 30, while Postman limits to 25. So the main advantage is that you can use it directly in VS Code without needing to context switch or dedicate computer resources to a separate application. Migration tips from experience: 1. Export Collections from Postman: Postman collections can be exported as JSON files and then imported into Thunder Client. 2. Reconfigure Environment Variables: Thunder Client uses a different structure for environment variables. While Postman supports dynamic environments, Thunder Client requires setting these variables in its Environment tab. 3. Take full advantage of Thunder Client's VS Code integration: Quick-pin collections, manage environment variables through .env files, and use keyboard shortcuts. Thunder Client works well for API development and testing. Postman offers additional features like monitoring and mock servers, so consider your needs when choosing between them. ### BlackBox AI: ChatGPT alternative for VS Code If you've been going back and forth between ChatGPT and VS Code, [BlackBox AI](https://marketplace.visualstudio.com/items?itemName=Blackboxapp.blackbox) is another AI coding assistant that works directly in VS Code. It can help you with code suggestions, explanations, and improvements. Yes, Copilot exists and it's now free to an extent for many developers. So, you can either use that or BlackBox AI. But what makes BlackBox AI unique is that it functions out of the box and requires no signup. It also works great for almost any code-related tasks, including chat, code generation, suggestions and reviews. ![BlackBox AI](/images/blog/5-vs-code-extensions-that-replace-entire-dev-tools/blackbox.png) One of the main advantages to ChatGPT is context awareness. BlackBox AI sees your whole project structure when making suggestions. It works well for developers who want AI assistance without leaving their editor. ### Time Master: Alternative to Toggl and RescueTime Many people use Toggl and RescueTime for time tracking. It's a way to understand how you spend your time and how productive you are. Freelancers might use it for client billing, and teams can track time for project estimates and resource allocation. Toggl is a standalone application that offers time tracking, and RescueTime provides productivity analytics. However, [Time Master](https://marketplace.visualstudio.com/items?itemName=iceworks-team.iceworks-time-master), a VS Code extension, combines both approaches with a developer-specific focus. Instead of requiring manual timer starts and stops, it automatically generates metrics, insights and time tracking reports from your VS Code activity. A typical Time Master report looks like this: ![Time Master](/images/blog/5-vs-code-extensions-that-replace-entire-dev-tools/time-master.png) The extension recognizes different types of development work and categorizes each automatically. Unlike RescueTime's window tracking, Time Master watches file changes and development activities. This can help you with more accurate reports for programming work. Time Master lacks some features from the standalone apps - mobile apps, client billing, and detailed charts. But for pure development tracking, you might find it more useful than the other timers. Also, the fact that it's right in VS Code, and you don't have to do anything complex to start tracking is very useful. ### Dendron: Alternative to Notion and Obsidian Knowledge management tools like Notion and Obsidian work well for general note-taking, but [Dendron](https://marketplace.visualstudio.com/items?itemName=dendron.dendron) takes a developer-first approach. Dendron is built specifically for managing technical knowledge. It handles everything from documentation to meeting notes right in VS Code. The extension uses a hierarchical structure that will feel familiar to developers: ![Dendron](/images/blog/5-vs-code-extensions-that-replace-entire-dev-tools/dendron.png) The key difference from tools like Notion is that everything is plain markdown files that work with git, and it's right in your editor. This means version control for your notes, blame history for edits, and the ability to use any text editor. Notes stay with your code, making documentation feel like a natural part of the codebase. You get features like quick lookups, backlinks, and structured hierarchies right where you write code. **What you trade:** Notion's real-time collaboration and polish, Obsidian's graph visualizations. **What you gain:** Developer-focused features like note references (similar to code imports), mermaid diagrams, and math equations through KaTeX. Plus, everything stays fast even with thousands of notes. ### GitDoc: Google Docs-style auto-saving for code While not replacing a standalone app entirely, [GitDoc](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gitdoc) transforms how code versioning works. It brings Google Docs-style automatic saving to your code; every save creates a git commit automatically. Here's what this means in practice: ```js // Edit your code const getUserData = () => { // Save the file with Ctrl+S // GitDoc creates a commit automatically } // Make another change const getUserData = () => { const data = fetchData() // Save again // Another commit is created } ``` The extension only commits error-free code by default, so you won't accidentally preserve broken states. For specific branches or files, like documentation or school work, you can enable permanent auto-commits. For feature work, turn it on temporarily to track how your changes evolve. GitDoc doesn't try to replace full git clients. Instead, it makes version control feel more natural - just save your file and a version is preserved. When needed, you can still squash commits or undo changes using regular git commands. ### Error Lens vs traditional error trackers Many developers use Rollbar and Sentry to track and monitor application errors in production. These tools collect, aggregate, and help debug issues that users encounter. [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens), a VS Code extension, serves a different purpose by focusing on development-time errors. Error Lens doesn't try to replace production error trackers like Sentry. Instead, it improves how you see problems in your code editor. The extension highlights entire lines that contain errors, warnings, or other diagnostic messages, and displays the full error text right next to your code. ![Error Lens](/images/blog/5-vs-code-extensions-that-replace-entire-dev-tools/error-lens.png) Error Lens makes development errors more visible by: - Displaying error messages inline instead of requiring you to hover - Adding clear visual indicators for different types of issues - Letting you quickly search for solutions or select problematic code ranges The immediate feedback loop can change your development patterns and help you fix problems during coding rather than having them show up in the console or in error dashboards later. ### Making the switch Replacing standalone tools with VS Code extensions brings several advantages: - **Reduced context switching**: Your development environment stays focused in one place. No more jumping between applications to check API responses or review documentation. - **Lower resource usage**: Fewer applications running means less memory usage and better system performance. VS Code extensions typically use fewer resources than standalone applications. - **Simplified updates**: Extensions update through VS Code's built-in system. You no longer need to manage updates for multiple development tools separately. - **Consistent interface**: Working within VS Code provides a familiar environment. The learning curve for new tools decreases when they share VS Code's interface patterns. That said, consider these factors before making the switch: - **Feature requirements**: Review the features you actually use in standalone tools. Some specialized features might not be available in VS Code extensions. - **Team compatibility**: If you work with a team, ensure everyone is comfortable with the transition. Some team members might prefer standalone tools they're familiar with. - **Performance impact**: While extensions generally use fewer resources than separate applications, too many extensions can slow down VS Code. Monitor performance and disable extensions you're not actively using. - **Backup plans**: Keep standalone alternatives available for critical tools. If an extension stops working or needs updates, you'll want a backup option. ### Final thoughts VS Code extensions have matured to the point where they can replace many standalone development tools. The benefits of a unified development environment often outweigh the limitations of extensions. Start small when making the transition. Replace one tool at a time and evaluate how it affects your workflow. You might find that some tools work better as extensions while others are worth keeping as standalone applications. Choose the combination of tools that works best for your specific needs and working style. And if you haven't seen these [10 new Git commands](https://appwrite.io/blog/post/10-git-commands-you-should-start-using?dofollow=true) that make your workflow faster and more flexible, check them out [here](https://appwrite.io/blog/post/10-git-commands-you-should-start-using?dofollow=true), along with our article that compares [Deno 2 vs Bun](https://appwrite.io/blog/post/deno-vs-bun-javascript-runtime); choosing the right runtime can make a big difference in how your applications perform and scale. ### More resources - [Building custom authentication flows with Appwrite](https://appwrite.io/blog/post/building-custom-auth-flows?dofollow=true) - [15 Git command line tips every developer should know](https://appwrite.io/blog/post/15-git-cli-tips?dofollow=true) - [Flutter vs React Native: Which framework is best for your app in 2025?](https://appwrite.io/blog/post/flutter-vs-react-native?dofollow=true) --- ## 7 practical steps to achieve GDPR compliance for your startup https://appwrite.io/blog/post/7-steps-to-achieve-gdpr-compliance-for-startups As your startup scales and collects more user data — especially if your audience includes users from the European Union — you need to ensure your application complies with GDPR ([General Data Protection Regulation](https://gdpr-info.eu/issues/personal-data/)). Not just because non-compliance could result in hefty fines, but because respecting user privacy builds long-term trust, which is important for sustainable growth. GDPR sets clear rules on how personal data should be handled and gives EU users greater control over their information. In this guide, we'll walk you through 7 practical steps to make your application or platform GDPR-compliant, without slowing down your development or stifling your business’s potential. ### What activities are illegal under GDPR? First off, you need to understand what you can and cannot do under GDPR. These regulations are pretty strict, so make sure you know how to stay compliant with GDPR: 1. **Collecting data without consent**: You must obtain explicit, informed consent before collecting personal data. Passive consent or pre-ticked boxes are not acceptable. 2. **Using data for unspecified purposes**: Personal data must be collected for specific, legitimate purposes and cannot be repurposed without additional consent. 3. **Neglecting user rights**: GDPR grants users rights to access, rectify, delete, and restrict their data. Ignoring these requests or failing to comply with data portability and deletion requests is illegal. 4. **Failing to implement adequate security**: Personal data must be protected with encryption and secure protocols. Inadequate security measures or failing to notify authorities of a breach are violations. 5. **Sharing data with unauthorized third parties**: You must have Data Processing Agreements (DPAs) in place when sharing data with third parties and ensure proper safeguards for international data transfers. 6. **Retaining data longer than necessary**: Data should not be kept longer than needed for its intended purpose. Implement clear data retention and deletion policies. 7. **Processing sensitive data without justification**: Special categories of personal data require explicit consent or other legal justifications for processing. To help you stay compliant and build user confidence, here are 7 essential steps for creating a GDPR-compliant web or mobile app. ### How to ensure your startup is GDPR-compliant #### 1. Understand what data you’re collecting The first step is to examine what personal data your app collects, such as: - Names, emails, and phone numbers - Location data - Device identifiers and IP addresses - Behavioral data, such as user preferences or interactions within the app Under GDPR, only collect data that is necessary for your application’s functionality, avoiding excess. This is known as data minimization and ensures you’re not gathering more information than you need. #### 2. Get explicit user consent Before collecting any personal data, you must get explicit consent from users. To meet GDPR requirements: - Use simple and clear language when asking for consent. - Inform users about what data you’re collecting, why you’re collecting it, and how it will be used. - Provide an option for users to opt-in and ensure they can withdraw consent just as easily. For example, if your app or website tracks user behavior for analytics or advertising, users need to actively opt into these practices. #### 3. Change your cookie policy To comply with GDPR, you need to rethink how your app handles cookies, ensuring users have full control over what’s tracked and stored. Cookies that collect personal data require clear user consent before they can be activated. Here’s how to stay compliant: - Ask for explicit consent before placing any non-essential cookies. Make sure users can accept or reject them easily. - Use a cookie banner that explains what cookies are used for and provides options to manage cookie preferences. - Allow users to revoke consent at any time through a simple, accessible interface. Avoid vague language and pre-checked boxes that mislead users. Few things erode trust faster than making new users sift through multiple pop-ups just to opt-out of cookie and data collection. #### 4. Encrypt all personal data Encrypting personal data is a crucial step for GDPR compliance and a smart way to keep your users' information safe. Encryption transforms data into a secret code, so even if someone gets unauthorized access, they can’t make sense of it. Here’s how to get it right: - [Encrypt data](https://appwrite.io/docs/advanced/security/encryption) in transit using protocols like TLS (Transport Layer Security). This keeps data secure while it’s being sent between users and your servers. - Encrypt data at rest with strong algorithms to protect it while it’s stored, whether in your database or backup files. - Keep encryption keys safe by following best practices for their storage and management. Regularly update and protect these keys to prevent unauthorized access. #### 5. Let users control their data GDPR grants users rights over their personal data, including the ability to access, correct, or delete it. To comply: - Provide users with a way to access the data you’ve collected on them. - Implement features that allow users to modify inaccurate data. - Offer an easy way for users to request data deletion (the right to be forgotten). Your app can integrate a data management portal that allows users to manage their personal information directly. #### 6. Enhance the security of user authentication To protect user data, you must ensure that your app uses strong security measures during authentication and authorization. This includes: - Implementing **multi-factor authentication (MFA)** to add an extra layer of security for logging in. - Using **encrypted tokens** or [**OAuth2**](https://appwrite.io/docs/products/auth/oauth2) for secure authorization processes. - Enforcing strong password policies to prevent weak credentials like [password hashing](https://appwrite.io/blog/post/password-hashing-algorithms) or [common passwords](https://appwrite.io/blog/post/goodbye-plaintext-passwords). These practices not only comply with GDPR’s requirements for secure data handling but also reduce the risk of breaches and unauthorized access. #### 7. Respond to data breaches quickly In the event of a data breach, GDPR requires that you notify the relevant data protection authorities within 72 hours and inform affected users if the breach poses a risk to their privacy. Here’s how it’s best to handle it: - **Develop a data breach response plan** that outlines steps for identifying, reporting, and addressing breaches quickly. - **Set up internal procedures** for monitoring data security so you can catch breaches early and minimize damage. - **Prepare a communication strategy** to notify both authorities and users promptly, ensuring transparency and compliance. By having a response plan in place, you can act fast in the event of a breach, stay compliant with GDPR, and maintain user trust. ### Conclusion Creating a GDPR-compliant app for your tech startup doesn’t have to be overwhelming. By focusing on transparency, security, and giving users control over their data, you can navigate compliance smoothly. Not only does this protect you from legal risks, but it also fosters trust with your users, building long-term success for your business. --- ## A recap of Init. The Appwrite community at its best https://appwrite.io/blog/post/a-recap-of-init Init has come to an end, and we’re happy how all of you showed up and made it an amazing week filled with product announcements, events, content, celebrations, a release, and most of all, community fun! In this blog, we will recap on what happened during Init and highlight some of our favorite moments and comments, and share all the content for each day. Let’s dive in. ### Announcing the start of something new For months, we have been working on creating all of the elements that make Init come alive. We spent many hours creating and planning the tickets, the website, the videos, the swags, Discord sessions, and all other content. We did everything in-house, which brought some challenges as we had to learn on the spot, but we had a lot of fun doing it, and the results were beyond our own expectations. We even filmed and produced five videos starring the Appwrite team. For most, it was also the first time being in front of the camera. Here is a [sneak peak of behind-the-scenes footage](https://www.instagram.com/reel/C4FvPehvxs5/) of the team working on the Init announcement videos. ![behind the scenes](/images/blog/a-recap-of-init/init1.png) ![behind the scenes](/images/blog/a-recap-of-init/init2.png) ![behind the scenes](/images/blog/a-recap-of-init/init3.png) ![behind the scenes](/images/blog/a-recap-of-init/init4.png) #### The fun begins And then, finally, we got to [announce Init](https://appwrite.io/blog/post/announcing-init) to all of you and have you join in the fun and create your tickets. ![Init begins](/images/blog/a-recap-of-init/init5.png) Many of you asked us, but what is Init? [Dennis explains](https://www.youtube.com/shorts/Brex4HwiKqU). Of course we understood we needed some more explaining to do, about what Init actually is. So, we prepared an announcement video, which was the most exciting part of the pre-campaign. This was a first for Appwrite, and we loved sharing this moment with you. {% youtube src="https://www.youtube-nocookie.com/embed/5NtrYks2dqE?si=0vjkBCZYg8yf2GUW" thumbnail="/images/blog/a-recap-of-init/thumbnails/thumbnail-1.png" /%} Of course, we designed Init swag for you to win and The Appwriter. Didn’t win an Appwriter? No worries, it will be coming to the [Appwrite Store](https://appwrite.store/) soon. ![Appwrite Init giveaway](/images/blog/a-recap-of-init/init6.png) #### Special guest line up But what would Init be without special guests? We invited friends and creators of Appwrite to join us during a Discord session each day to come geek out with us and join in the celebration. The lineup speaks for itself. ![Special guest line up](/images/blog/a-recap-of-init/init7.png) ![Reactions to announcing the line ups](/images/blog/a-recap-of-init/init8.png) ### Init begins After a week of pre fun it finally was time to make our first announcement. This product was one that was highly anticipated, and we holding our breath to share it with the world. So what did we kick off with? ##### The first of many On day 0 of Init, we brought you Messaging, a massive announcement as this was a product many of you have requested as it is a requirement for many applications, being able to communicate with your users. {% youtube src="https://www.youtube-nocookie.com/embed/w-izHSKXqtU?si=OV30JUel_Zoq10AU" thumbnail="/images/blog/a-recap-of-init/thumbnails/thumbnail-messaging.png" /%} The reactions are what we hoped for and made day 0 of Init a great start to the week. ![Reactions to Messaging](/images/blog/a-recap-of-init/init9.png) ![Reactions to Messaging](/images/blog/a-recap-of-init/init10.png) ##### Discord session We kicked off the first Discord session with Appwrite Hero and Vonage dev advocate [Diana Pham](https://twitter.com/dianasoyster) to talk about Messaging. As always, the community around Appwrite showed up in mass and made it a great hour of fun and learning! ![Discord session turn out](/images/blog/a-recap-of-init/init11.png) ##### Content list - [Best practices for sending push notifications](https://appwrite.io/blog/post/push-notifications-best-practices) - [How Twilio simplifies messaging for developers](https://appwrite.io/blog/post/simplify-messaging-twilio) - [Product tour video](https://www.youtube.com/watch?v=QdDgPeuBZ1I) #### Another day On day 1 we continued our same flow and announced Improved support for [server-side rendering.](https://appwrite.io/blog/post/introducing-support-for-server-side-rendering) {% youtube src="https://www.youtube-nocookie.com/embed/jeL4cSovOBA?si=0tMDecmUucWOYASg" thumbnail="/images/blog/a-recap-of-init/thumbnails/thumbnail-ssr.png" /%} This improvement was more than welcome. ![Welcome SSR](/images/blog/a-recap-of-init/init12.png) ##### Fund and knowledge For day 1 we invited [Hitesh Choudhary](https://twitter.com/Hiteshdotcom) to come and join us on stage to talk about SSR. ![Hitesh having fun](/images/blog/a-recap-of-init/init13.png) ![Community fun](/images/blog/a-recap-of-init/init14.png) ##### Content list - [SSR vs CSR with Next.js](https://appwrite.io/blog/post/csr-vs-ssr-with-nextjs) - [Product tour](https://youtu.be/7LN05c-ov_0) - [Announcement SSR](https://appwrite.io/blog/post/introducing-support-for-server-side-rendering) #### Safety first Day 2 of Init was all about safety and the dev experience. Two-factor authentication and Enum SDK support were announced. {% youtube src="https://www.youtube-nocookie.com/embed/hpdUXOFay4M?si=TPO8CistphsTOCas" thumbnail="/images/blog/a-recap-of-init/thumbnails/thumbnail-2fa.png" /%} As Eddie mentioned, with just a few lines of code you can now add 2FA to your apps. ![Eddie responding to 2FA](/images/blog/a-recap-of-init/init15.png) With Enum SDK support we make our commitment to improving your developer experience valid. ![James loving Enums](/images/blog/a-recap-of-init/init16.png) ##### Last minute guest We got a curve ball thrown at us on day 2 of Init, as our guest for the day had to cancel. But luckily we had [Francesco Ciulli](https://twitter.com/FrancescoCiull4) from daily.dev join us last minute to save the day. ![Francesco joining us last minute](/images/blog/a-recap-of-init/init17.png) ##### Content list - [Product tour 2FA](https://youtu.be/OWRju8ZZuQ8) - [Announcement 2FA](https://appwrite.io/blog/post/announcing-two-factor-authentication) - [From passwords to protection: Implementing 2FA in your applications](https://appwrite.io/blog/post/password-protection-2fa) - [Announcement Enum SDK support](https://appwrite.io/blog/post/introducing-enum-sdk-support) - [Effective use of enums in API design](https://appwrite.io/blog/post/enums-api-design) - [Enhancing type safety in software development with enums](https://appwrite.io/blog/post/enhancing-type-safety) #### More querying! What is a database without queries? Not a lot! The introduction of new Database operators for Appwrite Databases makes all the difference and gives you more control and flexibility when writing queries. {% youtube src="https://www.youtube-nocookie.com/embed/73flN6mZqAs?si=3hUI26G9DDVDzJG4" thumbnail="/images/blog/a-recap-of-init/thumbnails/thumbnail-db.png" /%} ![Community highlight](/images/blog/a-recap-of-init/init18.png) For day 3 of Init, we had [Danny Thompson](https://twitter.com/DThompsonDev) join us. Not only did he have great horror SQL stories, he also shared some helpful insights. Soon, we will share the videos of each Discord session. ![Danny joining us](/images/blog/a-recap-of-init/init19.png) ##### Content list - [Database operators announcement](https://appwrite.io/blog/post/introducing-new-database-operators) - [Product tour video](https://youtu.be/IMgl9f_iht4) - [Understanding data queries in database management](https://appwrite.io/blog/post/understand-data-queries) #### The end and the beginning The last day of Init was a proper celebration. We started off with our last announcement: [New and updated runtimes.](/blog/post/announcing-more-and-updated-runtimes) But the highlight of the day was the closing party we held on Discord with our friend from open-sauced, [Nick Taylor](https://twitter.com/nickytonline). ![Fun with Nick during the closing party](/images/blog/a-recap-of-init/init20.png) The closing party got an extra special touch with five giveaways to celebrate. And if you ever wondered who holds the power of the giveaway, according to Francesco it’s Aditya. ![Aditya the giveaway man](/images/blog/a-recap-of-init/init21.png) ##### Content list - [New runtimes announcement](https://appwrite.io/blog/post/introducing-enum-sdk-support) - [Sound null-safety for your Dart functions](https://appwrite.io/blog/post/sound-null-safety-for-your-dart-functions) - [Why you need to try the new Bun function runtime](https://appwrite.io/blog/post/why-you-need-to-try-the-new-bun-runtime) ### Thank you all for joining Init is the start of something new and we hope we have inspired you to get started on your next project. We hope to see you at the next Init! Do you have ideas on how to make it better? Share your feedback with us by filling out [this Typeform](https://t.co/OEaBMMGblT). --- ## How Pink Design helped us improve web accessibility https://appwrite.io/blog/post/accessibility-in-pink-design When creating products, accessibility can be an afterthought. Understandably, we want to ship our products fast and deliver value to our users. We might think that accessibility is needed for edge cases and therefore not prioritize it. It's good to be reminded that the World Health Organization (WHO) estimates that 16% of the global population has some form of disability (Dec 2022).Ignoring such a significant part of your user base simply doesn't create a good user experience.Creating accessible products is everyone's responsibility. Designers, developers, content authors, and whoever else is involved in creating products should do their part and strive towards achieving a better experience for everyone. It's not always easy to maintain a high level of accessibility, but it's definitely easier with a design system. The components we created in Pink Design, Appwrite's fully open source UI library, have an accessibility level of AA. This is the recommended level of accessibility for most products. To ensure our products will maintain a high accessibility level, we did the following: - Use high color contrast - Not relying on color - Allow keyboard navigation - Define font size in REM - Allow users to reduce motion ### Use high color contrast Color contrast might be the first thing that comes to mind when thinking about accessibility. A lack of contrast between the text and background might mean some people would be unable or have difficulty reading the text. Similarly, bright colors with high luminance are not readable for others. W3C recommends a contrast ratio between text and background of 4.5 to 1 for conformance level AA. | Item | Price | # In stock | |--------------|-----------|------------| | Apples | 1.99 | *7* | | Bananas | **1.89** | 5234 | ### Not relying on color The term “color blindness” is often used to describe people who have trouble identifying and distinguishing between certain colors, but color blindness, the inability to see any color, is extremely rare. According to the United Kingdom National Health Service (NHS), red-green color blindness affects 1 out of 12 men and 1 out of 200 women. People with this color vision deficiency may have difficulty differentiating between reds, oranges, yellows, browns, and greens. They also might find it hard to distinguish between shades of purple and may confuse red with black.Similarly, people with “blue-yellow” color vision deficiency may have difficulty differentiating between blues, greens, and yellows. We use four system colors in Pink Design — red, orange, green, and blue. Each of these colors represents a state in the Appwrite Console — red indicates an error or danger, orange indicates a warning, green indicates success, and blue indicates information. Knowing the difficulties our users might face while seeing these colors, we don't rely on color to make critical information understandable. ### Allow keyboard navigation People with fine motor control restrictions or disabled hands or arms will be unable to use a mouse. In Pink Design, we provide distinct states for interactive elements. By designing states like focus, hover, and active, we provide the ability to navigate all interactive elements with a keyboard. This is not only an accessible experience but also a better experience for all users who prefer keyboard navigation, including Appwrite's developer community. It is possible to enhance accessibility through development as well. In collaboration with our engineering team, we decided to incorporate the following into Pink Design: ![appwrite dashboard](/images/blog/accessibility-in-pink-design/cover.png) ### Define font size in REM Browsers have a default font size that users can change via the browser setting. A pixel is an absolute unit for fixed sizes and spaces that ignores browser settings. This means that if we are using pixels and a user (with or without vision impairment) changes the font size in their browser settings, their setting won't affect our product. That being said, pixels should not cause any problems if the user zooms in, but we make no assumptions about users' preferences. This is why we decided to define the font size in REM, which is a relative unit. ```client-web import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID const account = new Account(client); const promise = account.createVerification({ url: 'https://example.com' }); promise.then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` ### Allow users to reduce motion There is no doubt that animations are a nice addition to every product, but animations can also distract people. In some cases, animations can cause dizziness, vertigo, or epileptic seizures. Users that are sensitive to motion might choose to reduce motion in their operating system settings. In this case, we should skip the animation for them. In Pink Design, we decided to create a big animation to show the functionality of the library on the landing page. The animation is 10 seconds long and is the first thing you see on the page. It starts immediately when the page is loaded, but if “reduce motion” is enabled in the operating system, the animation skips to the end. --- ## Add a search function to your application https://appwrite.io/blog/post/add-a-search-function-to-your-app Function templates are pre-built Appwrite Functions that can be integrated into your Appwrite project with just a few clicks. Using them, you can easily incorporate new features and integrations into your app without writing additional code or managing infrastructure. One such integration you can implement using Appwrite Functions is **Searching** using **Meilisearch**. In this blog we show how you can use an Appwrite Function Template to integrate search with Meilisearch. ### Setting up the Template Meilisearch is a flexible and powerful user-focused search engine that can be added to any website or application. The purpose of this function template is to sync documents in an Appwrite database collection to a Meilisearch index. Using this function template, users can explore, search, and retrieve information from the connected database collection. Through this template, documents from the Appwrite collection are systematically indexed within Meilisearch. To use the function, you need the following set of keys: - `APPWRITE_KEY` - API Key to talk to Appwrite backend APIs.To generate API Keys you can follow the documentation [here](https://appwrite.io/docs/getting-started-for-server#apiKey) - `APPWRITE_ENDPOINT` - To get the Appwrite endpoint, you need to go to [Appwrite](https://cloud.appwrite.io/) and find it under “Settings” - `APPWRITE_DATABASE_ID` - The ID of the Appwrite database that contains the collection to sync. You can find the documentation [here](https://appwrite.io/docs/databases). - `APPWRITE_COLLECTION_ID` - The ID of the collection in the Appwrite database to sync. To use Meilisearch, you can either self-host it using the command 👇 ```bash curl -L [https://install.meilisearch.com](https://install.meilisearch.com/) | sh ``` Or use [Meilisearch Cloud](https://www.meilisearch.com/cloud). For this example, we will assume that you are using Meilisearch Cloud. Here’s the keys you need: - `MEILISEARCH_ENDPOINT` - This is the host URL of the Meilisearch server. Once you have logged in to Meilisearch Cloud and created a new project, you will find the URL under “Overview” and this is how it should look like 👇 ![Overview Meilisearch](/images/blog/add-a-search-function-to-your-app/functions.png) - `MEILISEARCH_ADMIN_API_KEY` - This is the admin API key for Meilisearch. You will find it in the Meilisearch Console under “API Key”. - `MEILISEARCH_SEARCH_API_KEY` - This is the API Key for Meilisearch search operations. To get this, you need you create a new index from the Meilisearch Console. Once created you will find it under `Overview` as `Default Search API Key` ![Creating an index from the Console](/images/blog/add-a-search-function-to-your-app/variables.png) ![Keys](/images/blog/add-a-search-function-to-your-app/connect.png) - `MEILISEARCH_INDEX_NAME` - Name of the Meilisearch index to which the documents will be synchronized. For e.g, in the above picture, the Index name is `Newindex`. You can also find it under `Settings` as `Index Name`. #### Preparing the Function The function template syncs documents in an Appwrite database collection to a Meilisearch index. It should get you up and running, but you will need to add real data to build a useful search index. If you want to see the source code, you can find it on our [templates GitHub repository](https://github.com/appwrite/templates/tree/main/node/sync-with-meilisearch). Now, let’s navigate to our functions page on **[Appwrite](https://cloud.appwrite.io/)**. From there, we will select the **Templates** tab, search for and select the **Sync with Meilisearch** function template. ![templates](/images/blog/add-a-search-function-to-your-app/templates.png) The function requires `APPWRITE_API_KEY`, `APPWRITE_DATABASE_ID`, `APPWRITE_COLLECTION_ID` , `MEILISEARCH_ENDPOINT`, `MEILISEARCH_ADMIN_API_KEY`, `MEILISEARCH_SEARCH_API_KEY`, `MEILISEARCH_INDEX_NAME`. Once you have added them you can proceed to the Connect step. Select **Create a new repository** (this will generate a GitHub repository for you with the function), and leave the production branch and root settings as default to create this function. ![connect](/images/blog/add-a-search-function-to-your-app/connecttemplate.png) ### Testing the Template Visit the **Domains** tab on the function page and copy the domain URL to test the function. ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/va05oaxklo04x6xh6fka.png) ### Conclusion We’ve added search functionality to our app and opened up many possibilities to improve the experience of our app’s users. How can the *template* be extended ? - Using events to automatically index new collections - Using weights and other meilisearch features to optimise search such as excluding certain fields from indexing Some examples are: 1. **Real-time Data Exploration:** It can be used to provide real-time search capabilities for datasets and data streams, allowing users to explore and analyze data in real-time. 2. **Content Management Systems:** The function template can be integrated into content management systems (CMS) to facilitate efficient content retrieval for editors and site visitors. Be sure to check out the other available Function Templates. We’ve created multiple that could be of use in your projects. You can find the [templates GitHub repository here](https://github.com/appwrite/templates). For more information about Appwrite and Appwrite Functions: 1. **[Appwrite Function Docs](https://appwrite.io/docs/functions)**: These documents provide more information on how to use Appwrite Functions. 2. **[Functions Announcement](https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f)**: Read the full announcement on Functions 1.4. 3. **[Appwrite Discord](https://discord.com/invite/appwrite)**: Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## How to add Figma OAuth2 login to your app with Appwrite https://appwrite.io/blog/post/add-figma-oauth2-appwrite Figma's API opens up some interesting doors, such as automating workflows, syncing design tokens, and bringing live design previews into your app. But before you can do any of that, your users need to connect their Figma account. If you're using Appwrite for authentication, this process is simpler than you might expect. In this guide, we'll walk through everything you need to do to support Figma OAuth2 login inside your Appwrite project. We'll cover both the configuration and the actual login flow, then show how to securely access Figma's API on behalf of the user. You won't need any server-side code because Appwrite handles the OAuth2 flow for you, and sessions are maintained like any other login provider. ### Understanding the OAuth flow in Appwrite When a user attempts to sign in with Figma, Appwrite starts a standard OAuth2 flow: 1. It redirects the user to Figma with the necessary query parameters (`client_id`, `redirect_uri`, `scope`, and `state`). 2. Once the user grants access, Figma sends back an authorization code. 3. Appwrite exchanges that code for an access token and refresh token. 4. A new Appwrite session is created for the user. 5. That session includes the user's identity and also stores the Figma tokens, so your frontend can later call the Figma API if needed. The access token is used to make requests to Figma's API. Appwrite also stores the refresh token and allows you to renew the access token when it expires. This is optional and only necessary if you continue to use Figma's API beyond the initial login. ### Creating your OAuth app in Figma To connect your Appwrite project to Figma, the first step is to register your app on the [Figma Developer Portal](https://www.figma.com/developers/apps). This allows you to request access to a user's Figma data through OAuth2. Click **"Create a new app"** in the top right. You'll be asked to fill in a few details: - **Name** - This will be shown to users during login. - **Website** - A link to your app or landing page. - **Logo** - Optional, but it helps users recognize your app in the consent screen. ![Figma Create App](/images/blog/add-figma-oauth2-appwrite/figma-create-app.png) Once you save the app, Figma will show your **Client ID** and **Client Secret**. These are the credentials you'll need to complete the setup in Appwrite. ![Figma Client ID and Secret](/images/blog/add-figma-oauth2-appwrite/figma-client-info.png) Take a moment to copy both values. The **Client Secret** is shown only once, so make sure to save it somewhere you can reference later. Now that the app is registered, find it in your list of apps and click to open it. Inside the modal, switch to the **OAuth 2.0** section and click **Add a redirect URL**. This is where you'll paste the callback URL that Appwrite generates in the next step. Keep this tab open. We'll come right back to it. ### Enabling Figma as a provider in Appwrite Now let's switch over to Appwrite. 1. Open the [Appwrite Console](https://cloud.appwrite.io/) and choose your project. 2. In the left-hand sidebar, click **Auth**. 3. Inside the Auth section, select the **Settings** tab. 4. Scroll down to the **OAuth2 Providers** section. 5. Click on **Figma**. This opens a configuration panel where you'll see: - A toggle to enable the Figma provider. - Input fields for **App ID** and **App Secret**. - A read-only **Redirect URI** that Appwrite will use for the callback. Copy the Redirect URI and return to your Figma app configuration. Under **OAuth 2.0**, paste it into the redirect URL field and save the changes. Once that's done, head back to Appwrite, paste in your **Client ID** and **Client Secret**, enable the toggle, and click **Update**. ![Appwrite Figma Provider](/images/blog/add-figma-oauth2-appwrite/appwrite-figma-oauth-screen.png) At this point, Figma is connected to your Appwrite backend. ### Logging in from your frontend Once Figma is enabled as a provider, you can trigger a login using the Appwrite SDK. Here's an example using the JavaScript SDK in a React app: ```js import { Client, Account, OAuthProvider } from 'appwrite' const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('your-project-id') const account = new Account(client) function signInWithFigma() { account.createOAuth2Session({ provider: OAuthProvider.Figma, success: '', failure: '', scopes: ['files:read'] // scopes (optional) }) } ``` This will redirect the user to Figma's consent screen. Once they log in and approve access, they'll be returned to your app with an active Appwrite session. ### Calling the Figma API (if needed) If your app just needs Figma for login, you don't have to think about the Figma token again. Appwrite already pulled the identity info (name, email, etc.) when the session was created. But if you want to call Figma's API after the initial login, either to fetch files, sync variables, or manage libraries, you'll need a valid access token. Figma's tokens expire after 24 hours, but Appwrite stores the refresh token and gives you a way to renew them: ```js async function getFigmaFiles() { // Ensure token is fresh await account.updateSession({ sessionId: 'current' }); const session = await account.getSession({ sessionId: 'current' }); const token = session.providerAccessToken const res = await fetch('https://api.figma.com/v1/files/file_key', { headers: { Authorization: `Bearer ${token}`, }, }) if (!res.ok) { throw new Error('Figma API error') } return res.json() } ``` The `updateSession()` call silently refreshes the Figma token using the stored refresh token. Your user remains logged in to your app, so they won't see any interruptions. ### What happens when the Figma token expires? Appwrite keeps the user logged in with their Appwrite session. Even after Figma's access token expires, your app's session remains valid. The only thing that stops working is the ability to make requests to Figma's API. So if your app never talks to Figma again, you don't have to refresh anything. But if you need to access the API after the token has expired, calling `updateSession()` is the way to renew it. ### Common issues to watch for **Redirect URI mismatch** If the redirect URI you added in Figma doesn't match exactly what Appwrite is using, including protocol, port, or trailing slashes, Figma will reject the login with a `redirect_uri mismatch` error. **Invalid client credentials** If you see an `invalid_client` error, it usually means your Client ID or Secret is incorrect, or you didn't copy the secret when it was first shown. **Expired token when calling Figma API** That's expected if the token has expired. Just call `updateSession('current')` before you hit the Figma endpoint again. **Scope mismatch** Make sure the scopes you request in `createOAuth2Session()` match what you enabled in Figma. Otherwise, Figma will block the request. ### Logging out To log the user out and remove access to their Figma account: ```js await account.deleteSession({ sessionId: 'current' }) ``` This clears both the Appwrite session and the associated access and refresh tokens. ### Final thoughts You don't need a custom backend to support "Sign in with Figma." Appwrite gives you a simple way to plug in Figma as an OAuth2 provider, handle authentication, and optionally access the Figma API, all with session management handled for you. If you're only using Figma for sign-in, your work is done once the session is created. But if your app needs to interact with Figma's API after that, Appwrite gives you a safe way to refresh tokens on demand. If you have any questions or run into any issues, feel free to reach out to the team through the [Appwrite Discord server](https://appwrite.io/discord). ### Further reading - [Appwrite OAuth2 docs](https://appwrite.io/docs/client/auth#oauth2) - [Figma API docs](https://www.figma.com/developers/api) - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) --- ## Add a URL shortener function to your application https://appwrite.io/blog/post/adding-url-shortener-function Appwrite Functions are user-defined functions that can start small and scale big, deploying automatically from source control. With the introduction of function templates, you can quickly add new integrations into your app without writing additional code or managing infrastructure. Function templates are pre-built Appwrite Functions that can be integrated into your Appwrite project with just a few clicks. A URL shortener takes a long or complex URL and generates a shorter, more concise version. The primary purpose of a URL shortener function is to make links easier to share, manage, and remember. For e.g https://dev.to/appwrite/introducing-appwrite-migrations-effortless-data-migration-from-your-platforms-5dhh to http://tinyurl.com/bd5usb3n In this blog, we’ll learn to build a URL shortener using Appwrite Functions template. The template benefits from the built-in scalability, reliability, and security of Appwrite Functions. You can find the source code on our [templates GitHub repository](https://github.com/appwrite/templates/tree/main/node/url-shortener). ### Setting up the Template To get started, you need to navigate to the functions page on the **[Appwrite](https://cloud.appwrite.io/)** console. From there, we will select the **Templates** tab, search for and select the **URL Shortener** function template. ![Function template](/images/blog/adding-url-shortener/functions.png) The function requires two variables: - `APPWRITE_API_KEY` - `SHORT_BASE_URL` ![Required variables](/images/blog/adding-url-shortener/variables.png) Once you have populated those, you can go straight to the **Connect** step. ![Connecting repository](/images/blog/adding-url-shortener/connect.png) Select **Create a new repository** (this will generate a GitHub repository for you with the function), and leave the production branch and root settings as default to create this function. ### Using the Function Visit the **Domains** tab on the function page and copy the domain URL to test the function. ![Domains](/images/blog/adding-url-shortener/shortener.png) Go to the function URL in your web browser, and you'll see a short url like the one shown below. ### Next steps URL shorteners often offer additional features such as tracking click-through rates, providing analytics on link usage, and allowing users to customize the shortened URL to some extent.This Function Template can be extended to perform a lot of functionalities. Some examples are: 1. **Link Analytics and Tracking:** Enhance the URL shortener to track click-through rates, geographical location of users, referral sources, and other analytics data. This could provide valuable insights into the popularity and effectiveness of shared links. 2. **Link Management Dashboard:** Create a user-friendly dashboard where users can manage their shortened links, view statistics, and perform various actions on their links. Be sure to check out the other available Function Templates. We’ve created many that could be of use in your projects. You can find the [templates GitHub repository here](https://github.com/appwrite/templates). For more information about Appwrite and Appwrite Functions: 1. **[Appwrite Function Docs](https://appwrite.io/docs/functions)**: These documents provide more information on how to use Appwrite Functions. 2. **[Functions Announcement](https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f)**: Read the full announcement on Functions 1.4. 3. **[Appwrite Discord](https://discord.com/invite/appwrite)**: Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Agentic AI vs Generative AI: A complete overview. https://appwrite.io/blog/post/agentic-ai-vs-generative-ai Let's be honest, the term AI has become overloaded. These days, no keynote or launch event goes by without someone mentioning it. We've gone from "AI-generated art" to "AI coding assistants" to "AI agents" in what feels like a few months. Every week, there's a new demo promising to replace another part of our workflow. But underneath the hype, something real is happening: A technical shift from generative to agentic systems. Generative AI was about creation. Agentic AI is about action. One predicts. The other decides. If you've been wondering what agentic AI vs generative AI really means, this blog post breaks it down for you. We'll go through what each actually does, their architectures, their real-world use cases, and why this shift matters for developers and builders. ### What is Generative AI? Generative AI (or Gen AI, as it's often shortened) refers to models that can generate new data from patterns they've learned: text, images, code, music, you name it. At its core, generative AI doesn't "think" or "reason." It predicts. It takes all the data it's been trained on and tries to produce the next most likely token or pixel sequence based on probability distributions. That's why a large language model (LLM) like GPT-4 or Claude feels conversational but doesn't truly understand context. It's incredibly good at simulating understanding through statistical patterns. You can think of it like a world-class autocomplete system: Feed it enough examples, and it'll generate something coherent, creative, and often brilliant. But it doesn't have awareness, goals, or persistence. Once it generates something, it stops there. ### Generative AI examples You've already used dozens of generative AI examples without realizing it: - **ChatGPT** — Generates text, explanations, and code based on natural language input. - **Midjourney / Stable Diffusion** — Create images from textual descriptions. - **Suno / Udio** — Produce songs from a few descriptive sentences. - **Runway / Pika** — Generate short videos and visual effects. They generate, but they don't decide what to do next. That's still up to you. ### Generative AI characteristics So what defines generative AI beyond just "it creates things"? Let's look at some key traits. - **Predictive, not proactive:** Models like GPT or Stable Diffusion are reactive. They only output when prompted. - **No goal awareness:** They don't plan steps or evaluate results. - **Stateless:** Unless you give them context windows or external memory, each prompt is independent. - **Representation learning:** They compress and abstract data patterns into meaningful latent spaces, allowing creativity without explicit rules. ### Generative AI applications Here are some real-world generative AI applications - **Content generation:** Text, art, audio, video, and code creation. - **Summarization & translation:** Synthesizing large amounts of text or converting between languages. - **Code assistance:** From auto-completion to documentation generation. - **Prototyping & ideation:** Quickly exploring variations of an idea (e.g., UX copy, design drafts). In short, Generative AI is good at creating things we can evaluate and use, but it doesn't evaluate or act on its own. ### What is Agentic AI? Now let's talk about Agentic AI. If *Generative AI* is about creation, *Agentic AI* is about autonomy. Agentic AI systems combine language models with reasoning, memory, and the ability to take actions. Instead of just producing text, they can **plan**, **decide**, **execute**, and **adapt,** often without step-by-step human guidance. Technically, *Agentic AI* builds on top of *Generative AI*. It uses the same LLM backbone for reasoning and communication, but wraps it with additional layers: - **Memory** — to retain context beyond a single conversation. - **Tools** — to interact with external systems (APIs, browsers, databases, environments). - **Planning & goal-setting** — to decompose objectives into actionable steps. - **Feedback loops** — to evaluate results and adjust strategy. Recent advancements like the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) have helped agentic systems bridge the gap between reasoning and real-world action. MCP provides a standardized way for AI models to connect with tools, APIs, and data sources, enabling them to move beyond static outputs and perform dynamic, goal-driven operations. For example, with [Appwrite's MCP Server](/docs/tooling/mcp), developers can expose Appwrite's backend capabilities, from authentication and databases to storage and functions, directly to AI agents. This allows them to not only reason and generate, but also execute real operations within live applications. ### Agentic AI examples Let's look at real *agentic AI examples* in the wild: - **AutoGPT** — One of the first frameworks to chain multiple GPT calls with goals, planning, and execution. - **Devin (by Cognition Labs)** — Who doesn't remember Devin? The first "AI software engineer" capable of autonomously writing, debugging, and shipping code. - **ChatGPT with Browse + Code Interpreter** — Not just responding, but searching the web, running code, and retrieving data. - **Cursor** — An AI-powered code editor that acts as an autonomous development partner, capable of understanding projects, writing code, fixing bugs, and iterating in real time through contextual awareness. - **ReAct / LangChain / OpenDevin frameworks** — Agentic architectures that integrate reasoning, reflection, and memory loops. Notice the difference? These systems don't stop at output. They can plan, execute, evaluate results, and adjust strategy based on the outcomes. ### Agentic AI characteristics Here are some key characteristics of agentic AI systems: - **Autonomous:** Executes sequences of tasks toward a goal without explicit next-step instructions. - **Goal-driven:** Operates based on objectives rather than prompts. - **Reflective:** Evaluates its performance and self-corrects. - **Interactive:** Uses tools, APIs, or browsers to gather new data or act. - **Persistent memory:** Can retain state and recall previous context across sessions. ### Agentic AI applications Here are some real-world applications of agentic AI systems: - **Autonomous coding:** Writing, testing, and deploying software agents (like Devin or SWE-agent). - **Workflow automation:** Monitoring pipelines, generating reports, and triggering alerts. - **Customer operations:** Agents resolving tickets, managing CRMs, or summarizing calls. - **Research assistants:** Reading papers, comparing results, summarizing trends, and proposing hypotheses. - **Data operations:** Agents cleaning, labeling, and transforming data across systems. In other words, Agentic AI takes Generative AI's creative intelligence and gives it purpose, memory, and direction. ### Agentic AI vs Generative AI: Key differences Here's a quick side-by-side comparison to crystallize it: | **Feature** | **Generative AI** | **Agentic AI** | | --- | --- | --- | | **Core function** | Generates new content | Acts and decides based on goals | | **Autonomy** | Requires explicit user prompts | Operates semi or fully-autonomously | | **Memory** | Limited (context window only) | Persistent, structured memory | | **Reasoning** | Pattern prediction | Reflective, iterative reasoning | | **Learning loop** | None. One-shot generation | Feedback loop through reflection and adjustment | | **Actionability** | Static. Outputs results | Dynamic. Executes actions via tools or APIs | | **Examples** | ChatGPT, Midjourney | Claude Code, Devin, Cursor | | **Applications** | Content creation, summarization | Workflow automation, autonomous development | | **Goal orientation** | Responds to input | Pursues objectives independently | It isn't about which is better. It's about which is more capable for a given context. If you need creativity, text generation, or ideation, then generative wins. If you need automation, decision-making, or adaptability, then agentic takes over. ### How to build an AI Agent If you're curious about how to build an AI agent, the building blocks are surprisingly approachable today. You don't need to train your own foundation model. Most developers start with an existing LLM and layer these components: 1. **Goal definition:** What is the agent trying to achieve? 2. **Planner:** Breaks the goal into tasks. 3. **Memory:** Stores short and long-term context (via vector databases or structured memory graphs). 4. **Tool use:** Integrates external APIs, databases, browsers, or code interpreters. 5. **Reflection:** Evaluates results, adjusts approach, and iterates. Start small. An agent that monitors your CI/CD pipeline or scrapes a dataset autonomously is enough to understand the shift from *prompt → plan → act.* ### Wrapping up Generative AI and Agentic AI are part of the same evolutionary path. - Generative AI focuses on generating information. - Agentic AI focuses on using that information to achieve defined outcomes. As AI systems evolve, the real differentiation will come from how effectively we combine both, using generative intelligence to create and an agentic intelligence to execute. That's where the next generation of AI applications will emerge. ### More resources - [Build AI powered applications with Appwrite](/docs/products/ai) - [Appwrite AI integrations](/integrations#ai) - [Appwrite MCP server](/docs/tooling/mcp) --- ## Top 5 tips to build an AI agent startup https://appwrite.io/blog/post/ai-agent-startup-tips With AI advancing at a rapid pace, 2025 is shaping up to be the year of AI agents. We're moving beyond basic chatbots and entering a new era where AI can autonomously solve complex tasks. So, in the near future, fully autonomous AI agents can scope out a project and complete it with all the necessary tools they need and with no help from human partners. They are evolving from just content generators to autonomous problem-solvers. This shift opens up massive opportunities for founders to build AI agents that have the ability to reason, to use tools and perform complex tasks. In this blog, we'll explain what AI agents are and give tips on how you can build a successful AI agent startup. ### What are AI agents? AI agents refer to programs or systems that use AI to perform specific tasks without human intervention. They can reason, plan, and have a memory, which shows some level of autonomy. The best part is that, unlike traditional AI workflows, which follow a fixed and pre-defined path where you have to outline every step explicitly, AI agents show autonomy in decision-making and execution. This means you just have to assign a task, and it's the AI agent's job to determine how many steps it needs to take and how it will approach the task to get the job done. For example, AI coding agents can autonomously make changes to code, run tests, and iterate on the solutions until the code is optimized. The agent doesn't know beforehand how many iterations or changes it needs to make. It's just focused on getting the best outcome. ### Applications of AI agents We can already see early glimpses of AI agents performing simple tasks like analyzing data, predicting trends, and automating workflows to some extent. However, for more sophisticated use cases, the technology has yet to mature. Here are a few applications of AI agents: #### Customer support AI agents can handle customer queries and iterate through questions and solutions until they resolve the customer's issue. Example: An AI agent interacts with a customer to troubleshoot an issue and also ask additional questions if needed. #### Coding and software development AI agents can help developers to write, modify, and test code. They can also run unit tests and iteratively improve the code until it passes. Example: A coding agent could identify bugs, suggest fixes, and run the relevant tests, all autonomously. #### Automating repetitive tasks Small tasks that are repetitive but important, such as processing data or categorizing documents, can be automated using AI agents. Example: An agent automates the process of categorizing emails or managing small, recurring administrative tasks. #### Multi-Agent systems Advanced applications where multiple agents can collaborate and interact with each other to complete more complex tasks. Example: Multiple AI agents can analyze vast amounts of financial data, providing insights and recommendations. ### Tips for building an AI agent startup With the current pace of AI advancements, where we now have better, faster and smaller models, as compared to the past 15-18 months, the stage is set for the next wave of AI agent development. Many large companies and startups are already experimenting with AI agents, and in the near future, AI agents will soon be everywhere, impacting industries in new and exciting ways. This is the perfect time to dive into building an AI agent startup. So, here are some essential tips to help you get started. #### Define the Agent's Autonomy Agents should have the ability to decide how many actions or steps they need to take to reach a resolution, rather than following a rigid, pre-defined sequence. Make sure your agent can loop through multiple steps, figuring out when to stop or go back to earlier stages until it finds the right solution. This flexibility allows it to handle more complex tasks on its own. #### Empathize with the model's limitations AI models often lack the broader context that humans naturally bring to a task. When building agents, try to understand things from the model's point of view. Think about what information or context it might be missing to perform well. This will guide your prompt design and ensure that the agent has enough clarity to make decisions and complete tasks effectively. #### Start simple and scale gradually When building agents, start with simple tasks or single-action models and gradually increase complexity. This lets you fine-tune each stage and make sure it works well before you add more complexity. In the beginning, keep it simple, measure performance, and adjust the agent's behavior as needed before scaling up. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} #### Ensure Feedback Mechanisms Incorporating feedback loops is essential for refining an agent's performance. In particular, coding agents can greatly benefit from feedback through tests that verify the correctness of their output. Set up systems where agents can learn from their results and keep improving until they get it right. #### Measure and Monitor Results Building agents without a way to measure their effectiveness can be risky. Before fully developing an agent, create clear metrics to evaluate its performance. For example, in coding agents, metrics could include test results, response times, or successful iterations. Having clear measurements ensures you can monitor progress, spot any issues, and fine-tune the agent as it evolves. ### Conclusion AI agents are changing the way businesses operate, and the timing couldn't be better for founders looking to tap into this space. Look for workflows or repetitive tasks that can be automated by AI agents. Start with simple tasks and then gradually increase the complexity. Appwrite can help you build AI solutions that scale. We also offer a startup program, giving you access to the tools, resources, and support needed to bring your AI agent ideas to life. [Apply to the program](https://appwrite.io/startups) --- ## Predicting your developer destiny: how I built the AI Crystal Ball https://appwrite.io/blog/post/ai-crystal-ball Have you ever wondered what you would be doing as a developer 5 years from now? I, for sure, have, which is why recently I developed an AI Crystal Ball to use information from my GitHub account and predict what my destiny as a developer would look like. This project picked up a lot more attention than anticipated as well as a number of requests asking how this project was developed. Therefore, in this blog, we’ll discuss how the AI Crystal Ball project was built in the first place. ### Prerequisites In order to build this application, we have a few prerequisites. We must set up the following: - OpenAI API key - GitHub OAuth app - Appwrite OAuth adapter for GitHub - Appwrite collections to store GitHub data and destinies #### OpenAI Since we are using GPT-4 to generate developer destinies, we will need an OpenAI API key to communicate with their platform. To create an OpenAI API key, we must first create an account on the [OpenAI platform](https://platform.openai.com/). Once the account is set up and a project is created, we can visit their [API keys](https://platform.openai.com/account/api-keys) page and create an API key. Ensure you copy and save this key in a safe place, as the OpenAI platform will not let you view the key after it is created. ![OpenAI API Keys](/images/blog/ai-crystal-ball/openai.png) > Note: To use the GPT-4 API, your account must be upgraded to the Usage tier 1. To learn more, visit their [Usage tiers documentation](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-one). > #### GitHub To generate personalized developer destinies, we are using information such as a user’s GitHub username, follower and following count, and the top 5 used programming languages in their most recently created repositories. For this, we will need them to sign into GitHub OAuth to get their access tokens. To create a GitHub OAuth application, we must visit the [Settings page](https://github.com/settings/profile) on GitHub and click on the [Developer Settings](https://github.com/settings/profile) tab. From here, we can register an OAuth app. While registering the app, you can add any temporary URL, such as `https://temporary-endpoint/`, in the **Authorization callback URL** field (we will update this in the next step). Ensure you save your Client ID and Client Secret when you visit this page, as we will need them in the next step. ![GitHub OAuth](/images/blog/ai-crystal-ball/github.png) #### Appwrite In this app, we primarily need Appwrite for the following: - Managing GitHub OAuth login - Saving the information we get from the GitHub API, so we don’t need to call it repeatedly - Saving developer destinies if a user wants to create a shareable link The only prerequisite here was creating an [Appwrite Cloud account](https://cloud.appwrite.io/), followed by creating a new project and adding your hostname as a web app to the project. ##### GitHub OAuth To implement GitHub OAuth, we must visit the **Auth** page on the Appwrite project, go to the **Settings** tab, click on **GitHub** from the list of OAuth adapters, and paste the Client ID and Secret we saved from the GitHub OAuth app. Ensure that copy the **redirect URI** and paste it into your GitHub OAuth app in the **Authorization callback URL** field. ![Appwrite OAuth for GitHub](/images/blog/ai-crystal-ball/oauth.png) #### Appwrite Database We must create a database with the ID `crystalball` and two collections with the IDs `githubData` and `destiny` in the Appwrite project with the following details: ###### The `githubData` collection Create the collection and add the following attributes: | Key | Type | Size | Required | Array | | --- | --- | --- | --- | --- | | languages | String | 2000 | - | Yes | | followers | Integer | - | Yes | - | | following | Integer | - | Yes | - | | username | String | 255 | Yes | - | Visit the collection settings, enable **Document security,** and set the following (collection-level) **Permissions**: | Role | Create | Read | Update | Delete | | --- | --- | --- | --- | --- | | Users | Yes | - | - | - | ###### The `destiny` collection Create the collection and add the following attributes: | Key | Type | Size | Required | | --- | --- | --- | --- | | destiny | String | 25000 | Yes | | username | String | 255 | Yes | Visit the collection settings, enable **Document security,** and set the following (collection-level) **Permissions**: | Role | Create | Read | Update | Delete | | --- | --- | --- | --- | --- | | Any | - | Yes | - | - | | Users | Yes | - | - | - | ### Building the web app To build this app, we used SvelteKit, a framework to build web applications using JavaScript. There are some prerequisites, however, that must be completed before building out the features themselves. > Note: The code snippets will focus only on the application logic. All CSS or styling-related information as well as any other miscellaneous features will be accessible in the final project repository at the end of the blog. > #### Prerequisites We first set up a skeleton SvelteKit project (without TypeScript): ```bash npm create svelte@latest ai-crystal-ball cd ai-crystal-ball npm i ``` Once that is done, install the following NPM packages we need to develop the project: ```bash npm i appwrite openai ``` Lastly, we must create a `.env` file at the root of the directory and add the following: ```bash PUBLIC_APPWRITE_ENDPOINT= PUBLIC_APPWRITE_PROJECT_ID= PUBLIC_APPWRITE_DATABASE_ID= PUBLIC_APPWRITE_COLLECTION_ID_GITHUBDATA= PUBLIC_APPWRITE_COLLECTION_ID_DESTINY= SECRET_OPENAI_API_KEY= ``` After the environment variables are created, we can set up the Appwrite SDK by creating a file `./src/lib/appwrite.js` and adding the following: ```js import { Client, Account, Databases, OAuthProvider } from 'appwrite'; import { env } from '$env/dynamic/public'; const client = new Client() .setEndpoint(env.PUBLIC_APPWRITE_ENDPOINT) .setProject(env.PUBLIC_APPWRITE_PROJECT_ID); export const account = new Account(client); export const databases = new Databases(client); export { OAuthProvider }; ``` Also, since this app is created without SSR, we will go to the `./src/routes` directory and creates a file `+layout.js` to add the following: ```js export const csr = true; export const ssr = false; ``` #### Login using GitHub OAuth First, we must use the Appwrite Web SDK to set up our Auth library in the application. We will create a file `.src/lib/user.js` to create a user store with the necessary auth-related functions. ```js import { writable } from 'svelte/store'; import { account, OAuthProvider } from './appwrite'; const isBrowser = typeof window !== 'undefined'; const createUser = () => { const store = writable(null); async function init() { try { let session = await getSession(); if(!session) throw new Error('No session found'); store.set(await account.get()); return true; } catch (error) { store.set(null); return false; } } init(); function login() { if(!isBrowser) return; account.createOAuth2Session({ provider: OAuthProvider.Github, success: `https://${window.location.hostname}/app`, failure: `https://${window.location.hostname}` }); } async function getSession() { try { return await account.getSession({ sessionId: 'current' }); } catch(err) { return null; } } return { subscribe: store.subscribe, init, login, getSession } } export const user = createUser(); ``` We will call the login function from the index page at `./src/routes/+page.svelte`. ```html

    AI Crystal Ball

    Find your developer destiny!

    crystal ball
    ``` #### Getting data from the GitHub API After the user successfully logs in using GitHub, we will use the GitHub API to get a user’s top 5 languages from the last 30 repositories they created on their personal account. For that, we will first create our GitHub library in the file `./src/lib/github.js`. ```js export const github = { getUser: async (token) => { const url = 'https://api.github.com/user'; var response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' } }); return await response.json(); }, getRepos: async (username, token) => { const url = `https://api.github.com/users/${username}/repos?sort=created&direction=desc`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' } }); return await response.json(); }, getLanguages: async (username, repo, token) => { const url = `https://api.github.com/repos/${username}/${repo}/languages`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, 'X-GitHub-Api-Version': '2022-11-28' } }); return await response.json(); } } ``` At this point, we also want to create our Database library using the Appwrite SDK, so that we can store the information from the GitHub API. For that, we shall create a file `./src/lib/databases.js` and add the following: ```js import { Permission, Role, ID } from 'appwrite'; import { databases } from './appwrite'; import { env } from '$env/dynamic/public'; const databaseId = env.PUBLIC_APPWRITE_DATABASE_ID; const githubDataCollectionId = env.PUBLIC_APPWRITE_COLLECTION_ID_GITHUBDATA; export const db = { getUserData: async(documentId) => { try{ return await databases.getDocument({ databaseId, collectionId: githubDataCollectionId, documentId }); } catch(err){ return false; } }, addUserData: async(userId, username, followers, following, languages) => { return await databases.createDocument({ databaseId, collectionId: githubDataCollectionId, documentId: userId, data: { username, followers, following, languages }, permissions: [ Permission.write(Role.user(userId)), Permission.read(Role.user(userId)) ] }) } }; ``` We will be calling these functions for our main application page in the file `./src/routes/app/+page.svelte` ```html {#if githubLoading}

    Loading your Profile

    Crystal Ball

    {loadingMessage}

    {:else}
    {#if destinyLoading === ''} Crystal Ball {:else if destinyLoading === 'loading'} Crystal Ball {:else if destinyLoading === 'complete'}

    Five Years from Today

    {destiny}

    {/if}
    {/if} ``` #### Generating destiny with the OpenAI GPT-4 API Once we have our GitHub data, it is time to generate our destiny using the OpenAI GPT-4 API. For that, we will create an API route using SvelteKit’s server-only functions in the file `./src/routes/api/destiny/+server.js` and add the following code: ```js import { error } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import { OpenAI } from 'openai'; export async function POST({ request }) { try{ const requestBody = await request.json(); const openai = new OpenAI({ apiKey: env.SECRET_OPENAI_API_KEY }); const userData = requestBody.userData; const prompt = `You have the following data on a developer from their GitHub account:\n\nGitHub username: ${userData.username}\nFollowers: ${userData.followers}\nFollowing: ${userData.following}\nTop 5 languages:\n${userData.languages.join(',')}\n\nBased on this data, create a humourous, realistic prediction to lightly roast the individual about what they'll be doing in 5 years from now. Do not explicitly include their GitHub data in the final message. Only use gender-neutral pronouns.`; const response = await openai.chat.completions.create({ model: 'gpt-4', max_tokens: 250, messages: [{ role: 'user', content: prompt }], }); const completion = response.choices[0].message?.content; console.log(completion); return new Response( JSON.stringify({ destiny: completion }), { headers: { 'Content-Type': 'application/json' }, status: 200 } ) } catch(err){ console.error(err); throw error(500, err.message); } } ``` We will send this request by adding the following function to the file`./src/routes/app/+page.svelte` ```jsx async function getDestiny() { destinyLoading = 'loading'; document.getElementById("ballClick").disabled = true; const destinyRequest = await fetch('/api/destiny', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userData }) }) destinyLoading = 'complete'; let destinyRequestBody = await destinyRequest.json(); if(destinyRequest.status == 200){ destiny += `${destinyRequestBody.destiny}`; } else { destiny += `Error occured:\n\n${destinyRequestBody.error}`; } } ``` Since our UI has already been prepared in the previous step, we need not make any additional changes. #### Storing and sharing the destiny Lastly, to share our destiny with the rest of the world, we must create an additional page that gets destiny data from the Appwrite Database and displays it. For this, we must first add functions to add and get destinies from the Appwrite Database to our Database library. To do so, we visit `./src/lib/databases/js` and add the following functions to our export: ```js . . . const destinyCollectionId = env.PUBLIC_APPWRITE_COLLECTION_ID_DESTINY; export const db = {** . . . addDestiny: async(username, destiny) => { return await databases.createDocument({ databaseId, collectionId: destinyCollectionId, documentId: ID.unique(), data: { username, destiny } }) }, getDestiny: async(documentId) => { try{ return await databases.getDocument({ databaseId, collectionId: destinyCollectionId, documentId }); } catch(err){ return { username: 'Not found', destiny: 'Not found' } } } . . . ``` After that, we will create a new directory `./src/routes/destiny/[slug]` and add the following ###### A `+page.server.js` file Since this page has no reactive logic, we will render this page on the server side and send it to the user. We will also get our destiny via a `load` function here: ```js import { db } from '$lib/databases'; export const ssr = true; export const csr = false; export async function load({ params }) { let destiny = await db.getDestiny(params.slug); return { destiny }; } ``` ###### A `+page.svelte` file We will render our page using the pre-fetched destiny here: ```html

    {username}'s Destiny

    {destiny}

    ``` Lastly, we need to add a function in `./src/routes/app/+page.svelte` to store the destiny in the Appwrite Database using our library function and open the destiny link in a new tab for our user. For that, we add the following code in the ` ``` Let's breakdown our JavaScript logic: - **Form submission**: When the user submits a message, the form is prevented from reloading the page. Next, the user's message is displayed in the chat box. - **Sending the message to the serverless function**: The user's message is sent to the serverless function as a POST request. While waiting for the AI response, we display a “Processing...” message in the chat. This lets the user know that the AI is working on their request. After receiving a response from Google Gemini, the UI is updated with the AI-generated message. If an error occurs, an error message is displayed instead. - **Scrolling to the bottom**: After each message is displayed, the chat box is scrolled to the bottom so that the latest messages are always visible. With the current structure, you can open the `index.html` file directly in your browser to see what it looks like. It won't look pretty just yet, and the chat functionality won't work because we need to run the Appwrite function through the CLI, which will serve the HTML file and handle the Gemini API logic. Before we run the function, let's add some CSS styles to make the chat interface visually appealing. #### Step 5.2: Adding CSS styles To make the chat interface visually appealing, let's add some CSS styles. Update the ` ``` This styling will give the chat interface a modern look and feel. The chat messages will be displayed in alternating colors for user and Gemini messages, and the chat box will scroll automatically as new messages are added. ### Step 6: Running and testing locally Now that everything is set up, let's run the application locally and test it. If you already have the function running, you can skip the next step. #### Step 6.1: Running the function locally Ensure Docker is running, then use the following command to start the serverless function locally: ```bash appwrite run function ``` This will start the Appwrite function in your local environment. You should see a message indicating that the function is running and a link to access it. #### Step 6.2: Testing the application Open the link provided by the Appwrite CLI in your browser. You should see the chat interface with the message input field. Try sending a message to the AI, and you should receive a response in the chat box! ![Gif of the chat app in action](/images/blog/build-a-chat-app-with-appwrite-and-gemini/chat-gemini-demo.gif) You can also use cURL or Postman to test the serverless function by sending a POST request with a sample prompt: ```bash curl -X POST http://localhost:3000/ \ -H "Content-Type: application/json" \ -d '{"prompt": "Hello, how are you?"}' ``` ### Step 7: Deploying the application Once you've verified that the application works locally, it's time to deploy it to the Appwrite cloud. This will make your chat app accessible to anyone with an internet connection. #### Step 7.1: Deploying the function to the cloud To deploy the serverless function, run the following command: ```bash appwrite push function ``` This will return a prompt asking you to select the function(s) you want to deploy. Choose the `Chat Gemini` function, and hit Enter. Appwrite will deploy the function to the cloud, with a success message that looks like this: ```bash (base) user appwrite-project % appwrite push function ? Which functions would you like to push? chat-gemini (chat-gemini) ℹ Info: Validating functions ... ℹ Info: Checking for changes ... ℹ Info: Pushing functions ... ✓ Deployed • chat-gemini (chat-gemini) ✓ Success: Successfully pushed 1 functions. ``` You can now navigate to your [Appwrite cloud console](https://cloud.appwrite.io/console) > Projects (select your project) > Functions to see the deployed function. Click on the function to view its details, including the function URL. ![Screenshot of the function details in the Appwrite console](/images/blog/build-a-chat-app-with-appwrite-and-gemini/appwrite-function-details.png) With the function deployed, you now have a live chat application built with Appwrite and Google Gemini API. Users can interact with the AI chatbot in real-time, and you can continue to enhance the app with more features, customizations, and integrations. ### Conclusion In this tutorial, you've learned how to build a chat application using [Appwrite local function development](/docs/products/functions/develop-locally) and Google Gemini API. We covered everything from setting up the environment to writing the serverless function, creating a frontend, and testing the app locally before deploying it to the cloud. Appwrite's local function development is a powerful tool for building and testing serverless applications efficiently. Now that your chat app is up and running, you can expand its functionality, refine the frontend, or scale it to handle more complex interactions. If you have any questions or feedback, feel free to reach out to the Appwrite community on [Discord](https://appwrite.io/discord) or send me a message on [LinkedIn](https://www.linkedin.com/in/ebenezerdon/). You can view the complete codebase for our Gemini + Appwrite chat app on [GitHub](https://github.com/appwrite-community/chat-gemini-appwrite). ### Frequently asked questions (FAQs) **1. Can I use Appwrite with other AI models besides Gemini?** Yes. The same setup works with any AI model that exposes an HTTP API, like OpenAI, Claude, or Mistral. Just swap the Gemini endpoint in your function. Appwrite handles the function runtime, environment variables, and authentication exactly the same way. **2. How do I make sure my AI app doesn’t expose the API key?** Never call the AI model directly from the frontend. Keep your key in Appwrite Functions or environment variables so only your serverless function communicates with the model. Your frontend should only talk to Appwrite, just like in this Gemini chat example. **3. Can I store chat messages securely in Appwrite?** Absolutely. You can use Appwrite Databases to store chat logs with proper permissions. For instance, only the logged-in user can read their messages, while the backend can write AI responses. It’s ideal for personal chat history or analytics. **4. How do I scale AI chat apps built on Appwrite?** Appwrite Functions scale automatically in Appwrite Cloud, so you can handle higher traffic without infrastructure work. You can also self-host Appwrite and scale Docker containers horizontally. **5. Is it possible to run Appwrite Functions without an internet connection?** Yes. When developing locally. You can run functions offline to test your app logic using mock data or cached responses. Just note that external APIs like Gemini won’t respond without an internet connection. **6. How do I add authentication to my AI chat app?** You can use Appwrite Authentication (email/password, OAuth, or anonymous sessions). This ensures only registered users can access the chat and lets you link chat data to user IDs for secure, personalized experiences. ### Further reading: - [Appwrite Functions Documentation](https://appwrite.io/docs/products/functions) - [Local serverless function development with the new Appwrite CLI](https://appwrite.io/blog/post/functions-local-development-guide) - [Build an intelligent chatbot with ChatGPT and Appwrite Functions](https://appwrite.io/blog/post/function-template-prompt-chatgpt) --- ## Building a currency converter API with Deno 2 and Appwrite https://appwrite.io/blog/post/build-a-currency-converter-with-deno2 When building APIs, one of the most useful things you can create is a currency converter. Whether you're working on an application that handles pricing in different currencies or something more personal like tracking expenses across borders, having a reliable currency converter is a great tool. Today, we'll walk through building one using **Deno 2** and **Appwrite**. You'll learn how to set up your Deno project using Appwrite, how to fetch and parse JSON data with Deno, and how to use Appwrite Functions to deploy and run your API. By the end, you'll have a working API that converts currencies, provides users with detailed information on how to interact with it, and even utilizes Node.js packages like **Zod** for input validation. Let's get started. ### Setting up your Deno project Before we dive into the code, we need to set up our project. If you've already worked with Appwrite before, this will be familiar. If not, don't worry—I'll guide you through it. There are two ways you can set up a Deno project with Appwrite: through the Appwrite cloud console or using the Appwrite CLI. #### To use the Appwrite Cloud console 1. Go to the [Appwrite dashboard](https://cloud.appwrite.io/). 2. Create a new project if you don't have one already. 3. Navigate to the **Functions** tab and create a new function. 4. Choose **Deno** as the runtime and set up your function configuration. ![Create a new deno function](/images/blog/build-a-currency-converter-with-deno2/deno-create-function.png) 5. Choose where you want your code to live (GitHub or manual deployment), and click **Create** to finish. 6. You can now clone/download your function and start working on it. If you're developing locally, the [Appwrite CLI](https://appwrite.io/docs/tooling/command-line/installation) is much more convenient. It allows you to set up and manage your projects, functions, and other Appwrite resources without deploying to the cloud after every change. #### To use the Appwrite CLI First, make sure you have docker installed and running. If you don't, you can download it from the [official Docker website](https://www.docker.com/products/docker-desktop). Next, install the **Appwrite CLI** if you haven't already. Open your terminal and run: ```bash npm install -g appwrite-cli@latest ``` This will install the CLI globally, so you can use the `appwrite` command from anywhere on your machine. Now, log into your Appwrite account by running: ```bash appwrite login ``` Once you're logged in, create a new project with: ```bash appwrite init project ``` ### Initializing the Function with Deno With the project set up, let's create the Deno function that will handle our currency conversion. Appwrite Functions support the Deno runtime out of the box, so it's easy to get started. To initialize the function, run the following command: ```bash appwrite init function ``` Appwrite will ask for the name of your function, the ID, and the runtime you want to use. Go ahead and name the function something like `Currency Converter Function` and select **Deno** as the runtime. **Deno** natively supports TypeScript, so you don't have to worry about going through extra steps to configure your project for TypeScript. Appwrite will generate a basic Deno function for you, including a `main.ts` file in the `src` directory. You can test the function locally by running: ```bash appwrite run function ``` This will list the available functions in your project. Select the function you just created, and you'll get a URL where you can access your function locally. You can open this URL in your browser or use `curl` to test the function: ```bash curl 'your-function-url' ``` ### Fetching exchange rates The first feature we need for our API is fetching the exchange rates. We'll be using the **ExchangeRate API** to do this. It's a free API that provides real-time exchange rates for over 170 currencies. You can sign up for a free API key on their [website](https://www.exchangerate-api.com/). Once you have your API key, the next step is to create a `.env` file in the root of your function and store the key there. Here's what your `.env` file should look like: ``` EXCHANGE_RATE_API_KEY=your_api_key_here ``` When you later deploy your function, you'll also need to set this environment variable in the Appwrite dashboard. To add your API key in the Appwrite console: 1. Go to your project -> Functions. 2. Find your function and navigate to the **Settings** tab. 3. Scroll to the **Environment Variables** section and add a new environment variable with the key `EXCHANGE_RATE_API_KEY` and paste your API key as the value. Once that's done, we can now write the function to fetch exchange rates. Update the `main.ts` file in your function with the following code: ```tsx async function fetchExchangeRates(): Promise<{ [currency: string]: number }> { console.log('Fetching exchange rates from API') const apiKey = Deno.env.get('EXCHANGE_RATE_API_KEY') const response = await fetch( `https://v6.exchangerate-api.com/v6/${apiKey}/latest/USD`, ) const data = await response.json() if (data.result === 'success') { return data.conversion_rates } else { throw new Error('Failed to fetch exchange rates') } } ``` What's happening here is straightforward. We're using `Deno.env.get()` to access the API key stored in our environment variables. This ensures the key isn't exposed in the code, which is good practice for security. Then, we use Deno's built-in `fetch` function to call the ExchangeRate API. Unlike in Node.js, where you'd need to install a library like `node-fetch` to make HTTP requests, Deno includes this functionality natively, making things a little simpler. After fetching the data, we check if the request was successful, and if it is, we return the exchange rates. If something goes wrong, we throw an error to signal the issue. ### Converting the currency Now that we have a way to fetch exchange rates, the next step is to convert between two currencies. For the conversion logic: we'll take an amount, the currency to convert from, and the currency to convert to, and use the exchange rates to calculate the result. Here's the code to handle that: ```tsx async function convertCurrency( amount: number, from: string, to: string, ): Promise<{ amount: number from: string to: string result: number rate: number timestamp: string }> { const rates = await fetchExchangeRates() if (!(from in rates)) { throw new Error(`Currency not found: ${from}`) } if (!(to in rates)) { throw new Error(`Currency not found: ${to}`) } const fromRate = rates[from] const toRate = rates[to] const conversionRate = toRate / fromRate const result = amount * conversionRate return { amount, from, to, result: Number(result.toFixed(2)), rate: Number(conversionRate.toFixed(6)), timestamp: new Date().toISOString(), } } ``` This function uses the rates we fetched earlier to calculate the conversion. First, it checks whether the `from` and `to` currencies exist in the list of rates. If either currency is missing, we throw an error. Then, we calculate the conversion rate by dividing the `to` currency rate by the `from` currency rate. Finally, we multiply the amount by this conversion rate to get the converted amount, and return the result along with some additional information like the rate and timestamp. ### Validating user input with Zod It's important to make sure that the data coming into our API is valid. For example, we don't want users sending negative amounts or invalid currency codes. To handle this, we'll use **Zod**, a TypeScript-first schema validation library. The great thing about **Deno 2** is that it allows us to easily use Node.js packages like Zod without extra configuration. Let's first import Zod and define our validation schema: ```tsx import { z } from 'npm:zod@3.21.4' const CurrencyCodeSchema = z .string() .length(3) .transform((val) => val.toUpperCase()) const ConversionRequestSchema = z.object({ amount: z.number().positive(), from: CurrencyCodeSchema, to: CurrencyCodeSchema, }) ``` Here, `CurrencyCodeSchema` ensures that the currency code is a string of exactly three characters and automatically converts it to uppercase. `ConversionRequestSchema` ensures that the amount is a positive number and that both the `from` and `to` currencies are valid according to `CurrencyCodeSchema`. Using Zod in this way ensures that the data entering our API is clean and valid before we try to process it. Catching bad input early can prevent many headaches later. ### Handling API requests Now that we've validated the user input and written the conversion logic, we need to handle incoming requests to our API. We'll create two main routes: - **`/convert`**: To convert an amount from one currency to another. - **`/currencies`**: To list all available currencies. We'll also create a default route that provides some basic information about how to use the API. #### Handling currency conversion requests (`/convert`) Let's start with the `/convert` endpoint. This is where users will send a request to convert an amount from one currency to another. The request needs to include three query parameters: `amount`, `from` (the currency to convert from), and `to` (the currency to convert to). Add the following code to your function: ```tsx if (req.path === '/convert' && req.method === 'GET') { try { const validatedInput = ConversionRequestSchema.parse({ amount: Number(req.query.amount), from: req.query.from, to: req.query.to, }) const conversionResult = await convertCurrency( validatedInput.amount, validatedInput.from, validatedInput.to, ) return res.json(conversionResult) } catch (err) { if (err instanceof z.ZodError) { return res.json({ error: err.errors }, 400) } error('Conversion failed:', err) return res.json({ error: 'Conversion failed' }, 500) } } ``` Let's break down what's happening here: 1. **Checking the request path and method**: We first check if the request path is `/convert` and if it's a GET request. This ensures that we're only processing valid requests. 2. **Validating input**: We use the `ConversionRequestSchema` (which we defined earlier using Zod) to validate the incoming data. The schema ensures that: - `amount` is a positive number. - `from` and `to` are valid 3-letter currency codes. If the validation fails, we return a 400 error with detailed validation errors. 3. **Performing the conversion**: If the input is valid, we call the `convertCurrency()` function with the validated data. This function handles the actual conversion logic using the exchange rates we fetched earlier. 4. **Returning the result**: Once the conversion is successful, we send the result back to the user in JSON format. If any errors occur during the process (e.g., invalid currency codes or issues with the conversion), we log the error and return a generic "conversion failed" message with a 500 status code. This way, we're ensuring that invalid input is caught early and that the user gets meaningful feedback on what went wrong. #### Handling available currencies requests (`/currencies`) Next, let's handle the `/currencies` endpoint. This will return a list of all available currencies that our API supports. It's a simple GET request, and we'll use the exchange rates we fetched earlier to get the list of currencies. In your function, add the following code: ```tsx else if (req.path === '/currencies' && req.method === 'GET') { try { const currencies = await getAvailableCurrencies(); return res.json(currencies); } catch (err) { error('Failed to fetch currencies:', err); return res.json({ error: 'Failed to fetch currencies' }, 500); } } ``` Let's walk through it: 1. **Checking the request path and method**: Similar to the `/convert` endpoint, we check if the request path is `/currencies` and if it's a GET request. 2. **Fetching currencies**: We call the `getAvailableCurrencies()` function, which returns a list of all the supported currency codes based on the exchange rates. 3. **Handling errors**: If something goes wrong (e.g., issues fetching the exchange rates), we catch the error, log it, and return a 500 error message indicating that the currencies couldn't be fetched. If everything works fine, the user will receive a list of available currencies in JSON format. #### Providing a default response Lastly, we'll handle any requests that don't match `/convert` or `/currencies`. Instead of just returning a 404 error, it's helpful to give users a bit more information about how to use the API. Add this to your function: ```tsx else { return res.json({ message: 'Welcome to the Currency Converter API.', endpoints: { '/convert': 'GET - Convert currency. Parameters: amount, from, to', '/currencies': 'GET - List available currencies', }, examples: { convertCurrency: 'http://localhost:3000/convert?amount=100&from=USD&to=EUR', listCurrencies: 'http://localhost:3000/currencies', }, }, 200); } ``` This is a simple but effective way to guide users. Here's what it does: 1. **Providing a welcome message**: The default response includes a message explaining that the API is a currency converter. 2. **Listing available endpoints**: We provide information about the two main endpoints (`/convert` and `/currencies`), along with a description of what each endpoint does. 3. **Giving examples**: To make it easier for users, we also provide example URLs they can use to test the API. This helps users understand how to format their requests correctly. Note that you have to change `localhost:3000` to the URL of your Appwrite function or deployed API. ### Wrapping it all together - The `/convert` endpoint handles currency conversions and validates user input to prevent errors. - The `/currencies` endpoint provides a list of supported currencies. - The default route offers helpful information and examples for users who may not know how to interact with the API. With that, the API is complete and ready to be tested locally or deployed to your Appwrite instance. ### Testing your Deno API Now that the API is ready, let's test it locally. If you already have your function running locally, you can test it by sending requests to the endpoints we created. If not, you can start the function by running: ```bash appwrite run function ``` This will spin up your function and give you a URL where you can access it locally. Open this URL in your browser or use `curl` to test the API. For example, to convert 100 USD to EUR, you can run: ```bash curl 'http://localhost:3000/convert?amount=100&from=USD&to=EUR' ``` To list all available currencies, use: ```bash curl 'http://localhost:3000/currencies' ``` Remember to change `localhost:3000` to the URL of your function if you're running it on a different port. ### Deploying your Deno API Once you're happy with how the API works locally, you can deploy it to your Appwrite cloud project. This will make it accessible to anyone who has the URL. To deploy your function, run: ```bash appwrite push function ``` You'll get a response asking you to choose the function you want to deploy. Select the function you just created, and Appwrite will deploy it to your cloud project. You can view the function in the Appwrite dashboard and access it using the provided URL. ![Function overview page](/images/blog/build-a-currency-converter-with-deno2/function-overview-page.png) ### Wrapping up Congrats on building a working currency converter API using **Appwrite Functions** and **Deno 2**. Along the way, we explored how Deno's built-in TypeScript support and its straightforward integration with Node.js packages like Zod make the development process easier. You also saw how Appwrite's Functions allow you to test everything locally, ensuring smooth deployment when you're ready. This project should give you a solid foundation for building more complex APIs in the future. If you're looking to expand this, you could add features like more advanced error handling, [authentication](https://appwrite.io/docs/products/auth) and user search history, or even integrate real-time updates for currency rates. Feel free to experiment with and extend this API however you see fit. If you have any questions or run into issues, reach out to us on the [Appwrite Discord server](https://appwrite.io/discord) or send me a message on [LinkedIn](https://www.linkedin.com/in/ebenezerdon/). ### Further reading - [Deno 2.0 and what it means for Appwrite Functions](https://appwrite.io/blog/post/deno-2-appwrite-functions) - [Local serverless function development with the new Appwrite CLI](https://appwrite.io/blog/post/functions-local-development-guide) - [Building a chat app with Appwrite and Google Gemini](https://appwrite.io/blog/post/build-a-chat-app-with-appwrite-and-gemini) --- ## Build a “delivery store locator” using Spatial Columns in Appwrite Databases https://appwrite.io/blog/post/build-delivery-store-locator-spatial-columns We just launched spatial columns for Appwrite databases which allows you to perform queries on real, spatial data like locations, areas, and routes. This unlocks a lot of possibilities where you can tap into real-world geo data and query the information you need. This saves you from performing your own calculations on longitudes and latitudes and instead relying on Appwrite's database to query the data you need. In this article, we will look at an example delivery store locator and how it uses spatial columns to find the nearest stores. ### Overview ![Overview of the delivery store locator app](/images/blog/build-delivery-store-locator-spatial-columns/overview.png) The nearest delivery store locator allows the user to pick a point on a map and check if any stores are available to deliver to the user. Any stores that can deliver will appear on the right side. In this case, **Store 1** is available to deliver the order as the store is within a ~10 mile radius of the location picked by the user. To make the development of the app easy, we aren’t using authentication and allowing anyone to freely add and remove stores. ![Add and remove stores](/images/blog/build-delivery-store-locator-spatial-columns/add-store.png) You can manage stores by using the hamburger menu at the top right side, where you can add and remove stores from the database. You can access the [delivery store locator](https://delivery-store-locator.appwrite.network/) and play around with it. Please note that this is an example and not suitable for production usage. You can find the code for this project in our [GitHub repository](https://github.com/atharvadeosthale/delivery-store-locator-appwrite). ### Setting up spatial columns Head to [Appwrite Cloud](https://cloud.appwrite.io) and choose your project. Go to **Databases** section from the sidebar and create/choose your database. Here, we will create a new table called `stores`. In the columns tab for your new table, let’s create two columns that will be needed. ![Adding spatial columns in Appwrite console](/images/blog/build-delivery-store-locator-spatial-columns/required-columns.png) - `name`: name of the store, this should be a column of type **String**. - `location`: exact location coordinates of the store, this should be a column of type **Point**. These are the only two columns we need that represent a store in our application. ### Creating a store In the application, we have a hamburger on the top right to manage stores we have in our application. When you click on **Add Store**, you will get a popup similar to the following: ![Adding stores](/images/blog/build-delivery-store-locator-spatial-columns/add-store.png) This modal will accept a store name, and you can select a location on the map where you want to set to have the store at. Once you click **Add store**, it gets added to the database, and the updated stores appear on the sidebar. However, there's something important to know here — it's the way we're storing the location information in the database. We're not just storing this information as a string, we're using the special **Point** column we made in Appwrite console earlier. When you click on **Add store**, we're making an API call, which then adds to database using the following call: ```js const store = await tablesdb.createRow({ databaseId: "ecommerce", tableId: "stores", rowId: ID.unique(), data: { name, location: [location.lon, location.lat] }, }); ``` In the above code, we're passing location coordinates in an array format (`[lon, lat]`), which then gets correctly stored in the database. This column is designed in a way that it abstracts away all the calculations between two sets of coordinates, and managing these in a database now becomes easier. ### Finding nearby store After you add a store, if you choose a location very close to the store on the map on the main screen, you should be able to see the store in the list of nearby stores. However, if you choose a location far away from the store, it should not show you the store you just added. ![Finding nearby stores](/images/blog/build-delivery-store-locator-spatial-columns/finding-nearby-store.png) To make this work, we are not using some highly logical geometrical calculations, we are simply running a query on our Appwrite database mentioning the distance limit we want to limit our store search to. ```js const serviceableStores = await tablesdb.listRows({ databaseId: "ecommerce", tableId: "stores", queries: [Query.distanceLessThan("location", [lon, lat], 16000, true)], }); ``` In the above code, we are using the query `distanceLessThan` to get all the records in the database whose `location` column is within a specific distance radius of a set of coordinates provided, which in our case, is the location that the user selects. `16000` is the distance in meters, which roughly translates to 10 miles. We pass `true` to tell the database we mentioned our distance in meters. This will return the stores within the range, without you needing to iterate through your database or applying complex logic to do so. You will have coordinates of nearby store, and then you can run calculations to calculate the distance between the store and user-provided coordinates, etc. This way, you can sort multiple stores based on their distance, if you receive more than one record from the database. ![Two stores displayed in locator](/images/blog/build-delivery-store-locator-spatial-columns/two-stores.png) ### Wrapping up Spatial columns are very powerful when dealing with real-world location data and could reduce the friction of performing calculations while querying your database. If you have any questions, please join our [Discord server](https://appwrite.io/discord). [Spatial columns announcement](/blog/post/announcing-spatial-columns) --- ## Build a fullstack Notes app with Cursor, Appwrite, and TanStack Start https://appwrite.io/blog/post/build-fullstack-notes-app-cursor-appwrite-tanstack-start Developers are entering a new era where AI can *understand context and build with you*. AI-powered editors like **Cursor** blur the line between development and automation, while **Appwrite** provides the unified backend that turns those AI-generated ideas into production-ready applications. Together, they represent the next leap forward, where AI helps you go from idea → API → local app in minutes. I chose to use Cursor as it's my *favorite* coding agent. We all have our favorite, and I'll definitely keep building with Cursor as it's just more user friendly. In this tutorial, you’ll: - Connect Cursor to Appwrite using **MCP** - Build a **TanStack Start** app with authentication and CRUD - Run it locally --- ### What is MCP and why it matters **MCP (Model Context Protocol)** lets AI tools like Cursor securely connect to APIs and databases. The **Appwrite MCP server** gives Cursor access to your Appwrite project so it can create tables, write code, and query real data. 👉 [**Add Appwrite MCP to Cursor**](https://appwrite.io/docs/tooling/mcp/cursor) --- ### Setting up the Appwrite MCP Server #### 1. Create an API Key in Appwrite Cloud * Log in to [Appwrite Cloud](https://cloud.appwrite.io). * Create a **new API key** with the following scopes: - `databases.read` and `databases.write` (or `tables.read` and `tables.write`) - `users.read` and `users.write` * Copy the key. You can find it at Overview -> Integrations -> API keys > **Note:** If you encounter permission errors, you can temporarily use "all scopes" for troubleshooting, but we recommend using specific scopes for better security. #### 2. Copy your Project ID Go to **Project → Overview**, hover over **Project ID**, and copy it. #### 3. Add the MCP Server in Cursor In Cursor → **Tools → Installed MCP Servers → Add Custom MCP**: ![Cursor MCP Settings](/images/blog/build-fullstack-notes-app-cursor-appwrite-tanstack-start/cursor-mcp-settings.PNG) You can specify specific routes instead of choosing --all, such as --tables, --users, --functions since having too many may degrade performance in Cursor. | Field | Example | |---|---| | Name | `appwrite` | | Command | `uvx mcp-server-appwrite --all` | | APPWRITE_ENDPOINT | `https://[REGION].cloud.appwrite.io/v1` | | APPWRITE_PROJECT_ID | `` | | APPWRITE_API_KEY | `` | Make sure you update your region, project ID, and API key, which is all found in your Appwrite Console. Click **Install**, then verify it appears in your MCP list. --- ### Troubleshooting MCP | Issue | Fix | |---|---| | `uvx not recognized` | Install **uv**:
    Windows → `irm https://astral.sh/uv/install.ps1 | iex`
    macOS/Linux → `curl -LsSf https://astral.sh/uv/install.sh | sh` | | Only “users” endpoint visible | Use `--all` flag or add endpoints manually in config | | MCP not found | Open a **new Cursor conversation** or restart Cursor | --- ### Building the notes app > Fully local CRUD app with Appwrite. > **Preview:** Here's what we'll build by the end of this tutorial. ![TanStack Notes App](/images/blog/build-fullstack-notes-app-cursor-appwrite-tanstack-start/tanstacknotesapp.PNG) --- ### Create a project folder ```bash mkdir ai-notes && cd ai-notes cursor . ``` --- ### Scaffold TanStack Start (SSR) **Prompt (Cursor):** ```text Create a TanStack Start project named "ai-notes" with TypeScript and SSR. Set up a clean src structure with routes, components, and lib folders. ``` **Commands:** ```bash npm create tanstack@latest Choose: Start (SSR), TypeScript npm i ``` > 💡 Cursor may prompt: `npm install --save-dev tsx` — that's expected for SSR dev mode. --- ### Remove scaffolded docs + create routes > **Note:** TanStack Start scaffolds a demo/docs route. You'll replace it with real app routes. **Prompt (Cursor):** ```text Remove any TanStack Start demo/docs routes. Replace them with: - / → Landing page (redirects to /notes if logged in, else /login) - /login → Login page - /signup → Signup page - /notes → Notes app (protected, requires login) Ensure "/" never shows the TanStack Start docs page. ``` --- ### Create `.env.local` from MCP **Prompt (Cursor):** ```text Create a .env.local file and populate: VITE_APPWRITE_ENDPOINT and VITE_APPWRITE_PROJECT_ID Use values from my connected Appwrite MCP server (use regional endpoint with /v1). ``` **Example:** ```env VITE_APPWRITE_ENDPOINT=https://nyc.cloud.appwrite.io/v1 VITE_APPWRITE_PROJECT_ID= ``` Restart `npm run dev` after editing `.env.local`. --- ### Configure the Appwrite SDK **Prompt (Cursor):** ```text Install the Appwrite Web SDK as a normal dependency (not dev). Create a client helper that reads from VITE_APPWRITE_ENDPOINT and VITE_APPWRITE_PROJECT_ID. ``` **Commands:** ```bash npm i appwrite ``` **Helper:** ```ts // src/lib/appwrite.ts import { Client, Account, TablesDB, ID } from 'appwrite'; const client = new Client() .setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT!) .setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID!); export const account = new Account(client); export const tablesDB = new TablesDB(client); export { ID }; ``` --- ### Create Database & Table via MCP **Prompt (Cursor):** ```text Using the Appwrite MCP server, create a database with ID "notes-db" (name it "Notes Database") and a table with ID "notes" (name it "Notes") with columns: title, content, userId, createdAt, updatedAt. Grant authenticated user read/write and save IDs to src/lib/config.ts. ``` --- ### Add Authentication (session-safe, no auto-login) **Prompt (Cursor):** ```text Create an auth helper with session-safe login/logout. Import the account instance from ./lib/appwrite.ts (not creating a new one). Requirements: - login(email, password): 1. Check if a session exists; if yes, delete all sessions first. 2. Create a new email/password session. 3. If "session already active" error occurs, delete all sessions and retry once. 4. Return the current user via account.get(). - logout(): delete all sessions. - getCurrentUser(): return account.get(); if unauthenticated, return null. Update the /login page: - NEVER auto-redirect away from /login. Always show the form so users can switch accounts. - On successful login, navigate to /notes. - Show clear errors if something fails. Update route guards: - /notes loader/server check: if no session, redirect to /login. - / index loader/server check: if session → /notes, else → /login. ``` **Helper (`src/lib/auth.ts`):** ```ts import { ID } from 'appwrite'; import { account } from './appwrite'; export async function getCurrentUser() { try { return await account.get(); } catch { return null; } } export async function deleteAllSessions() { try { await account.deleteSessions(); } catch { // ignore; we might not have a session } } export async function login(email: string, password: string) { await deleteAllSessions(); try { await account.createEmailPasswordSession(email, password); } catch (err: any) { const msg = err?.message?.toLowerCase() ?? ''; if (msg.includes('session') && msg.includes('active')) { await deleteAllSessions(); await account.createEmailPasswordSession(email, password); } else { throw err; } } return await account.get(); } export async function signup(email: string, password: string, name?: string) { await account.create(ID.unique(), email, password, name); return await login(email, password); } export async function logout() { await deleteAllSessions(); } ``` --- ### Add CRUD for notes **Prompt (Cursor):** ```text Create a /notes route that lists, creates, updates, and deletes notes using Appwrite TablesDB API. Include loading, error, and empty states. ``` --- ### Style and quality pass (Tailwind v4, accessibility) **Prompt (Cursor):** ```text Add Tailwind CSS v4 and basic styling. Fix any invalid utilities like focus-visible:ring-opacity-* or custom @apply utilities. Add accessible focus styles (focus-visible:outline-none focus-visible:ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500/100). ``` --- Note: You can just add the first part "Add Tailwind CSS v4 and basic styling. I added the 2nd line in the prompt as I had issues with Tailwind utility errors I wasn't familiar with, so this prevents that issue from occurring. ### Run locally **Prompt (Cursor):** ```text Ensure npm scripts for dev, build, and preview exist. Run npm run dev first, then npm run build && npm run preview to verify before deployment. ``` **Commands:** ```bash npm run dev npm run build npm run preview ``` ✅ **Verify routes:** - `/` → redirects to `/notes` if logged in, `/login` if not - `/login` + `/signup` → show correct forms - `/notes` → works only when logged in - No TanStack Start docs page appears --- ### Troubleshooting | Issue | Fix | |---|---| | `/` shows TanStack docs page | Delete demo/docs routes and create `/`, `/login`, `/signup`, `/notes` manually. | | `focus-visible-ring` or `ring-opacity-*` errors | Tailwind v4 removed those utilities — use color/alpha rings instead. | | Missing `.env` file | Create `.env.local` using values from Appwrite MCP. | | 401/403 errors | Verify API key scopes + table permissions. | | "Creation of a session is prohibited when a session is active." | Use session-safe login flow. Delete all sessions first, retry once if needed, and remove auto-login logic from `/login`. | --- ### Still having issues? Everyone has a different experience going through a tutorial. Luckily, with using Cursor, you can enter any errors or problems you are having, and it should fix it and adjust your code as needed. ### Master prompt list 1️⃣ **Scaffold app** ```text Create a TanStack Start project named "ai-notes" with TypeScript and SSR. ``` 2️⃣ **Fix routes** ```text Remove scaffolded docs routes. Create "/", "/login", "/signup", "/notes" with redirects using Appwrite Account API. ``` 3️⃣ **Appwrite SDK helper** ```text Install appwrite SDK. Create client helper using VITE_APPWRITE_* envs. ``` 4️⃣ **.env file** ```text Create a .env.local file and populate VITE_APPWRITE_ENDPOINT and VITE_APPWRITE_PROJECT_ID using values from Appwrite MCP. ``` 5️⃣ **Database + Table** ```text Create database with ID "notes-db" (name it "Notes Database") and table with ID "notes" (name it "Notes") via MCP with appropriate columns and permissions. ``` 6️⃣ **Authentication** ```text Add /login and /signup pages; protect /notes for logged-in users. Ensure session-safe login logic to prevent duplicate sessions. ``` 7️⃣ **CRUD** ```text Create /notes route with list, create, update, delete using Appwrite TablesDB API. ``` 8️⃣ **Polish** ```text Add types, a11y checks, and Tailwind v4-safe styling. ``` 9️⃣ **Local test** ```text Run npm run dev, then npm run build && npm run preview. Verify all routes and auth logic. ``` --- > *If AI can handle the setup, what will you build next?* --- ## Building a full-stack app with Svelte and Appwrite https://appwrite.io/blog/post/build-fullstack-svelte-appwrite Managing personal finances is a common need, and building an expense tracker is an excellent way to learn full-stack development with Svelte. In this tutorial, we'll create a web application that helps users track their spending. Users will be able to sign up, log their expenses, and categorize them to view spending patterns. By the end, you will have built an app that looks and functions like this: ![Expense app demo](/images/blog/build-fullstack-svelte-appwrite/expense-app-demo.gif) You can also try out the app using this [live URL](https://expense-appwrite-svelte.netlify.app/). We'll use SvelteKit with JavaScript for our frontend so that we can take advantage of its built-in routing. For styling, we'll use Tailwind CSS. And on the backend, we'll use Appwrite to handle user authentication and database operations. You'll learn how to implement authentication flows, manage application state, handle form submissions, and create an intuitive user interface. #### Prerequisites This tutorial assumes you have basic knowledge of TypeScript and Svelte. You'll need: - Node.js version 18 or later installed on your system - pnpm as your package manager - An Appwrite instance (either self-hosted or cloud) #### Project setup and initial configuration Let's start by creating a new Svelte project. Open your terminal and run: ```bash npx sv create expense-app ``` When prompted to select a template, choose "Sveltekit minimal". For type checking, you can select "Yes" or "No", depending on your preference, but we'll go with "No" for this tutorial. For the question "What would you like to add to your project?", select "prettier" and "tailwindcss". Next, choose your preferred package manager, then create your Sveltekit project. In this tutorial, we'll use **pnpm**. With that done, you should have your new Sveltekit project named `expense-app`. You can test it by running: ```bash pnpm dev ``` Next, navigate to the project directory and install the additional dependencies we need. We'll use `appwrite` for authentication and database operations, and `date-fns` for date formatting: ```bash cd expense-app pnpm install pnpm add appwrite date-fns ``` #### Environment configuration Our application needs to communicate with [Appwrite](https://cloud.appwrite.io/?doFollow=true), which requires several configuration values. Create a `.env` file in your project root and add these environment variables: ``` PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 PUBLIC_APPWRITE_PROJECT_ID=your-project-id PUBLIC_APPWRITE_DATABASE_ID=expense-db PUBLIC_APPWRITE_COLLECTION_ID=expenses ``` The `PUBLIC_` prefix makes these variables available to our client-side code in Svelte. You'll need to replace `your-project-id` with your actual Appwrite project ID, which we'll create in the next step. #### Setting up Appwrite Before we continue with the frontend implementation, we need to configure our Appwrite backend. Log into your [Appwrite Console](https://cloud.appwrite.io/console?doFollow=true) and follow these steps: 1. Create a new project 2. Open the **Databases** tab from the sidebar and create a database with the ID "expense-db" 3. In your new database, create a collection with the ID "expenses" The expenses collection needs several attributes to store the expense data effectively. Open the **Attributes** tab of your new collection and add the following attributes: ```md - `userId` (String, required) - `amount` (Float, required) - `category` (Enum, required) - Elements: "food", "rent", "transportation", "entertainment", "shopping", "healthcare", "utilities", "education", "other" - `description` (String, required) - `date` (DateTime, required) - `createdAt` (DateTime, required) - `updatedAt` (DateTime, required) ``` Notice that the `category` attribute is an enumerated type with a set of predefined values. This structured approach helps us organize and filter expenses effectively. We have both a `date` attribute and a `createdAt` attribute because when an expense is created is not necessarily the same as when it occurred. To ensure that users can only access their own expenses, Open the collection's **Settings** tab and scroll to **Permissions**. Click **Add role**, select **Users** and check **Create** permission. Next, enable **Document Security** to allow users to access their documents. We'll ensure this by giving users the **Read** permission when creating documents in our code. ![permissions-document-security](/images/blog/build-fullstack-svelte-appwrite/permissions-document-security.png) #### Project structure Our application needs a clear structure to make it easy to maintain. Create the following directory structure in your project: ``` src/ ├── lib/ │ ├── stores/ │ │ └── auth.js │ └── appwrite.js ├── routes/ │ ├── auth/ │ │ └── +page.svelte │ ├── +layout.svelte │ └── +page.svelte ├── app.html └── app.css ``` This structure follows Svelte's conventions while keeping our code organized and maintainable. The `lib` directory contains reusable utilities and stores, while `routes` handles our application's pages and layouts. We'll use the `lib/stores/auth.js` store to manage our user state, and the `lib/appwrite.js` file to handle our Appwrite operations. The `routes` directory contains our application's pages and layouts. The `+layout.svelte` file is our main layout component, which we'll use to handle our application's structure and ensure that users can only access protected routes if they're authenticated. #### Styling the application For our expense tracker, we'll use Tailwind CSS for styling. The styling includes: - Custom color variables for consistent theming - Base styles for typography and common elements - Component-specific classes for our custom UI elements - Interactive element styles with hover and focus states You can find the complete CSS code here: [Complete app.css code](https://github.com/appwrite-community/svelte-expense-tracker/blob/main/src/app.css). Copy this code into your `src/app.css` file. There's no need to do anything else to make this work if you selected the "tailwindcss" option when creating your project. Having these styles in place will ensure that each component you create looks good from the start. #### Base HTML template For the base `src/app.html` file, we'll use the default Sveltekit template, but you might want to update the meta tags to include a title and description. ```html Expense Tracker %sveltekit.head%
    %sveltekit.body%
    ``` This template provides the basic structure for our application. The `data-sveltekit-preload-data="hover"` attribute enables SvelteKit's built-in preloading feature to make navigation faster. #### Setting up the Appwrite client Let's set up our connection to Appwrite. If you haven't already, create a new file in the `src/lib` directory named `appwrite.js`. We'll use this file to configure the Appwrite client and provide access to our database and account services. ```js import { Client, Account, Databases } from 'appwrite' import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID, PUBLIC_APPWRITE_DATABASE_ID, PUBLIC_APPWRITE_COLLECTION_ID } from '$env/static/public' const client = new Client() client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject(PUBLIC_APPWRITE_PROJECT_ID) export const account = new Account(client) export const databases = new Databases(client) // Collection IDs from environment variables export const EXPENSES_COLLECTION_ID = PUBLIC_APPWRITE_COLLECTION_ID export const DATABASE_ID = PUBLIC_APPWRITE_DATABASE_ID ``` This configuration file initializes our connection to Appwrite. The `Client` class creates a new Appwrite client instance, which we configure with our endpoint and project ID from our environment variables. We then create instances of the `Databases` and `Account` services, which we'll use throughout our application for database operations and user authentication. Finally, we export the collection IDs from our environment variables so that we can use them in other parts of our application. #### Managing authentication state Our Svelte application needs to track the current user's authentication state. For that, we'll use a Svelte store. Create a new file in the `src/lib/stores` directory named `auth.js` and add the following code: ```js import { writable } from 'svelte/store' import { account } from '$lib/appwrite' export const user = writable(null) export async function initAuth() { try { const currentUser = await account.get() user.set(currentUser) return currentUser } catch (error) { console.error('Error initializing auth:', error) user.set(null) return null } } ``` This creates a Svelte store named `user` to manage our user state. The store starts with null when no user is logged in. When a user authenticates, we'll update this store with their information, making it available throughout our application. In the `initAuth` function, we're using the `account.get()` method to retrieve the current user's information from Appwrite. If successful, we update our `user` store with the user's information and return it. If there's an error, we log it and return null. We'll also need a `login` and `register` function to handle user authentication: ```js export async function login(email, password) { try { await account.createEmailPasswordSession({ email, password }) await initAuth() } catch (error) { console.error('Login error:', error) throw error } } export async function register(email, password, name) { try { await account.create({ userId: ID.unique(), email, password, name }) await login(email, password) } catch (error) { console.error('Registration error:', error) throw error } } ``` In the `login` function, we're using the `account.createEmailPasswordSession` method to create a new email/password session for the user. This method automatically logs the user in and updates our `user` store with the user's information. For the `register` function, we're using the `account.create` method to create a new user account. We then call the `login` function to log the user in after creating their account. Appwrite also provides other authentication methods, such as OAuth, Google, and Apple. You can learn more about them in our [docs](https://appwrite.io/docs/products/auth?doFollow=true). Finally, we'll add a `logout` function to the `auth.js` file to handle user logout: ```js export async function logout() { try { await account.deleteSession('current') user.set(null) } catch (error) { console.error('Logout error:', error) } } ``` The `logout` function uses the `account.deleteSession('current')` method to delete the current user's session from Appwrite. This effectively logs the user out and updates our `user` store to null. With this setup, we're ready to implement our authentication flow. #### Building the authentication page For the authentication page, we'll create a new file in the `src/routes/auth` directory and name it `+page.svelte`. This file will handle the sign-in and sign-up functionality. For the JavaScript functionality of our authentication page, add the following ` ``` Here, we're handling the authentication form submission and logic. In the `handleSubmit` function, we check if the user is logging in or registering. We then call the appropriate function (`login` or `register`) and update our `user` store with the user's information. Finally, we redirect the user to the home page using the `goto` function. For the template section, add the following code: ```svelte
    💰

    {isLogin ? 'Welcome back!' : 'Create your account'}

    {isLogin ? "Track your expenses with ease. Let's get you signed in." : 'Start your journey to better expense management'}

    {#if error}
    {error}
    {/if}
    {#if !isLogin}
    {/if}
    ``` This template creates an interface for both signing in and creating new accounts. The form dynamically changes based on whether the user is logging in or signing up, showing additional fields when needed. Notice that we're using the CSS classes from our `app.css` file to style our component. The template also handles loading states and error messages which will provide clear feedback to users during the authentication process. With this done, you can navigate to the `/auth` route in your browser and test the authentication functionality. #### Creating our layout component Let's structure our application's layout with the `src/routes/+layout.svelte` file. Here, we'll check the user's authentication status when the application loads, and redirect the user to the authentication page if they're not authenticated. We'll also add our app's navbar to this file, so that it can be accessed from any page. First, add the `script` section to handle our layout's logic: ```svelte ``` In the `onMount` function, we're checking the user's authentication status when the application loads. If the user is not authenticated, we redirect them to the authentication page. This function runs when the component is first mounted. The `toggleDropdown` function handles the dropdown menu's open/close state, which we'll use to show the logout button in the navbar. We can also use this dropdown for other purposes, like showing the user's profile information. The `handleLogout` function handles the user's logout process. It calls the `logout` function from our `auth.js` file and redirects the user to the authentication page. Next, let's add the template section for our layout's UI: ```svelte
    {#if !$page.url.pathname.startsWith('/auth')}
    {:else} {/if}
    ``` Here, we're adding a navbar to our layout. The navbar contains a brand logo and a user dropdown menu which displays the user's avatar and name. We're getting the user's avatar from the `api.dicebear.com` URL, which generates an avatar based on the user's initials. The user's name is gotten from the `user` store, and it's what handles the dropdown menu's open/close state. Notice that we've also added a footer to our layout. This footer contains a copyright notice. You can customize this footer to fit your app's needs, and provide links to your app's privacy policy and terms of service. #### Building the main expense tracker page The heart of our application is the expense tracker page. This component handles displaying, creating, updating, and deleting expenses, along with showing important statistics. Let's build this page in the `src/routes/+page.svelte` file. We'll start with our imports and state management: ```svelte ``` Here we're setting up our component's state. The `expenses` array will hold our list of expenses, while `loading` and `error` handle our application's loading and error states. We maintain separate form data for creating new expenses and editing existing ones. Next, let's define our expense categories and statistics tracking: ```js const categories = [ { id: 'food', name: 'Food & Dining', icon: '🍽️' }, { id: 'rent', name: 'Rent', icon: '🏠' }, { id: 'transportation', name: 'Transportation', icon: '🚗' }, { id: 'entertainment', name: 'Entertainment', icon: '🎮' }, { id: 'shopping', name: 'Shopping', icon: '🛍️' }, { id: 'healthcare', name: 'Healthcare', icon: '🏥' }, { id: 'utilities', name: 'Utilities', icon: '💡' }, { id: 'education', name: 'Education', icon: '📚' }, { id: 'other', name: 'Other', icon: '📦' } ] let stats = { total: 0, thisMonth: 0, thisWeek: 0 } $: currentAmount = editingExpense ? editFormData.amount : formData.amount $: currentDescription = editingExpense ? editFormData.description : formData.description $: currentCategory = editingExpense ? editFormData.category : formData.category ``` We're using reactive declarations to handle form data. These statements ensure our form always shows the correct data whether we're editing an existing expense or creating a new one. Now let's implement our core functionality for fetching and managing expenses: ```js onMount(async () => { await fetchExpenses() }) async function fetchExpenses() { try { loading = true const response = await databases.listDocuments({ databaseId: DATABASE_ID, collectionId: EXPENSES_COLLECTION_ID, queries: [ Query.orderDesc('$createdAt') ] }) expenses = response.documents calculateStats() } catch (e) { error = 'Failed to load expenses' console.error('Error fetching expenses:', e) } finally { loading = false } } function calculateStats() { const now = new Date() const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1) const thisWeek = new Date(now.setDate(now.getDate() - now.getDay())) stats.total = expenses.reduce((sum, exp) => sum + parseFloat(exp.amount), 0) stats.thisMonth = expenses .filter((exp) => new Date(exp.$createdAt) >= thisMonth) .reduce((sum, exp) => sum + parseFloat(exp.amount), 0) stats.thisWeek = expenses .filter((exp) => new Date(exp.$createdAt) >= thisWeek) .reduce((sum, exp) => sum + parseFloat(exp.amount), 0) } ``` The `fetchExpenses` function retrieves our expenses from Appwrite and sorts them by creation date, but you might want to sort instead by the date of the expense, depending on how you want to display expenses. After fetching, we calculate statistics including total expenses, this month's expenses, and this week's expenses. Let's add the functionality for creating and updating expenses: ```js async function handleSubmit() { try { const user = await account.get() const now = new Date().toISOString() const expenseData = { amount: parseFloat(currentAmount), description: currentDescription, category: currentCategory, userId: user.$id, date: now, createdAt: now, updatedAt: now } if (editingExpense) { await databases.updateDocument({ databaseId: DATABASE_ID, collectionId: EXPENSES_COLLECTION_ID, documentId: editingExpense.$id, data: { ...expenseData, updatedAt: now } }) } else { await databases.createDocument({ databaseId: DATABASE_ID, collectionId: EXPENSES_COLLECTION_ID, documentId: ID.unique(), data: expenseData, permissions: [ Permission.read(Role.user(user.$id)), Permission.update(Role.user(user.$id)), Permission.delete(Role.user(user.$id)) ] }) } // Reset form formData = { amount: '', description: '', category: 'other' } editFormData = { amount: '', description: '', category: 'other' } editingExpense = null showForm = false await fetchExpenses() } catch (e) { console.error('Error saving expense:', e) error = 'Failed to save expense' } } ``` The `handleSubmit` function handles both creating new expenses and updating existing ones. When creating a new expense, we set document-level permissions to ensure users can access their expenses. Here, we're giving users the **Read**, **Update**, and **Delete** permissions. Finally, let's add utility functions for managing expenses: ```js async function deleteExpense(id) { try { await databases.deleteDocument({ databaseId: DATABASE_ID, collectionId: EXPENSES_COLLECTION_ID, documentId: id }) await fetchExpenses() } catch (e) { error = 'Failed to delete expense' console.error('Error deleting expense:', e) } } function formatAmount(amount) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount) } function getCategoryIcon(categoryId) { return categories.find((cat) => cat.id === categoryId)?.icon || '📦' } function getCategoryName(categoryId) { return categories.find((cat) => cat.id === categoryId)?.name || 'Other' } function editExpense(expense) { editingExpense = expense editFormData = { amount: expense.amount.toString(), description: expense.description, category: expense.category } showForm = true } ``` Remember to close the script section using ``. These utility functions handle tasks like formatting currency amounts, retrieving category information, and setting up the edit form when modifying an expense. #### Building the user interface Now that we have our core functionality in place, let's build the user interface. Our UI will consist of three main sections: statistics overview, expense form, and expense list. Let's add this template section to our `src/routes/+page.svelte` file: ```svelte

    Total Expenses

    {formatAmount(stats.total)}

    This Month

    {formatAmount(stats.thisMonth)}

    This Week

    {formatAmount(stats.thisWeek)}

    ``` The statistics overview provides users with a quick snapshot of their spending patterns. We display three key metrics: total expenses, monthly expenses, and weekly expenses. Each metric is presented in its own card with distinct styling for visual separation. Next, we'll add the button to create new expenses and the expense form modal: ```svelte
    {#if showForm} {/if} ``` The expense form appears in a modal overlay when users click the "Add Expense" button or choose to edit an existing expense. The form adapts its behavior based on whether we're creating a new expense or editing an existing one. We use Svelte's reactive bindings to keep our form inputs synchronized with our component's state. Finally, let's implement the expenses list that displays all recorded expenses: ```svelte
    {#if loading}
    {:else if error}

    {error}

    {:else if expenses.length === 0}
    💸

    No expenses yet

    Start tracking your expenses by adding one above!

    {:else}
    {#each expenses as expense}
    {getCategoryIcon(expense.category)}

    {expense.description}

    {getCategoryName(expense.category)} • {formatDistanceToNow(new Date(expense.$createdAt), { addSuffix: true })}

    {formatAmount(expense.amount)}

    {/each}
    {/if}
    ``` The expenses list handles multiple states: - A loading state with a spinner while data is being fetched - An error state if something goes wrong - An empty state when no expenses exist - A list of expense items when data is available Each expense item displays: - The expense category icon - The description - The category name - The time since the expense was created - The amount - Edit and delete buttons The list is ordered with the most recent expenses first, making it easy for users to track their latest spending. #### Running and testing the application With all components and styles in place, we can now run our application. Start the development server using: ```bash pnpm dev ``` Visit the displayed URL in your browser, and you should see a fully functional expense tracking application that looks like this: ![Expense app final look](/images/blog/build-fullstack-svelte-appwrite/expense-app-final-look.png) #### Next steps The expense tracker can be enhanced in several ways. Data visualization with charts would help users understand their spending patterns over time. Advanced filtering and search would make it easier to find specific expenses or analyze spending by category. Data export would let users analyze their expenses in external tools. Custom categories would let users organize expenses in ways that make sense for them. Budget tracking with alerts could help users stay within spending limits, while receipt image uploads would provide better expense documentation. #### Conclusion In this tutorial, we built an expense tracking application using SvelteKit and Appwrite. We implemented core features including user authentication, database storage, and a responsive interface that works on different devices. The component-based structure we used makes the code easier to maintain and update. We covered practical aspects like securing user data, managing state, and handling form submissions - skills that apply to many web applications. Use this project as a starting point and build upon it based on what you need. The code is available on [GitHub](https://github.com/appwrite-community/svelte-expense-app) if you want to explore it further. You can also visit the [live application](https://expense-appwrite-svelte.netlify.app) to see it in action. If you have any questions or feedback, you can reach out to me on [LinkedIn](https://www.linkedin.com/in/ebenezerdon/) or join our [Discord community](https://appwrite.io/discord). #### Further reading - [Start with SvelteKit](https://appwrite.io/docs/quick-starts/sveltekit) - [Building a chat app with Appwrite and Google Gemini](https://appwrite.io/blog/post/build-a-chat-app-with-appwrite-and-gemini) - [Building a currency converter API with Deno 2 and Appwrite](https://appwrite.io/blog/post/build-a-currency-converter-with-deno2) --- ## Build a personal CRM with SvelteKit and Appwrite Databases https://appwrite.io/blog/post/build-personal-crm-sveltekit Keeping track of people is hard. Between networking events, Twitter threads, LinkedIn DMs, and emails, it's easy to lose context on who someone is and why they matter to you. Most CRMs are overkill for personal use. You probably don't need deal pipelines, email campaigns, or complex lead scoring. You just want to remember who you met, what you last talked about, and how to stay in touch. In this blog, we'll build a lightweight personal CRM using **SvelteKit** for the web app and **Appwrite Databases** with the new **Bulk API** for the backend. It's a simple, single-page app with an API route containing various CRUD functionalities. It is perfect for developers who want a private, no-fuss contact tracker they can extend over time. ### Configure your Appwrite project First, [create an Appwrite Cloud account](https://cloud.appwrite.io/) if you haven't already. Once your project is ready, go to the **Settings** page and copy your project ID and API endpoint for further usage. Head to the **Overview** page from the left sidebar, select the **API keys** tab under the **Integrations** section, and click on the **Create API key** button. Select the `documents.write` and `documents.read` scopes under the **Databases** dropdown, create the key, and copy it for further usage. Next, go to the **Databases** page from the left sidebar, create a new database with the ID `crm`, and then a collection with the ID `contacts` (save both IDs for further usage). Next, head to the **Attributes** tab and add the following: | Key | Type | Size | Required | | --- | --- | --- | --- | | name | String | 255 | Yes | | email | Email | | Yes | | phone | Integer | 25 | No | | notes | Integer | 2000 | No | ### **Prepare the app logic** Once our Appwrite project is set up, let's start building our app. #### **Create a SvelteKit app** To create the SvelteKit app, open up your terminal and run the following command: ```bash npx sv create ``` This will load the Svelte CLI, where you can enter the following inputs to create a minimal app: - Where would you like your project to be created? **>** `personal-crm` - Which template would you like? **>** `SvelteKit minimal` - Add type checking with TypeScript? **>** `No` - What would you like to add to your project? **>** `prettier`, `eslint` - Which package manager do you want to install dependencies with? **>** `npm` Once that is done, enter the app's working directory and install all dependencies by running the following commands: ```bash cd personal-crm npm install ``` #### **Install the Appwrite Node.js SDK** Since the Bulk API is currently only available on Appwrite's server-side SDKs, let's install the Appwrite Node.js SDK by running the following command: ```bash npm install node-appwrite ``` In the root directory of your app, create a `.env` file and add the information you saved from your Appwrite project: ```bash APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 APPWRITE_PROJECT_ID= APPWRITE_API_KEY= APPWRITE_DATABASE_ID=crm APPWRITE_COLLECTION_ID=contacts ``` Next, in the `src/lib` subdirectory, create a file `appwrite.js` and add the following code: ```js // src/lib/appwrite.js import { Client, Databases, ID } from 'node-appwrite'; import { env } from '$env/dynamic/private'; const client = new Client() .setEndpoint(env.APPWRITE_ENDPOINT) .setProject(env.APPWRITE_PROJECT_ID) .setKey(env.APPWRITE_API_KEY); const databases = new Databases(client); const databaseId = env.APPWRITE_DATABASE_ID; const collectionId = env.APPWRITE_COLLECTION_ID; // Add or update contacts async function upsertDocuments(contacts) { try { const documents = contacts.map((contact) => ({ $id: contact.$id || ID.unique(), name: contact.name, email: contact.email, phone: contact.phone, notes: contact.notes })); return await databases.upsertDocuments(databaseId, collectionId, documents); } catch (error) { console.error('Error upserting documents:', error); throw new Error('Failed to upsert documents'); } } // Delete a single contact async function deleteDocument(id) { try { return await databases.deleteDocument({ databaseId, collectionId, documentId: id }); } catch (error) { console.error('Error deleting document:', error); throw new Error('Failed to delete document'); } } // Delete all contacts async function deleteDocuments() { try { return await databases.deleteDocuments({ databaseId, collectionId, }); } catch (error) { console.error('Error deleting documents:', error); throw new Error('Failed to delete documents'); } } // List contacts async function listDocuments() { try { return await databases.listDocuments({ databaseId, collectionId }); } catch (error) { console.error('Error fetching documents:', error); throw new Error('Failed to fetch documents'); } } export const db = { upsertDocuments, deleteDocument, deleteDocuments, listDocuments }; ``` This code contains all our necessary database functions, including the bulk delete and upsert functions, to store and manipulate contact data in the database. #### Develop the web app Now that the Appwrite SDK is installed and the library functions are set up, let's prepare the web app. ##### Prepare an API route To prepare an API route for manipulating the database, head to the `src/routes` directory, create a subdirectory `api`, and another further subdirectory `contact`. Create a `+server.js` file and add the following code: ```js // src/routes/api/contact/+server.js import { db } from '$lib/appwrite.js'; import { ID, Query } from 'node-appwrite'; import { json } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; // GET - List all contacts export async function GET() { try { const response = await db.listDocuments(); return json(response.documents); } catch (error) { console.error('Error fetching contacts:', error); return json({ error: 'Failed to fetch contacts' }, { status: 500 }); } } // POST - Upsert multiple contacts (create or update) export async function POST({ request }) { try { const { contacts } = await request.json(); const response = await db.upsertDocuments(contacts); return json(response); } catch (error) { console.error('Error upserting contacts:', error); return json({ error: 'Failed to upsert contacts' }, { status: 500 }); } } // DELETE - Delete contacts export async function DELETE({ request }) { try { const { id } = await request.json(); let response; if (id) { // Single contact ID provided - delete a single document response = await db.deleteDocument(id); } else { // No ID provided - delete all documents response = await db.deleteDocuments(); } return json(response); } catch (error) { console.error('Error deleting contacts:', error); return json({ error: 'Failed to delete contacts' }, { status: 500 }); } } ``` ##### Add the page Next, we can add the page by heading to the `src/routes` directory and update the code in the `+page.svelte` file to the following: ```svelte Personal CRM

    📊 Personal CRM Spreadsheet

    Edit multiple contacts at once

    Total Contacts: {contacts.length} Modified: {modifiedCount}
    {#if loading}
    Loading contacts...
    {:else}
    {#each editingContacts as contact, index} {/each}
    Name * Phone Notes Actions
    markAsModified(index)} placeholder="Enter name..." class="cell-input" class:required={!contact.name.trim()} /> markAsModified(index)} placeholder="Enter phone..." class="cell-input" />
    {/if}

    💡 How to use:

    • Name and Email are required fields
    • Modified rows will be highlighted in blue
    • New rows will be highlighted in green
    • Click "Save All Changes" to batch save all modifications
    • Use "Add Row" to get an additional empty row for new contacts
    ``` You can implement styling in this app by creating an `app.css` file in the `src` directory and pasting the code from this [GitHub repo](https://github.com/appwrite-community/personal-crm/blob/original/src/app.css). ##### Enforce server-side rendering Lastly, create a `+layout.js` file in the `src/routes` directory and add the following code: ```js export const ssr = true; ``` ### Test the app To locally deploy and test the app, run the following command in your terminal: ```bash npm run dev ``` You can then visit `https://localhost:5173` in your browser and try out the app. ![Personal CRM demo](/images/blog/build-personal-crm-sveltekit/personal-crm-demo.png) ### Frequently asked questions (FAQs) **1. What’s the best tech stack for building a simple CRM or contact manager?** If you’re aiming for speed and control, a lightweight frontend like SvelteKit paired with a platform such as Appwrite is best. You get ready-to-use APIs for data, auth, and storage — no need to manage servers — and can still customize everything when needed. **2. How can I build a CRUD app quickly without setting up a full backend?** Use a service that gives you REST or SDK-based APIs for data operations. Appwrite, Firebase, or PocketBase let you perform create, read, update, and delete actions straight from your frontend. You just define your data schema once and use their SDKs instead of writing backend routes manually. **3. What’s the easiest way to sync frontend state with backend data?** If you’re using frameworks like SvelteKit or React, pair them with APIs that support real-time updates or bulk operations. Appwrite’s Realtime and Bulk APIs make syncing large data changes smooth, so the UI always reflects the latest backend state without page reloads. **4. How can I handle bulk data updates efficiently in web apps?** Instead of looping over every record, use batch APIs (like Appwrite’s upsertDocuments()). It minimizes network requests, prevents throttling, and speeds up updates in spreadsheet-style apps. **5. What’s the simplest way to deploy a full-stack web app?** If you’re using Appwrite, you can deploy both the backend and frontend in one place with Appwrite Sites. Otherwise, host your frontend on Vercel or Netlify, and connect it to an API from Appwrite, Firebase, or your own Node.js backend. ### Next steps And with that, our personal CRM built with Appwrite Databases and SvelteKit is ready! You can find the source code for this application in our [GitHub repo](https://github.com/appwrite-community/offline-journal). Learn more about Appwrite Databases: - [Bulk API docs](/docs/products/databases/bulk-operations) - [Appwrite Databases product docs](/docs/products/databases) --- ## Building apps with Bun and Appwrite https://appwrite.io/blog/post/building-apps-with-bun-and-appwrite If you are a developer, your definition of `bun` must have recently changed. From what we knew to be a round piece of bread, it is now a new runtime in JavaScript, and as Bun claims, it is faster than the rest! In this article, we will find out what Bun really is, how it compares with Node.js and Deno, and how you can build apps with Bun and Appwrite. ### What is Bun? Bun is an all-in-one JavaScript runtime & toolkit designed for speed, complete with a bundler, [test runner](https://bun.sh/docs/cli/test), and Node.js-compatible [package manager](https://bun.sh/package-manager). To install Bun, follow the instructions [here](https://bun.sh/). It is built from scratch to serve the modern JavaScript ecosystem. It has three major design goals: - **Speed** - **Elegant APIs** - **Cohesive Developer Experience** You may think, what is all the hype about? Building with JavaScript is now faster with Bun, which is production-ready with its latest version 1.0 release. Bun lets you read environment variables from a **`.env`** file and utilize the familiar **`fetch()`** method, enabling you to access and handle data from external sources within your application. Bun also natively supports TypeScript out of the box. Other than that, it also supports `.js`, `.cjs`, `.mjs`, `.jsx`, and `.tsx` files. *Bonus: Bun package manager is known for its speed, offering faster package management as one of its key features. Even if you don't use Bun as a runtime, you can use Bun's built-in package manager, that can significantly speed up your development workflow.* Try it for yourself: ```bash bun install bun add [--dev|--production|--peer] bun remove bun update ``` The powers of Bun are endless. If you are interested to know more about the capabilities, read the [release announcement](https://bun.sh/blog/bun-v1.0). **How is Bun so fast?** > Bun made some bold decisions to make this happen! It is not because they are using Zig or not using V8 nor because it's machine code. It is because of the mindset to make everything as highly performant as possible. > Other than that, there are things like: While installing a package, Bun doesn’t do a network check to see if it is downloading in the latest version. `@latest` tags are effectively ignored in Bun. Also, making small adjustments, such as creating a list of labeled pointers instead of keeping the function pointers separately, greatly improved the speed. ### Introduction to Appwrite Explore the capabilities of Bun with [Appwrite](https://appwrite.io/) - a backend platform to help you minimize time to create value. Appwrite abstracts away the complexities and repetitiveness of building a modern application so you can jump straight to the fun parts, building impactful features. Using Appwrite, you can quickly deploy and scale your Bun/JavaScript code base with ease. To achieve that, you can use Appwrite Functions. Appwrite Functions is available in multiple languages and runtimes (and Bun is one of them). This gives developers the ability to plug and play with Appwrite in the language of their choice. ![Bun start](/images/blog/building-apps-with-bun-and-appwrite/bun-start.png) Appwrite functions are capable of doing more. In our latest release, we have added `Function templates`, which means you’ll be able to add Functions engineered by the Appwrite team and community to your Appwrite project. Functions also have their own `domain`, either custom or generated by Appwrite. This lets you write Appwrite Functions that act like typical REST endpoints to handle webhooks, custom integrations, or even serve HTML content. Appwrite Functions will now also fit into your existing workflow right alongside the rest of your code as you can deploy them directly from `Git`. ### Integrating Bun in Appwrite Now that you have been introduced to Bun and Appwrite, it is time to test them out! To use Bun in Appwrite, you need to use the latest version of Appwrite. Installing a self-hosted version of Appwrite is pretty straight-forward, all you need to do is: 1. Have Docker installed 2. Run the command: ```bash "\t\t\t\t\tcontent: 'docker run -it --rm \\\n" + --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ appwrite/appwrite:1.8.1 ``` For one-click setups, check out the [installation docs](https://appwrite.io/docs/self-hosting). To use Bun in Appwrite, you need to add it to `_APP_FUNCTIONS_RUNTIMES` in the `.env` file and restart your Appwrite instance with `docker compose up -d`. ![Bun functions](/images/blog/building-apps-with-bun-and-appwrite/bun-functions.png) Next, go ahead and create a Bun function using the Appwrite CLI by running appwrite init function. Now that your function is set up, we can try some examples: **Example 1:** Bun aims to provide a consistent and predictable module resolution system that just works. The specialty of Bun is that you can use `import` or `require` in the same file—they both work all the time. Use the following code in your function to test the above statement: ```jsx import lodash from "lodash"; const _ = require("underscore"); export default async ({ res }) => { const numbers = [5, 5, 2, 5, 7]; return res.json({ withLodash: lodash.without(numbers, 5), withUnderscore: _.without(numbers, 5), }); }; ``` The output will be: ![Bun output](/images/blog/building-apps-with-bun-and-appwrite/bun-output.png) **Example 2:** Bun supports `.jsx` and `.tsx` files out of the box. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution. Let us test with the following code: ```jsx import { renderToString } from "react-dom/server"; export default async ({ res }) => { const html = ( <>

    Hello World!

    Current time is {new Date().toLocaleTimeString()}

    ); return res.text(renderToString(html), 200, { "Content-Type": "text/html", }); }; ``` *Note: For the code to work, you need to run `bun install react react-dom`* When the function successfully executes, you will get the following output: ![Bun output two](/images/blog/building-apps-with-bun-and-appwrite/bun-output-two.png) ### Takeaways In this article, we learned about Bun and using it with Appwrite. While we support Node.js, Deno, and Bun within Appwrite, it is up to you to choose which one you want to build with. While comparing the performance of these three runtimes with Appwrite, we have come up with some interesting results: - Build time - Bun is almost 3x faster than Node.js when installing un-cached dependencies for a Next.js project - Cold-start - Bun is almost 2x faster than Node.js, and 3x faster than Deno - Warm-start - All runtimes in similar ranges with differences ranging between 1-3ms - Load testing - All in similar ranges, with Bun and Node.js being the most stable > Benchmarks above are related to runtime used in Appwrite Functions. > Here are some resources to get started: - [Appwrite Function Docs](https://appwrite.io/docs/functions) - [Bun Docs](https://bun.sh/docs) - [Appwrite Discord](https://discord.com/invite/appwrite) With the benchmarks shared above and having the flexibility within Appwrite to build with any runtime, what would be your choice? --- ## How to build cross-platform applications with React Native https://appwrite.io/blog/post/building-cross-platform-applications-with-react-native Android, iOS, macOS, Linux, Windows, and the Web. Different platforms with different codebases. As a developer, you might have faced the challenge of building one application for multiple platforms. Considering the challenge of mastering all of the skills to build an application that performs well cross-platform, it’s no wonder we have seen the rise of cross platform frameworks such as React Native and Flutter. These frameworks allow you to create applications that run smoothly on multiple operating systems from a single codebase. This saves time, reduces costs, and reaches a larger user base. ### Why cross-platform? Why might one choose cross-platform development over developing applications for each platform? Based on the intro, it might seem logical, but let’s take a deeper look at why this is beneficial in some cases. There might be several reasons, but the most important ones are as follows: - Broader target: cross platform development enables developers to reach a broader target from the beginning by targeting multiple platforms. - Cost: cross platform development saves costs, as fewer developers are required to develop applications for multiple platforms. - Feature parity: Features across platforms can be deployed in parallel without discriminating the users. - Faster development time: With a single codebase, development cycles are shorter. Features bug fixes are implemented just once for all the platforms, increasing the delivery speed. - Simple codebase management: managing a single codebase is much simpler than managing multiple projects with different platform-specific languages and APIs. - Uniform design: It is easy to implement uniform designs across platforms and also platform specific changes if and when required. But as good as it might sound, there are also situations when choosing cross platform is not the best idea. - Advanced device features: When your application relies on advanced device features, and cutting-edge platform-specific features, then it’s a good idea to choose native over cross-platform development - High-performance requirements: Native development is preferred over cross-platform development for applications with maximum performance requirements, such as highly intensive graphical and computational requirements. - Fine control over memory management and processing: Native development is preferred when your application requires fine-grained control over memory management, processing power, and background tasks. For instance, an application that runs intensive algorithms on large datasets in real-time would benefit a lot by going native due to access to low-level APIs and system resources. - Development skill set: If your team is already fluent in Native development but have to learn cross-platform development from scratch and you don’t have time and resource for that, it’s beneficial to choose native development. Choosing the right tool for your application is crucial whether going native or cross-platform, and as we've seen, cross-platform development offers a range of benefits that are hard to ignore. Some of these can be a major win for businesses trying to save cost and target large user bases. However, you should decide based on your project's requirement and your team's skillset and preferences. ### How do cross-platform frameworks work Cross platform frameworks provide a high level unified API in a single language. For example, Flutter uses Dart, React Native uses Javascript, and Xamarin (now .NET MAUI) uses C#. The code written in these languages is compiled for specific platforms mostly ahead of time during the build and deployed mostly as native code. Some frameworks like React Native also use bridging, where the framework interprets the javascript code and communicates with the native element through the bridge. UI elements can be rendered in two ways - Native rendering: map the UI to native platform specific components. React Native does this. this allows apps to maintain a native look and feel - Custom rendering engine: A framework like Flutter completely bypasses the native UI and renders the components with its own rendering engine. This provides more control over the appearance and behavior of the UI Finally, when the application needs to handle native device-specific functionalities, all cross-platform frameworks do this via plugins. Plugins write the platform-specific codes required for each platform and provide a high-level API for developers to use. Whenever there is a lack of specific functionalities, the framework provides a way for developers to write their own custom plugins to handle those specific functionalities. ### Try React Native for your next project There are many great cross-platform framework technologies, like [Flutter](/docs/quick-starts/flutter), .NET MAUI, Ionic, and more. Each cross-platform frameworks have their advantages and disadvantages and we are not here to say one is better. Which framework to choose depends on one’s preference, previous knowledge and skill set, project requirements, and more. But to celebrate the release of the [Appwrite React Native SDK](https://github.com/appwrite/sdk-for-react-native) in open beta, we want to share a few points that make React Native an attractive choice. - Learn once, write anywhere: If you already know JavaScript and React, you can easily learn and start using React Native. So, this is already an attractive choice for JavaScript developers, and it might be the one framework with the easiest learning curve - Native performance: React native communicates with native platform components directly, which leads to a responsive user experience close to native performance - Large community and ecosystem: React and React Native, created by Facebook, are popular frameworks, leading to large community and ecosystem support with content and packages. React Native applications also benefit from the huge number of packages of JavaScript - Live and hot reloading: Live and hot reloading features enhance developer productivity by allowing developers to iterate faster. - Integrating with existing applications: Another major attraction point is developers can integrate React Native into their existing applications if they already have one. ### Get started with React Native and Appwrite React Native allows you to create applications that run on multiple operating systems from a single codebase, whereas Appwrite provides you with a full-functioning backend with just a few lines of code. This means that you can build a full functioning app, targetting users across iOS, Android, Mac OS, Linux, Windows, and the Web, and cut down your development time tremendously. To get started, follow these steps. #### Step 1: Set up Appwrite First, you must set up an Appwrite project. To do so, you must either [create an Appwrite Cloud account](https://cloud.appwrite.io) or [self-host Appwrite](/docs/advanced/self-hosting) on your system. ![Get started with React Native](/images/blog/overview-react-native.png) Once that is done, under **Add a platform**, add an **Android app** or an **Apple app**. You can skip optional steps. - **iOS steps:** Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. - **Android steps:** Add your app's **name** and **package name.** Your package name is generally the **applicationId** in your app-level `build.gradle` file. ### Step 2: Install the React Native SDK Once Appwrite is up and running, install the React Native SDK in your project. This will connect your React Native app with Appwrite's suite of backend services. You can install the SDK (and necessary dependencies) using the following command: ```bash npx expo install react-native-appwrite react-native-url-polyfill ``` #### Step 3: Explore the Documentation Familiarize yourself with [Appwrite's documentation](/docs). It's packed with tutorials, examples, and API references designed to get you up to speed in no time. #### Step 4: Start Building With everything in place, you're ready to start building your app. The React Native SDK is designed to be intuitive, allowing you to implement features like authentication, database operations, and file storage with ease. ```jsx import { Client, Account, ID } from 'react-native-appwrite'; const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint .setProject('455x34dfkj') // Your project ID .setPlatform('com.example.myappwriteapp'); // Your application ID or bundle ID const account = new Account(client); // Register a user account.create({ userId: ID.unique(), email: 'me@example.com', password: 'password', name: 'Jane Doe' }) .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` Read the [quick start](/docs/quick-starts/react-native) to get started or find a [tutorial](/docs/tutorials/react-native/step-1) to build an ideas tracker with React Native. ### Resources Visit our documentation to learn more about our SDKs, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our source code. - [Docs](/docs/sdks) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Building culture in a remote world: Camp 5.0 recap https://appwrite.io/blog/post/building-culture-remote-camp As a fully remote company, we know that crafting a strong company culture takes a bit more effort since we don’t get to gather in person as often as traditional teams do. To bridge that gap, our amazing operations team organizes Camps — week-long events where our team travels to a location to work, collaborate and have fun. We just wrapped up Camp 5.0 in beautiful Barcelona, and wow, what a week it was! Let’s look back at what we got up to in Barcelona and how Appwrite fosters a strong company culture and team connections in a remote-first setup. ![Appwrite Camp 5 team photo](/images/blog/camp-5-barcelona/1.png) ### Core values for building remote-first company culture At Appwrite, we believe a successful remote-first culture is built on a few core principles that guide our everyday interactions and long-term goals. We stick to an **async-first** communication for remote work to make this possible: - **Transparency**: Open communication is the bedrock of our company. Whether you're in product, growth or success, we ensure that everyone stays in the loop through regular async company-wide updates, team dailies and by cc-ing relevant people or teams in discussions. - **Collaboration**: Collaboration is at the heart of open-source software. We actively encourage collaboration across departments through project collaboration channels on Discord, where everyone can share feedback freely without being confined to their team channels. - **Work-life balance**: Flexibility is a core part of our approach. We understand that maintaining a healthy work-life balance is essential to long-term productivity and well-being. After all, a motivated team is a productive team! ### The goals of Camp While we’ve mastered the art of remote work, creating an efficient and productive team, there’s no denying the benefits of in-person collaboration. That’s why we make the most of these rare opportunities to come together. Here’s what we aim to get out of our Camps: - **Build relationships**: In a remote-first setup, building personal connections can be challenging. Our Camps give the team a rare chance to meet in person, build stronger bonds, and develop trust. ![Team photo 1](/images/blog/camp-5-barcelona/2.png) - **Regain focus**: Sometimes stepping away from the usual day-to-day routines helps clear the mind. Camps allow the team to reset and focus on what matters most — whether it's planning for the next product update or brainstorming new ideas. ![Team photo 2](/images/blog/camp-5-barcelona/3.png) - **Accelerate work**: Camps aren’t just a time to relax; they’re an opportunity to kick things into high gear. We always include a few intense collaboration sessions to help accelerate work or break through the usual blockers. ![Team photo 3](/images/blog/camp-5-barcelona/4.png) - **Have fun**: While work is important, we also enjoy each other's company. [Dylan](https://appwrite.io/blog/post/appwrite-decoded-dylan), the Camp organizer, ensures we get a mix of work and fun with planned workshops, food tours, and company dinners. ![Team photo 4](/images/blog/camp-5-barcelona/5.png) ### A short recap of Camp 5.0 in Barcelona #### Work sessions This year, we focused on a few short but intense work sessions to help us regain focus and align on future goals. During sessions, all teams present their progress and share plans. This is a chance for everyone to get on the same page about the company's future, ask questions, and contribute ideas. To switch things up, we introduced a new Team Lead Insight session, where team leads shared their perspectives on leadership, work processes, and maintaining a healthy work-life balance. It also gave team members the opportunity to ask questions and engage in open, honest discussions. ![Team Lead Insight](/images/blog/camp-5-barcelona/6.png) #### Bug’athon After the last camp Hackathon, we went a different route in Barcelona: the Bug’athon was a new addition this year, a fast-paced, competitive coding event that perfectly captures our startup’s spirit of working smart and growing fast. The rules were simple: people divided into teams (which didn’t have to match official roles) to find and squash bugs as quickly as possible. We had a tally on the big screen and a buzzer for each team. In the end, the team with the most bugs squashed took home the prize. It was not only a fun way to foster teamwork but also a great opportunity to boost the platform's stability and tackle blockers together. Can you guess which team won? ![Bugathon](/images/blog/camp-5-barcelona/7.png) #### Team-building workshops In Barcelona, we took part in 2 team-building workshops: mosaic building and graffiti creation, both led by [local artists](https://www.instagram.com/daveandworld?igsh=ZzZmN2Y2aTN1bWNp&utm_source=qr). These were creative challenges none of us had tackled before, and with a team ranging in design skills but overflowing with creativity, it was exciting to see how we organized roles and adapted to these new tasks. What we created in the end was best summed up by our founder Eldad: ![Mosaic](/images/blog/camp-5-barcelona/8.png) ### Conclusion Camp 5.0 was an intense but exceptional week, reaffirming our culture and values. We didn’t just get a lot of work done — we had fun doing it. Plus, we used this time to film something very exciting for you, so stay tuned! Investing in our culture — through engaging activities like Camps — is a key factor in Appwrite’s ongoing success as a fully remote company. Here’s to more adventures, more laughs, and more breakthroughs in the future! --- ## Building custom authentication flows with Appwrite https://appwrite.io/blog/post/building-custom-auth-flows While Appwrite provides built-in authentication methods like email/password, OAuth, and Magic URL, there are scenarios where you need more flexibility. You might have an existing user database, a legacy authentication system that needs to be maintained, or specific security requirements that demand custom implementation. Appwrite's custom authentication solves these challenges by allowing you to integrate your existing authentication system or third-party identity providers. You can validate users through your system while still benefiting from Appwrite's session management and user features. In this guide, you'll learn how to implement custom authentication flows using Appwrite's custom tokens. We'll cover validating users through an external system, generating custom tokens, creating secure sessions, and managing the complete authentication lifecycle. ### What we'll build A simple authentication demo that: - Uses a simulated third-party authentication system - Integrates with Appwrite's custom token authentication - Provides a login/logout flow with session management ### Setting up your project Before diving into the code, let's ensure you have the necessary prerequisites in place. Start by verifying your Node.js installation in your local environment: ```bash node --version ``` Next, you'll need to set up your Appwrite project. Head over to the [Appwrite Console](https://cloud.appwrite.io/) and either create a new project or open an existing one. Make sure to note down your Project ID, which you can find in the Settings page. To enable custom authentication, you'll need an API key with the appropriate permissions. In your project's overview page, navigate to the **Integrations** section and click on **API Keys**. Create a new API key with a suitable name (e.g., "Custom Auth") and optionally set an expiry date. When configuring permissions, ensure you select the **Auth** scope. After creation, securely save your API key as you'll need it in the next steps. ### Project setup Let's start by creating a new Vite project. We'll use vanilla JavaScript for this tutorial to keep things simple and focused: ```bash npm create vite@latest . -- --template vanilla ``` Our project will require several dependencies to handle both frontend and backend functionality. Install them using npm: ```bash npm install appwrite cors dotenv express node-appwrite ``` Now, let's set up our environment configuration. Create a `.env` file in your project root to store your Appwrite credentials and other important variables: ``` VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 VITE_APPWRITE_PROJECT_ID=your_project_id VITE_BACKEND_URL=http://localhost:3000 ### Server-only variables APPWRITE_API_KEY=your_api_key APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 APPWRITE_PROJECT_ID=your_project_id ``` To properly configure our project for modern JavaScript modules and add convenient scripts, update your `package.json`: ```json { "type": "module", "scripts": { "dev": "vite", "server": "node server.js", "build": "vite build", "preview": "vite preview" } } ``` ### Backend implementation The backend server will handle user validation and generate Appwrite custom tokens. Create a new file named `server.js` in your project root. Let's break down the implementation into logical sections. First, we'll set up our basic server infrastructure by importing dependencies and loading environment variables: ```js import express from 'express' import cors from 'cors' import { Client, Users } from 'node-appwrite' import dotenv from 'dotenv' // Load environment variables dotenv.config() ``` Before proceeding, we should validate that all required environment variables are present. This helps catch configuration issues early: ```js // Validate required environment variables const requiredEnvVars = [ 'APPWRITE_ENDPOINT', 'APPWRITE_PROJECT_ID', 'APPWRITE_API_KEY', ] for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { console.error(`Missing required environment variable: ${envVar}`) process.exit(1) } } ``` With our environment validated, we can set up the Express server and configure necessary middleware: ```js const app = express() app.use(cors()) app.use(express.json()) ``` Now we'll initialize the Appwrite client with our configuration: ```js // Initialize Appwrite const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY) const users = new Users(client) ``` For demonstration purposes, we'll create a simulated user database. In a real application, this would be replaced with your actual user database or authentication system: ```js // Simulate a third-party auth database const thirdPartyUsers = { 'demo@example.com': { password: 'demo1234', name: 'Demo User', id: 'external_123', }, } ``` {% info title="Security Note" %} This simulation uses plain text passwords for simplicity. In a production environment, always use secure password hashing and proper security measures. {% /info %} Let's implement the login endpoint that will handle authentication requests: ```js // Simulate third-party login app.post('/auth/external/login', async (req, res) => { const { email, password } = req.body // Simulate external auth validation const user = thirdPartyUsers[email] if (!user || user.password !== password) { return res.status(401).json({ message: 'Invalid credentials' }) } try { // Check if user exists in Appwrite try { await users.get(user.id) } catch { // User doesn't exist, create them await users.create( user.id, email, undefined, // phone undefined, // password can be undefined for custom auth user.name, ) } // Create Appwrite token and return it to the client await users.get({ userId: user.id }) await users.create({ userId: user.id, email, phone: undefined, password: undefined, // can remain undefined for custom auth name: user.name, }) const token = await users.createToken({ userId: user.id }) res.json({ userId: user.id, secret: token.secret, name: user.name, }) } catch (error) { res.status(500).json({ message: error.message }) } }) ``` This endpoint handles several important tasks: - Validates user credentials against our simulated database - Creates the user in Appwrite if they don't already exist - Generates a custom token for the authenticated user - Returns the token and user information to the client Finally, let's start the server on our specified port: ```js const PORT = process.env.PORT || 3000 app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`) }) ``` With the server implementation complete, you can start it using: ```bash npm run server ``` ### Frontend implementation Now that our authentication server is running, let's create an intuitive user interface. We'll break this down into several parts: HTML structure, styling, and JavaScript logic. #### HTML structure and styling The foundation of our frontend starts with a clean HTML structure. We'll create a simple container-based layout that will house our authentication components. In your `index.html` file, add the following code: ```html Appwrite Custom Auth Demo
    ``` To ensure our interface is easy to use, we'll add some CSS styles: ```html ``` To help users understand what the demo does, we'll add an informative section at the top which will include the test credentials: ```html

    Custom Token Auth Demo

    This demonstrates using Appwrite's custom token authentication with a simulated third-party auth system.

    Try: demo@example.com / demo1234

    ``` The main authentication interface consists of two views: the login form and the logged-in state. First, let's create the login form with proper input validation: ```html

    External Auth System

    ``` We also need a view for when the user is successfully authenticated. This view will display the user's information and provide a logout option: ```html ``` #### JavaScript implementation Now let's implement the frontend logic in `src/main.js`. We'll break this down into specific parts that work together to handle the authentication flow. First, we set up the connection to Appwrite. This code tells our frontend how to talk to Appwrite's servers: ```js import { Client, Account } from 'appwrite' // Initialize Appwrite const client = new Client() .setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT) .setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID) const account = new Account(client) ``` We need quick access to our HTML elements to show/hide them and update their content. These variables help us do that: ```js // DOM Elements const loginForm = document.getElementById('loginForm') const loggedInView = document.getElementById('loggedInView') const userInfo = document.getElementById('userInfo') ``` The `handleExternalAuth` function is the main piece of our login process. It takes the user's email and password and does four specific things: ```js // Handle external auth and Appwrite session creation async function handleExternalAuth(email, password) { try { // First, authenticate with external system const response = await fetch( `${import.meta.env.VITE_BACKEND_URL}/auth/external/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }), }, ) const data = await response.json() if (!response.ok) throw new Error(data.message) // Then create Appwrite session using the custom token const session = await account.createSession({ userId: data.userId, secret: data.secret }) await account.deleteSession({ sessionId: 'current' }) // Show logged in state loginForm.classList.add('hidden') loggedInView.classList.remove('hidden') userInfo.textContent = `Logged in as: ${data.name}` return session } catch (error) { throw new Error('Authentication failed: ' + error.message) } } ``` This function: - Sends the login details to our backend - Gets back a token if the login is successful - Creates an Appwrite session with this token - Shows the logged-in screen with the user's name Next, we add click handlers to our login and logout buttons. These functions run when users click the buttons: ```js // Handle login button click document.getElementById('loginButton').addEventListener('click', async () => { const email = document.getElementById('email').value const password = document.getElementById('password').value try { await handleExternalAuth(email, password) } catch (error) { alert(error.message) } }) // Handle logout document.getElementById('logoutButton').addEventListener('click', async () => { try { await account.deleteSession('current') loginForm.classList.remove('hidden') loggedInView.classList.add('hidden') } catch (error) { alert('Logout failed: ' + error.message) } }) ``` These click handlers: - Get the email and password from the form - Try to log the user in or out - Show error messages if something goes wrong - Switch between the login and logged-in screens Lastly, we check if the user is already logged in when they load the page: ```js // Check auth status on load async function checkAuth() { try { const session = await account.get() loginForm.classList.add('hidden') loggedInView.classList.remove('hidden') userInfo.textContent = `Logged in as: ${session.name}` } catch { loginForm.classList.remove('hidden') loggedInView.classList.add('hidden') } } checkAuth() ``` This check is important because it: - Looks for an existing login session - Shows the logged-in screen if a session exists - Shows the login form if no session is found ### Running the application To run the application, you'll need to start both the backend and frontend servers. First, start the backend server: ```bash npm run server ``` Then, in a new terminal window, start the frontend development server: ```bash npm run dev ``` Navigate to the URL shown by Vite (typically `http://localhost:5173`) in your browser. You can test the authentication using these credentials: - Email: `demo@example.com` - Password: `demo1234` ### Putting it all together Now that we have all the pieces in place, let's do a quick rundown of what happens when a user logs in. Once a user submits their login details, their credentials go to our backend server. The server checks these against our user database and, if they're valid, asks Appwrite to create a custom token. This token comes back to the frontend along with user information. Finally, the frontend uses this token to create an Appwrite session, which keeps the user logged in and lets them access protected resources. ### Next steps To enhance this basic implementation, consider these improvements: - Replace the simulated user database with your actual authentication system - Add comprehensive error handling and input validation - Implement user registration functionality - Improve the UI with loading states and better error messages. You might want to use a UI framework or template engine depending on your project's requirements. ### Conclusion This tutorial has demonstrated how to implement custom authentication with Appwrite. You've learned how to: - Set up a custom authentication server integrated with Appwrite - Generate and manage Appwrite custom tokens - Create a frontend that handles the authentication flow - Implement secure session management While we've used a simple in-memory user database for demonstration, these concepts apply to any external authentication provider, whether it's your own user database or third-party identity providers. Remember that authentication is an important security component. Before deploying to production, ensure you've implemented proper security measures, error handling, and user management features. The complete source code for this tutorial is available in our [GitHub repository](https://github.com/appwrite-community/appwrite-custom-auth). ### Frequently asked questions (FAQs) **1. What exactly are custom authentication flows?** They’re login systems you build yourself instead of relying on standard providers like Google or Firebase. You decide how users are verified, how tokens or sessions are issued, and how identity data is stored. It’s useful when your app needs unique logic or you already manage users elsewhere. **2. When does it make sense to build a custom auth flow?** Mostly when existing solutions don’t fit, for example, you have a legacy user database, need to integrate with an enterprise SSO, or want to control every step of the login process. If your app’s auth logic is straightforward, built-in options are faster; if it’s not, custom flows give you freedom. **3. What’s the most common mistake when building custom auth?** Mixing frontend and backend responsibilities. Token creation and validation should always happen on the server. Another one is skipping input validation or leaving long-lived tokens active. Small security gaps in auth often turn into big problems later. **4. Can I combine custom auth with other login methods like OAuth or magic links?** Yes. It’s actually common to offer multiple login options, say, Google or GitHub for convenience and your own system for internal or enterprise users. You can unify them under the same session layer once they’re verified. **5. Can I create custom authentication flows on Appwrite?** Yes. Appwrite lets you build your own auth flow using custom tokens. That means your backend can verify users however you want, through an existing database, SSO, or a third-party system, and then generate a token that Appwrite uses to create a secure session. You get full control over user validation while Appwrite handles session management, permissions, and APIs. ### More resources - [Appwrite Custom token login](https://appwrite.io/docs/products/auth/custom-token) - [Appwrite Authentication docs](https://appwrite.io/docs/products/auth) - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) --- ## Building the giveaway app for Init Discord sessions https://appwrite.io/blog/post/building-init-giveaway-app Last week, we saw the culmination of a whole new initiative, the celebration of everything new with Appwrite and the community, called [Init](https://appwrite.io/init). From February 26th to March 1st, we celebrated a new product and/or feature every day and shared educational content around the same. Alongside, we hosted online events each day in the Appwrite Discord server with creators and friends of Appwrite to learn about the new releases, ask questions, and, most of all, geek out together. One popular aspect of these events was a random giveaway that we ran every single day, giving out Init swag to a number of our community members. When planning out these giveaways, however, we wanted to create a high-quality experience for our community members, to make it as special and exciting as every other aspect of Init. Therefore, using Discord OAuth and Databases in Appwrite, we built our very own “Spin The Wheel” giveaway roulette application! #### What the app does The giveaway app has two major features: - **Giveaway registration** The giveaway registration requires users to log in to the app with their Discord account. As soon as a user logs in with Discord, the app adds their Discord username and email address to an Appwrite collection. ![Sign In With Discord](/images/blog/building-init-giveaway-app/signin.png) - **Winner selection** The data added to the Appwrite collection is loaded in a wheel that is also updated in real time in case new registrations are added. The wheel can then be spun like a roulette wheel to find a randomly selected winner. ![Spin The Wheel](/images/blog/building-init-giveaway-app/winner.png) ### How we built it To discuss how the app was built, we’ll discuss the part of setting up Appwrite and implementing the code as two separate parts. #### Setting up Appwrite To build this application, we used the Discord OAuth adapter in Appwrite Auth and Appwrite Databases. The only prerequisite here was creating an [Appwrite Cloud account](https://cloud.appwrite.io), followed by creating a new project and adding your hostname as a web app to the project. ##### Discord OAuth Implementing Discord OAuth required us to first set up an application on the [Discord Developer Portal](https://discord.com/developers/applications). After creating an application, we visited the OAuth2 tab within the application to get the **client ID** and **secret** to set up the Discord **OAuth adapter** in our Appwrite project. We also copied to **redirect URI** to add it to our Discord Developer App. ![Discord OAuth Adapter](/images/blog/building-init-giveaway-app/discord.png) ##### Appwrite Database We created a **database** and a **collection** for each day of the giveaway in our Appwrite project. Each collection had the same steps to set up. Each collection contained two **attributes**: | Key | Type | Size | Required | | ----------- | ----------- | ----------- | ----------- | | discordName | String | 255 | Yes | | email | Email | - | Yes | To ensure that one user could only be added once for a day’s giveaway, we created an **index** of the `unique` type. | Key | Type | Attribute | Order | | ----------- | ----------- | ----------- | ----------- | | names | unique | discordName | ASC | Lastly, we added **collection-level permissions** to allow anyone to read data (since the giveaway page was meant to be publicly accessible) and only authenticated users to add data (their Discord username and email address) to the collection. | Role | Create | Read | Update | Delete | | ------ | ------ | ------ | ------ | ------ | | Any | | Yes | | | | Users | Yes | | | | #### Setting up the app Looking at the two primary features, there were three primary challenges we needed to solve to develop this app. To build this app, we used SvelteKit, a framework to build web applications using JavaScript. There are some prerequisites, however, that must be completed before building out the features themselves. > Note: The code snippets will focus only on the application logic. All CSS and styling-related information will be accessible in the final project repository at the end of the blog. ##### Prerequisites We first set up a skeleton SvelteKit project. ```bash npm create svelte@latest init-giveaway-roulette ``` We then entered the application directory, installed the Appwrite Web SDK and Pink Design using `npm install appwrite "@appwrite.io/pink"`, and set up our constants. ```js // ./src/lib/constants.js export const APPWRITE_ENDPOINT = 'https://.cloud.appwrite.io/v1'; export const APPWRITE_PROJECT = ''; export const DATABASE_NAME = ''; export const COLLECTION_NAME = ''; ``` After the constants were added, we could now set up the Appwrite SDK. ```js // ./src/lib/appwrite.js import { Account, Client, Databases, OAuthProvider } from 'appwrite'; import { APPWRITE_ENDPOINT, APPWRITE_PROJECT } from './constants'; export const client = new Client(); client.setEndpoint(APPWRITE_ENDPOINT).setProject(APPWRITE_PROJECT); export const account = new Account(client); export const database = new Databases(client); export { OAuthProvider }; ``` Once this is done, we could move forward to create the main application. ##### Login using Discord First, we used the Appwrite Web SDK to set up our Auth library in the application. We used `account.createOAuth2Session` in the `add` function to leverage our Discord OAuth adapter. The `get` function returns the current logged-in user’s details. ```js // ./src/lib/user.js import { account, OAuthProvider } from './appwrite'; export const user = { login: async () => { account.createOAuth2Session({ provider: OAuthProvider.Discord, success: `https://${window.location.hostname}/success`, failure: `https://${window.location.hostname}/failure` }); }, get: async () => { return await account.get(); } }; ``` The `login` function was called through a button click on the index page of our application to start the authentication process. ```js // .src/routes/+page.svelte
    Plain black T-shirt with the text Init and a keyboard branded with the Appwrite logo on the Escape key

    Login with Discord and get a chance to win some amazing gifts!

    ``` ##### Adding data to Appwrite Database To add or get information from the database, we created a database library in the project. The `add` function adds the Discord username and email to our collection. The `list` function gets the list of all documents in our collection and returns it after reformatting for our roulette wheel for the giveaway. ```js // ./src/lib/database.js import { Query, ID } from 'appwrite'; import { database } from './appwrite'; import { COLLECTION_NAME, DATABASE_NAME } from './constants'; export const db = { list: async () => { let entries = await database.listDocuments({ databaseId: DATABASE_NAME, collectionId: COLLECTION_NAME, queries: [ Query.limit(500), Query.select(['discordName']) ] }); var options = []; entries.documents.forEach((entry) => { options.push({ label: entry.discordName }); }); return { options: options, total: entries.total }; }, add: async (discordName, email) => { try { await database.createDocument({ databaseId: DATABASE_NAME, collectionId: COLLECTION_NAME, documentId: ID.unique(), data: { discordName: discordName, email: email } }); console.log('Added to the raffle'); } catch (error) { console.log(error.message); } } }; ``` We integrated the `add` function directly into the `success` endpoint for our OAuth process so that the user’s information could be added to the database as soon as we had a successful login. We use Svelte’s `onMount` function to achieve this as soon as our page is rendered in the DOM. ```js // .src/routes/success/+page.svelte

    Success!

    Thanks for participating in the giveaway, {userId}

    ``` ##### Creating our giveaway roulette To create our giveaway roulette, we used an NPM package [`spin-wheel`](https://www.npmjs.com/package/spin-wheel). To set this up, we first used a `load` function to get the list of Discord usernames that were already available in the collection before the giveaway page was rendered. ```js // .src/routes/giveaway/+page.js import { db } from '$lib/database'; export async function load() { var dbResponse = await db.list(); return { entries: dbResponse.options, total: dbResponse.total }; } ``` Using the load function would ensure that this list would be available on our page as soon as it gets rendered. We used this list with the `spin-wheel` package to generate a roulette wheel. As a bonus, we also integrated Appwrite Realtime to add new Discord usernames to our list and regenerate the wheel with the updated data. We let the winner selection remain mathematically random and used the `spin-wheel` package to spin the roulette wheel and show us the winner. The wheel spinning function also required us to install an additional NPM package `easing-utils` to select the easing function defining the rate of change in the rotation speed of the wheel. ```js // .src/routes/giveaway/+page.svelte
    ``` ### Outcome The giveaway roulette app was used over 450 times by 230+ different users across the different Init events we hosted. The application is still live and can be tried through the following links: - Participate in the giveaway roulette: [giveaway.appwrite.io](https://giveaway.appwrite.io) - Giveaway roulette: [giveaway.appwrite.io/giveaway](https://giveaway.appwrite.io/giveaway) You can find the application’s complete source code at this [GitHub Repo](https://github.com/adityaoberai/InitGiveawayRoulette). [Join us on Discord](https://appwrite.io/discord) to be the first to get updates and to be part of a vibrant community! --- ## Building with Appwrite AI Function templates https://appwrite.io/blog/post/building-with-ai-function-templates It’s an exciting time for software development, as many new concepts and techniques pop up every day, giving us endless possibilities to build new and shiny things. But this sea of opportunity can be hard to navigate and keep up with. AI, for instance, is a field that is rapidly evolving and is influencing not only the products we can build but also the way we develop them. However, building AI-powered applications can be complicated. Appwrite’s new AI Function templates make it easier to build AI powered applications. So, what did we add? This blog provides an overview of all the concepts we’ve added, tutorials, templates, and the integrations we support. ### Computer vision [Computer vision](https://appwrite.io/blog/post/state-of-computer-vision) is a field of AI aiming to provide machines with a comprehensive understanding of visual data from a variety of sources. Two sub techniques we added support for are image classification and object detection. Both techniques are widely used in our day to day lives, let’s take a look. #### Image classification Image classification is a process used in machine learning and artificial intelligence to categorize images into different groups based on their content. It’s a technique that serves many use cases, such as: 1. **Healthcare**: In the medical field, image classification is used to analyze medical images, such as X-rays, MRIs, and CT scans, to detect and diagnose diseases. For example, AI can identify patterns in imaging that may indicate tumors, fractures, or other abnormalities. 2. **Autonomous vehicles**: Self-driving cars use image classification to understand their surroundings. Cameras capture images that the system classifies in real-time to identify road signs, pedestrians, other vehicles, and various obstacles to navigate safely. 3. **Retail**: Image classification can automate inventory management by identifying products on shelves. It's also used in visual search systems, allowing customers to search for products by uploading images instead of using text-based queries. #### Function templates added to the Console - Image Classification - Classify images using the Hugging Face inference API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/image-classification" title="Image classification" %} Understand and label the contents of images {% /cards_item %} {% /cards %} #### Object detection Object detection is a technology used in computer vision that not only categorizes images but also identifies and locates multiple objects within a single image. It's a step beyond basic image classification because it involves not just identifying what objects are present in an image, but also pinpointing their specific boundaries (usually through bounding boxes). The technique has made many media appearances, and companies like Tesla and Apple have widely used it in their products. Some examples: 1. **Autonomous driving**: Object detection is critical for autonomous vehicles to interpret their surroundings accurately. It helps cars detect and localize objects like pedestrians, other vehicles, traffic lights, and road signs, facilitating safe navigation and decision-making. 2. **Face recognition**: In security and surveillance, object detection is used for facial recognition systems. These systems identify and verify individuals by detecting faces in images or video streams, useful in law enforcement, secure facility access, and authentication systems. 3. **Retail**: In retail, object detection is used for customer behavior analysis, stock management, and theft prevention. Cameras can detect when items are picked from shelves, track customer movements, and identify actions that might indicate suspicious behavior. #### Function templates added to the Console - Object Detection - Detect objects in images using the Hugging Face inference API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/object-detection" title="Object detection" %} Detect and label objects in images {% /cards_item %} {% /cards %} ### Natural language processing [Natural language processing](https://appwrite.io/blog/post/state-of-natural-language-processing) (NLP) is a fascinating intersection of computer science, artificial intelligence, and linguistics. It's about teaching computers to understand, interpret, and generate human language. Translating languages, answering questions, or helping find information, NLP is at the heart of many technologies we use every day. Two techniques that Appwrite Function templates support are text generation and language translation. #### Text generation Text generation, facilitated by technologies like artificial neural networks, specifically those designed for natural language processing (NLP), has a wide range of applications. The most well known applications are probably chatbots, but it supports a lot more applications you (probably) use every day: 1. **Personal assistants**: Digital assistants such as Siri, Alexa, and Google Assistant use text generation to converse with users, manage tasks, and provide information in a natural, conversational manner. 2. **Coding and development**: AI-driven text generation aids developers by suggesting code completions, generating documentation, and even writing code snippets based on descriptions of functionality. 3. **Creative writing**: AI can assist in generating creative text for stories, poetry, scripts, and even music compositions, providing a new tool for artists and writers to explore creative ideas. #### Function templates added to the Console - Text Generation - Generate text using the Hugging Face inference API. - Chat - Create a chatbot using the Perplexity AI API. - Prompt ChatGPT - Ask questions and let OpenAI GPT-3.5-turbo answer. - RAG with LangChain - Generate text using a LangChain RAG model. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/text-generation" title="Text generation" %} Generate human-like text {% /cards_item %} {% /cards %} #### Language translation Language translation, particularly when facilitated by advanced artificial intelligence (AI) technologies, is a crucial application of natural language processing (NLP) that serves numerous practical and essential purposes. Thanks to this technological advancement, we no longer need to master a language to order food in a foreign restaurant. Here are more use cases: 1. **Translation services**: Language translation is critical in machine translation services like Google Translate, which convert text from one language to another. While not perfect, these services are continually improving and are invaluable for global communication. 2. **Education and learning**: Translation tools help students and educators access a broader range of educational materials and resources. They allow people to learn languages independently or access courses and content that are not available in their native language. 3. **Travel and tourism**: Translation apps and devices are indispensable for travelers, helping them navigate foreign environments, read signs and menus, communicate with locals, and understand cultural nuances. #### Function templates added to the Console - Analyze - Automate moderation by getting the toxicity of messages. - Language Translation - Translate text using the Hugging Face inference API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/language-translation" title="Language translation" %} Translate text between languages {% /cards_item %} {% /cards %} ### Audio processing [Audio processing](https://appwrite.io/blog/post/state-of-audio-processing) is a field of machine learning that allows machines to understand, analyze, and manipulate various audio signals. The applications are vast and varied, from speech recognition to music generation and noise reduction. It's used in many everyday tools you use, including voice assistants, music streaming services, and for noise reduction in online calls. We’ve added support for speech to recognition, text-to-speech, and even music generation. #### Speech to recognition Speech recognition technology, which converts spoken language into text, is a vital component of modern computing interfaces and has numerous applications across various sectors. One of its most known use cases is the ability to turn speech into text messages on your phone. But who actually uses this? Here are some better use cases: 1. **Virtual assistants**: Devices like smartphones and smart speakers use speech recognition to enable users to interact with them through voice commands. Virtual assistants like Siri, Alexa, and Google Assistant respond to inquiries, control smart home devices, set reminders, and play music, all initiated by voice. 2. **Accessibility**: Speech recognition significantly enhances accessibility for individuals with disabilities. It allows people who have difficulty using conventional input devices (like keyboards and mice) to interact with computers and mobile devices using their voice. 3. **Customer service**: Many companies employ speech recognition in their customer service operations, using voice-driven interfaces to handle customer inquiries, automate responses, and route calls to appropriate departments without human intervention. #### Function templates added to the Console - Speech Recognition - Transcribe audio to text using the Hugging Face inference API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/speech-recognition" title="Speech recongition" %} Efficiently convert speech to text {% /cards_item %} {% /cards %} #### Text to speech Text-to-speech (TTS) technology converts written text into spoken words, and these days, it has made headlines for its help in creating deep fakes. But it’s not all bad as it can be a very helpful tool for many use cases such as: 1. **Consumer electronics**: Many devices, including smartphones and computers, incorporate TTS to read text aloud, from navigation directions in GPS systems to operating instructions and notifications. 2. **Telecommunications**: Companies use TTS for automated telephony systems, which help in delivering information to callers and guiding them through menu options without human intervention. 3. **Media**: TTS technology is used in news reading apps and websites, where it can read articles to users, making content consumption possible even while multitasking. #### Function templates added to the Console - Text to Speech - Convert text to speech using the Hugging Face inference API. - Speak with ElevenLabs - Convert text to speech using the ElevenLabs API. - Speak with LMNT - Convert text to speech using the LMNT API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/text-to-speech" title="Text to speech" %} Generate music from a text prompt {% /cards_item %} {% /cards %} #### Music generation Music generation with artificial intelligence (AI) involves using machine learning algorithms to create music, often without direct human input for composition. Although many artists are not happy with this trend, the technology is growing in popularity and applications. Here are some examples where AI generated music is used: 1. **Film and Video Games**: AI can generate background music and sound effects for films and video games, adapting dynamically to the visuals or gameplay to enhance the user experience without the need to compose each piece of music manually. 2. **Music Production**: AI assists artists by generating music samples, beats, or entire compositions. Musicians can modify these AI-generated elements or use them to inspire new works. This technology is particularly helpful for artists with limited access to professional studios or expensive equipment. 3. **Copyright-free Music Generation**: AI can generate original compositions that do not require licensing fees for creators who need copyright-free music for videos, podcasts, or other media. #### Function templates added to the Console - Music generation - Generate music from a text prompt using the Hugging Face inference API. #### Tutorial {% cards %} {% cards_item href="/docs/products/ai/tutorials/music-generation" title="Music generation" %} Efficiently convert text to music {% /cards_item %} {% /cards %} ### Getting started with AI and Appwrite As we have covered in this blog, we have added a lot of great templates for you to get started on your AI journey. Including support for AI integrations: - [Pinecone](https://appwrite.io/docs/products/ai/integrations/pinecone) - [OpenAI](https://appwrite.io/docs/products/ai/integrations/openai) - [Hugging Face](https://appwrite.io/docs/products/ai/tutorials/image-classification) - [ElevenLabs](https://appwrite.io/docs/products/ai/integrations/elevenlabs) - [Perplexity](https://appwrite.io/docs/products/ai/integrations/perplexity) - [Replicate](https://appwrite.io/docs/products/ai/integrations/replicate) - [fal.ai](https://appwrite.io/docs/products/ai/integrations/fal-ai) - [LangChain](https://appwrite.io/docs/products/ai/integrations/langchain) - [Together AI](https://appwrite.io/docs/products/ai/integrations/togetherai) ![Image of Appwrite Console](/images/blog/building-with-ai-function-templates/function-templates-overview.png) We've also added more AI Function templates for more use cases than discussed above: - Sync with Pinecone - Sync your Appwrite database with Pinecone's vector database. - Generate with fal.ai - Generate images using fal.ai's API. - Censor with Redact - Censor sensitive information from a provided text string using Redact API by Pangea. - Analyze with PerspectiveAPI - Automate moderation by getting toxicity of messages. - Generate with Replicate - Generate text, audio and images using Replicate's API. Take a look at these two blogs to learn more about using Function templates in your projects: - [Streamline receipt scanning with Appwrite Cloud](https://appwrite.io/blog/post/scan-receipts-with-appwrite-functions) - [Build an intelligent chatbot with ChatGPT and Appwrite Functions](https://appwrite.io/blog/post/function-template-prompt-chatgpt) We will continue to add more AI tutorials, integrations, blogs, videos, and Function templates. You can also contribute by adding your own pull requests to the [repository](https://github.com/appwrite/templates). --- ## Building with Appwrite Sites templates https://appwrite.io/blog/post/building-with-sites-templates Your web application only provides value when it is live and accessible to users. Appwrite Sites simplifies deployment, allowing you to launch modern, production-ready websites directly from your Appwrite console. With built-in templates, you can quickly deploy complete websites that include integrated version control, environment configuration, and secure global delivery, all without complicated setup or infrastructure. In this guide, we will walk through the process of deploying your website using one of Appwrite's pre-built templates. We'll build an ecommerce store for this example, but the process is the same for any other type of site. ### Opening the Sites page After signing into your Appwrite console, look at the sidebar on the left. Under the **Deploy** section, you will find the **Sites** button. Clicking it opens the Sites page. Here, you will see the **Sites** tab selected by default. Click **Create site** to start the deployment process. ![Sites Concole](/images/blog/sites-templates/sites-templates1.png) At this point, Appwrite offers two options: **Clone a template** or **Connect a repository**. For this guide, we will focus on cloning a template, which lets you quickly set up a site based on a pre-built project. ![Clone or connect Git](/images/blog/sites-templates/sites-templates2.png) ### Choosing a template When you select **Clone a template**, Appwrite displays a library of available templates. You can browse through them by scrolling, or you can narrow down the selection using the filters on the left side of the screen. Templates can be sorted by framework. If you are looking for something specific, you can check one or more framework options like Next.js, Svelte, Astro, and others. Templates can also be sorted by use case. The available categories include: - Blog - Documentation - Ecommerce - Events - Portfolio - Starter You can also use the search bar above the list to find a template by name. For this walkthrough, we'll use the **Store template** under the Ecommerce category, so if you don't see it right away, you can search for it. ![Search templates](/images/blog/sites-templates/sites-templates3.png) Clicking the template will open a new page where you can configure your new site. #### Setting up site details Once you have selected the template, you will be prompted to enter basic details for your site. You will need to provide a **Site name** and a **Site ID**. The Site ID becomes part of your site's URL, so it should be lowercase and use hyphens if necessary. ![set details](/images/blog/sites-templates/sites-templates4.png) At this stage, you can choose whether to connect your repository now or later. If you choose **Connect your repository**, you will be able to either create a new repository based on the template or link it to an existing one. If you prefer to skip this step for now, you can choose **Connect later** and connect your version control later using the CLI or through the settings page. If you opt to connect a repository immediately, you will see the following options: - Select whether to create a new repository or connect to an existing one - (Optional) Choose a GitHub organization if you have access to multiple organizations - Name your repository if you are creating a new one - Decide whether to keep the repository private by ticking the checkbox Once your settings are ready, click **Create** to move to the next step. ### Configuring deployment settings After creating your site, you will be taken to the deployment configuration screen. Here, you will select your **Production branch**. This is the branch that Appwrite Sites will use for deployments. You can optionally set a **Root directory** if your production build is inside a specific folder within your repository. There is also a **Silent mode** option. If enabled, Appwrite will not automatically create comments when pushing changes to the connected repository. Next, you will configure your **Environment variables**. Two variables will already be present: - `PUBLIC_APPWRITE_ENDPOINT` - `PUBLIC_APPWRITE_PROJECT_ID` You can edit these values if needed, and you can add more environment variables if your application requires them. Additionally, you can define the domain for your site, using the Appwrite Sites domain. ![Configure](/images/blog/sites-templates/sites-templates5.png) After reviewing your configuration, click **Deploy**. ### Monitoring the deployment Once you initiate the deployment, you will be taken to a progress page where you can monitor the build and deployment in real time. The logs will show you the different stages of the build process. Depending on the size of your template and the dependencies involved, this process can take anywhere from a few seconds to a few minutes. ![Deploy](/images/blog/sites-templates/sites-templates6.png) When deployment is complete, Appwrite will display a success screen showing: - A preview image of your live site - The domain name - The size of the deployment - Global CDN status (should show **Connected**) - DDoS protection status (should show **Connected**) ![Build](/images/blog/sites-templates/sites-templates7.png) You will also see a **Visit site** button that you can click to view your newly deployed website. ### After deployment: Next steps After successfully deploying your site, Appwrite provides a few options to help you continue setting up or expanding your project. You can **Add domain**, connecting an existing custom domain you own or registering a new one through the console. You can **Share site**, allowing you to collaborate with your team by sharing access to your project. You can also **Open on mobile**, previewing your live site directly from a mobile or tablet device to ensure everything looks and functions as expected across different screen sizes. ### Wrapping up Deploying a site using templates on Appwrite Sites provides a straightforward path from project setup to a live deployment. By using a template, you can quickly spin up a professional-quality application without needing to configure hosting infrastructure manually. Connecting a repository, setting environment variables, and defining your site domain are all built directly into the console, allowing you to move from idea to launch with clarity. Once your site is deployed, you can continue to customize it, link it to a custom domain, or expand it as your project grows. If you have any questions, don’t hesitate to reach out to us via [Discord](https://appwrite.io/discord). ### More resources - [Appwrite Sites docs](/docs/products/sites) - [Appwrite compared to Vercel](/blog/post/open-source-vercel-alternative) - [Appwrite Sites product tour](https://youtu.be/VtDe6hDw91k) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Share your resume using Appwrite Functions https://appwrite.io/blog/post/bun-function-resume One of the coolest things about Appwrite Functions is that you can now consume them as REST APIs. This means you can send HTTP requests to any path, using common HTTP methods such as `GET` and `POST` to any path on the function and get a response in JSON or any other text-based formats (such as plain text, HTML, and CSV). This has opened up a lot of potential use-cases, one of which is how you can host and share your online resume through an Appwrite Function when applying for a new job. Therefore, in this blog, we will leverage one of Appwrite’s newest functions runtimes, Bun, to create a function that delivers an HTML-based resume. ### Pre-requisites To create the Appwrite Function, you must go to [Appwrite Cloud](https://cloud.appwrite.io) and create a new project. Once a new project is created, you must visit the **Functions** page. Once you click on the **Create a new function** button, you will discover a number of starter templates, of which you must select **Bun**. Once you have created a starter function with Bun and connected your GitHub account, we are ready to begin developing our resume function. ![Appwrite Cloud Functions page](/images/blog/bun-function-resume/appwrite.png) ### Building the Appwrite Function Once your function’s GitHub repository is ready, clone it to your local device and enter the directory. You will notice a directory structure as follows: ```md . ├── src/ │ └── main.ts ├── README.md ├── bun.locklb └── package.json ``` #### Creating the HTML resume First things first, let’s create an HTML resume. We will create a folder `static` at the root level of our project directory and add a file `resume.html`. We will then add the following HTML to this file: ```html Resume

    John Doe

    Software Engineer


    Work Experience

    Software Engineer, ABC Company (Jan 2019 - Present)

    • Developed APIs for aggregation and analysis of automotive sales data using .NET 5

    Education

    Master of Science in Computer Science, XYZ University (2017 - 2019)

    • CGPA: 9.0

    Bachelor of Engineering in Computer Science, XYZ University (2013 - 2017)

    • CGPA: 8.5

    Skills

    • Programming Languages: JS, TS, C#, Java, Python
    • Frameworks: .NET, Spring Boot
    • Database: MySQL, MongoDB
    • Cloud: AWS, Azure

    Projects

    Project 1

    • Developed a web application for online shopping using Spring Boot

    Project 2

    • Developed a web application for calendar management using ASP.NET

    Contact Me

    • Email: john.doe@test.com
    • Phone: 1234567890
    ``` > Note: You can customize this file in any way you want. The only thing I recommend, however, is to keep the styles within the same file, as the function will only deliver the content of this particular file at a time. #### Preparing our utility function to get the HTML content To simplify the function logic, we create an additional utility function to help easily read the content from our HTML resume and return it as a `string`. This is necessary to deliver the content as a response from our function. For that, we shall enter the `src` folder and create a file `utils.ts` with the following code: ```ts import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export function getStaticFile(fileName: string): string { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } ``` #### Developing the function logic Now that our HTML resume and utility function are ready, we can develop our final function logic. For that, we shall enter the `main.ts` file in the `src` folder and replace it with the following code: ```ts import { getStaticFile } from './utils.js'; export default async ({ req, res }) => { if (req.method === 'GET' && req.path === '/') { return res.text(getStaticFile('resume.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } }; ``` This function will return our HTML resume content with the appropriate content type when we send a `GET` request to the default path of our function domain. At this point, our project directory structure should look as follows: ```md . ├── src/ │ ├── main.ts │ └── utils.ts ├── static/ │ └── resume.html ├── README.md ├── bun.locklb └── package.json ``` #### Testing the function Once you’ve completed all the aforementioned steps, you can push the code to our GitHub repository, at which point Appwrite Cloud will automatically deploy the changes to your function. ![Function deployment](/images/blog/bun-function-resume/deployment.png) You can then go ahead and test your function by opening the function domain in your browser. Here is what an example of this looks like: [apwr.dev/bun-functions-resume-demo](https://apwr.dev/bun-functions-resume-demo) ### Next steps And with that, you have successfully deployed your resume using Appwrite Functions. If you liked this project or want to investigate the full project code, visit our [GitHub repository](https://github.com/adityaoberai/resume-appwrite). Additionally, if you would like to learn more about Appwrite Functions, here are some resources: - [Appwrite Functions docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Bun functions announcements](https://appwrite.io/blog/post/why-you-need-to-try-the-new-bun-runtime): Read the full announcement about our Bun functions runtime. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Lynx by ByteDance vs React Native https://appwrite.io/blog/post/bytedance-lynx-vs-react-native ByteDance, the company behind TikTok, recently released a new cross-platform UI framework called [Lynx](https://lynxjs.org/blog/lynx-unlock-native-for-more.html). It allows developers to build applications for Android, iOS, and the web using familiar web technologies (React and CSS). Lynx is still new, but ByteDance has successfully used it internally across several of its apps, including TikTok. Let's explore what Lynx actually offers developers, and compare it with **React Native**. We'll look at the technical strengths and limitations of both frameworks, and when you might want to use one over the other. ### What is Lynx, really? Lynx is ByteDance's open-source UI framework designed for cross-platform application development across Android, iOS, and the web. Simply put, it's for building native-quality apps without the need for separate codebases or teams. Lynx achieves this by combining: - A custom JavaScript engine called [PrimJS](https://github.com/lynx-family/primjs) - A dedicated rendering engine that can adapt to different platform primitives and graphics interfaces - Dual-thread JavaScript execution (one for responsive UI, one for heavy tasks) - Genuine CSS styling with animations, transitions, selectors, variables, and modern visual effects - Built-in cross-platform support: Android, iOS, and Web This setup means Lynx apps can easily look consistent across all platforms, something React Native approaches differently. ### Lynx's technical strengths Lynx offers several technical advantages over traditional frameworks like React Native: #### 1. Dual-thread architecture Lynx divides JavaScript work between two threads: **Main UI thread:** Handles UI updates instantly. Animations, scrolling, or tapping respond quickly because this thread isn't busy calculating data or logic. **Background thread:** Manages most React logic, data fetching, state management, and heavy computations. This reduces UI lag or stutter, especially in applications with heavy logic or data processing. Apps can feel smoother to the user. The Lynx team reports that surfaces migrating from Web to Lynx often achieve a 2-4x reduction in launch times across the board. #### 2. Instant first-frame rendering (IFR) and main-thread scripting (MTS): The dual-thread architecture enables two key features: **Instant first-frame rendering (IFR):** Prioritizes initial UI rendering to eliminate blank screens during startup, creating the impression of immediate app launch. **Main-thread scripting (MTS):** Allows critical interaction code to run directly on the main thread, ensuring touch responses and animations remain smooth regardless of background processing. #### 3. PrimJS: A lightweight JavaScript engine: PrimJS optimizes JavaScript execution: - Faster startup and reduced memory usage - Efficient garbage collection reduces memory-related slowdowns In simple terms: this means your app loads faster, performs smoothly, and behaves consistently even on older devices. #### 4. CSS for real web-like styling: With Lynx, styling works like regular web development: - Standard CSS syntax (`.button:hover`, media queries) - Native support for Flexbox and Grid layouts - CSS-driven animations and transitions without heavy JavaScript - Modern CSS visual effects like gradients, clipping, and masking This should lower the learning curve for web developers and speed up UI development. #### 5. Framework flexibility: While Lynx ships with [ReactLynx](https://lynxjs.org/react/) ("React on Lynx") as its initial frontend framework, it's designed to be framework-agnostic. The official announcement notes that other frameworks already represent roughly half of Lynx's overall usage. #### 6. Built-in cross-platform support (Android, iOS, Web): Lynx apps run on Android, iOS, and browsers without additional frameworks or modifications. The same UI and logic can appear consistently on all platforms by default. No third-party bridging or significant rework needed. This simplicity can save substantial development and testing effort when targeting multiple platforms. ### Lynx vs React Native technical comparison #### Performance and rendering: | Framework | Approach & implications | |------------------|-----------------------------------------------------------------| | **Lynx** | Dual-thread JS model. Responsive main thread means fewer stutters under heavy load. Good for animation-heavy apps.| | **React Native** | Traditionally single JS thread, which can lag if overloaded. Improved by the recent Fabric renderer but still relies on single-thread execution for most app logic.| In practice, Lynx promises smoother UI performance in intensive apps, whereas React Native works well under typical conditions, though it requires careful coding to avoid slowdowns in complex apps. #### Developer experience: | Framework | Styling and debugging | |------------------|------------------------------------------------------------------| | **Lynx** | CSS styling identical to web development. Familiar Chrome DevTools debugging. New ecosystem with fewer resources.| | **React Native** | JS-based styles (Flexbox, no Grid). Debugging with Flipper or Chrome. Mature ecosystem, extensive libraries, well-established patterns.| If you prioritize a straightforward transition from web development, Lynx's CSS approach is beneficial. React Native has a slight learning curve for web developers but offers more immediate third-party support and extensive debugging resources. #### Platform coverage and UI consistency: | Framework | Platform support & consistency | |------------------|------------------------------------------------------------------| | **Lynx** | Android, iOS, Web built-in. Consistent UI across platforms by default.| | **React Native** | Android, iOS officially. Web via React Native Web (third-party). Native look by default differs across platforms.| Choose Lynx if consistency across web and mobile is critical. Choose React Native if you prefer apps that adhere more closely to native platform styles out of the box. #### Ecosystem and community maturity: | Framework | Community support & libraries | |------------------|-----------------------------------------------------------------| | **Lynx** | Newly open-sourced. Currently limited community and ecosystem. Expect to write more native modules initially.| | **React Native** | Mature, large community, extensive third-party libraries. Strong corporate backing (Meta, Microsoft, Shopify).| React Native offers immediate solutions to common tasks (payments, maps, camera integrations), whereas Lynx will require more hands-on initial development. ### Practical examples: Lynx and React Native Let's compare how Lynx and React Native handle a common development scenario: building animations. **Lynx:** Animations can use standard CSS keyframes or transitions, running smoothly on the main thread. No extra libraries required. ```css .button { transition: transform 0.3s ease; } .button:active { transform: scale(0.9); } ``` **React Native:** Requires JavaScript-driven animations using built-in Animated API or libraries like React Native Reanimated for smooth results. ```jsx import { Animated } from 'react-native'; const scaleAnim = new Animated.Value(1); const pressAnimation = () => { Animated.spring(scaleAnim, { toValue: 0.9, useNativeDriver: true }).start(); }; // Apply scaleAnim to style ``` Both achieve good results, but Lynx is simpler because of CSS, whereas React Native's approach is slightly more complex to the average web developer. ### Final thoughts: Should you try Lynx? If you're open to experimenting, comfortable diving deeper into technology, and perhaps frustrated with React Native's rendering hiccups or styling approach, Lynx is worth exploring. The dual-thread JS runtime, clean CSS-based styling, and instant UI responsiveness are practical advantages. On the other hand, if proven ecosystem maturity, large community support, and immediate availability of third-party integrations matter more to your project, React Native remains the clear choice. Lynx's arrival, in any case, is excellent news. New competition means React Native will likely continue improving, and Lynx offers an intriguing alternative path forward, especially for web-oriented developers looking for better mobile experiences without compromise. It's early days, but Lynx could quickly become an influential player in cross-platform app development. ### Further reading - [Flutter vs React Native: Which framework is best for your app in 2024?](https://appwrite.io/blog/post/flutter-vs-react-native?doFollow=true) - [Setting up route protection in React Native](https://appwrite.io/blog/post/setting-up-route-protection-in-react-native?doFollow=true) - [Building a full-stack app with Svelte and Appwrite](https://appwrite.io/blog/post/build-fullstack-svelte-appwrite?doFollow=true) --- ## CCPA vs GDPR: Understanding the differences and implications https://appwrite.io/blog/post/ccpa-vs-gdpr When you build your application, one of the first things you need to set up is your database and authentication. In other words, you're handling and storing user data. But with this data comes great responsibility. If you’re here, you probably want to understand how to handle this responsibility and manage user data properly. In this blog, we’ll break down some of the major compliance standards like **California Consumer Privacy Act (CCPA)** and the **General Data Protection Regulation (GDPR),** highlight their differences, and guide you on how to stay compliant with these regulations. ### Overview of CCPA vs GDPR #### What is GDPR? The General Data Protection Regulation [GDPR](https://appwrite.io/docs/advanced/security/gdpr) is a comprehensive **data protection** law that came into effect on May 25, 2018, across the European Union (EU). It aims to protect the **personal data** of EU citizens and harmonize privacy laws across Europe. GDPR applies to all businesses, regardless of location, that process the personal data of EU residents. Any developers building applications for EU residents must be GDPR-compliant. #### What is CCPA? The California Consumer Privacy Act [CCPA](https://oag.ca.gov/privacy/ccpa), enacted on January 1, 2020, is a state-level privacy law in the United States that applies to residents of California. The **CCPA for developers** is often considered the most significant U.S. privacy regulation, giving Californians more control over their personal data and how businesses handle it. Developers looking to build **CCPA-compliant apps** must implement features that allow users to exercise their rights under the CCPA. Here is a straightforward overview to compare the two: | **California Consumer Privacy Act (CCPA)** | **General Data Protection Regulation (GDPR)** | | --- | --- | | Provides rights to California residents who are consumers. | Grants rights to individuals residing in the EU. | | Covers personal information that can identify, relate to, describe, or be associated with a consumer or household, with certain exceptions. | Covers all personal data of an individual, excluding household data. Only anonymized information is outside its scope. | | Applies to for-profit companies operating in California that meet specific financial thresholds, as well as their service providers. | Governs data controllers and processors that handle personal data of EU residents. | ### To who do the laws apply? #### Who must comply with GDPR - GDPR applies to businesses operating within the EU, offering goods and services to, or monitoring the behavior of EU residents. - It covers personal data processing, including names, emails, IP addresses, location data, and more. - Applies to companies of all sizes, as long as they process the personal data of EU residents. #### Who must comply with CCPA - CCPA applies to for-profit businesses operating in California or serving California residents and that meet at least one of these thresholds: 1. Gross annual revenues of over $25 million. 2. Buy, receive, sell, or share the personal information of 50,000 or more California consumers, households, or devices. 3. Derive 50% or more of their annual revenues from selling California consumers' personal information. - Businesses that meet these thresholds need to prioritize **CCPA compliance** by ensuring **user data control** and clear **opt-out functionality** for data sales. ### What are the definitions of personal data? #### GDPR Under GDPR, personal data is broadly defined as any information that relates to an identified or identifiable individual. This includes names and email addresses, as well as things like IP addresses, location data, and cookie identifiers. Ensuring secure data management for these data types is a key aspect of compliance for developers. #### CCPA The [CCPA](https://appwrite.io/docs/advanced/security/ccpa) defines personal information similarly but includes additional categories such as browsing history, geolocation data, and inferences drawn from personal data to create a consumer profile. The law emphasizes the sale of personal information, so developers must implement opt-out functionality to meet CPA compliance. ### What are a consumer’s rights? Both GDPR and CCPA provide users with enhanced rights regarding their personal data, but the rights differ slightly. #### Under GDPR, consumers have: - **Right to access**: Individuals can request access to their data and information on how it is processed. - **Right to rectification**: Users can ask for inaccuracies in their personal data to be corrected. - **Right to erasure (Right to be forgotten)**: Individuals can request that their personal data be deleted. - **Right to restrict processing**: Consumers can request to limit the use of their personal data. - **Right to data portability**: Individuals can receive their data in a machine-readable format and transfer it to another service. - **Right to object**: Consumers can object to the processing of their data under certain conditions. - **Right not to be subject to automated decision-making**: Including profiling based on personal data. #### Under CCPA, consumers have: - **Right to access**: Users can request a copy of the personal information collected about them in the previous 12 months. - **Right to deletion**: Individuals can ask that their data be deleted (with some exceptions, such as for completing transactions or legal compliance). - **Right to opt-out of the sale of personal information**: California residents can instruct companies not to sell their personal data to third parties. - **Right to non-discrimination**: Businesses cannot discriminate against consumers for exercising their CCPA rights (e.g., charging them higher prices or offering lower-quality services). - **Right to correct**: Consumers can correct any inaccurate personal information a business has about them. - **Right to limit:** Consumers can limit how businesses use and share their sensitive personal information. ### How are both laws enforced? #### GDPR penalties GDPR imposes severe penalties for non-compliance: - Fines can be up to €20 million or 4% of the company’s global annual revenue, whichever is higher. - Non-compliance can lead to significant financial and reputational damage, making **privacy by design** a priority for developers. #### CCPA penalties CCPA penalties are less severe but still impactful: - Fines of up to $2,500 per violation or $7,500 for intentional violations. - There is also a provision for consumers to sue businesses directly for certain types of data breaches, which can result in additional financial liability for companies. ### Opt-in vs. opt-out **GDPR** requires businesses to obtain explicit consent (opt-in) before processing personal data, particularly sensitive data categories like health or financial information. **CCPA** operates primarily on an opt-out basis, where businesses can process personal information unless the consumer explicitly opts out, particularly regarding the sale of personal information. This means developers need to implement developer-friendly CCPA tools and secure APIs for CCPA compliance to ensure consumers can easily exercise their rights. ### Global implications for developers If you’re building applications or services that target a global audience, you may need to comply with both **CCPA** and **GDPR**. The overlap between the two means you can streamline some compliance efforts, but you’ll also need to account for the specific requirements of each law. For example: - Under **GDPR**, [consent management and data minimization are key](https://appwrite.io/blog/post/7-steps-to-achieve-gdpr-compliance-for-startups), so developers need to build features like granular consent capture and access logs. - Under **CCPA**, the emphasis on **opt-out mechanisms** for data sales means that developers should build clear, user-friendly options for consumers to opt out of data selling practices. Using platforms like Appwrite, which offers compliant backend services with built-in Appwrite privacy features, developers can ensure their applications meet both CCPA and GDPR standards. With Appwrite’s secure backend and [privacy compliance solutions](https://appwrite.io/docs/advanced/security/gdpr), developers can seamlessly integrate privacy controls like opt-out functionality, user data control, and secure data management into their applications. ### Building with privacy in mind Both the **CCPA** and **GDPR** are designed to empower consumers and provide them with more control over their personal data, but they take different approaches to achieve this goal. For developers, the key takeaway is that compliance isn’t just about meeting legal requirements—it’s about fostering trust with users. By designing **privacy-conscious applications**, developers can ensure they meet both **CCPA** and **GDPR** standards, keeping their products legally compliant and user-focused. Understanding the differences between CCPA and GDPR helps developers build apps that protect privacy and are transparent. Using developer-friendly platforms like Appwrite, which offer privacy-focused tools and secure APIs, makes it much easier to create apps that comply with both CCPA and GDPR. Take a look at our [documentation](https://appwrite.io/docs) to learn more about how we approach security and compliance. --- ## Appwrite 1.5: celebrating the contributors https://appwrite.io/blog/post/celebrating-1.5-contributors For those of you who have been following Appwrite, you might have noticed how much we value open source and the community that keeps it going. Appwrite has been a part of that community since 2019. However, the team has been contributing to open source long before Appwrite became an entity. If one thing in life is certain, it is that Appwrite is open-source. ### What does open source mean to Appwrite? What does it actually mean to be open source as a company? It means that our code is open for other developers to use, copy, and fork, but most importantly, to contribute to. It's about being transparent and about building in the open; it is about giving back to those who helped us get here. Most of Appwrite’s engineers were community members before they joined. To us, this embodies an important mindset that instates open source into the core of Appwrite. It is the foundation we build on and will always be at the heart of everything we do. With over 800+ contributors contributing to Appwrite, it's fair enough to say that the Appwrite you see today was built by you, the open-source community. Our latest release, Appwrite 1.5, was no exception to the same ### Introducing Appwrite 1.5 If you haven’t yet heard, Appwrite recently announced the release of version 1.5, which has brought with it numerous features and upgrades, starting with Appwrite Messaging, our new offering to simplify implementing communication and messaging services in your applications. We have also released support for Server-Side Rendering frameworks in our SDK, allowing a substantial amount of web developers in our community to build with a lot more ease. Additionally, we have introduced Two-Factor Authentication in our suite of authentication offerings to improve the security of your app’s users. We have also added support for Enums in our SDKs to improve the developer experience and added new Bun and Dart function runtimes to Appwrite Cloud, among other updates and changes. You can learn more about the release by visiting the [Init website](/init). ### Contributors’ highlight All of our contributors are very special to us, for they make Appwrite what it is. With this release, we also wanted to highlight some contributors whose stories inspired us: - [Bishwajeet Parhi](https://github.com/2002Bishwajeet) Bishwajeet has been an active contributor to the Appwrite community for the last 2+ years and was chosen among the inaugural batch of the Appwrite Heroes. One of the features he contributed to in Appwrite 1.5 was the addition of Enums in the Appwrite SDKs to simplify the experience of working with constants, such as the names of different providers. Here’s how Bishwajeet felt about his experience contributing to Appwrite SDK Generator: > “I recently contributed to adding support for enums in the Appwrite repositories. The contributing guides helped me a lot in setting up Appwrite and the SDK Generator locally. It took me a while to get a hold of the codebase and understand where to add new changes. Jake helped me navigate the codebase a lot and pointed me to what to do and how to test it. Once the PR was up, I had multiple reviews involving Eldad, Jake, and Damodar. Everyone was supportive and helpful, and after a bunch of reviews, the PR was finally merged. I can proudly say that now I can code in multiple languages.” - [Utkarsh Ahuja](https://github.com/UtkarshAhuja2003) Utkarsh is an undergraduate student pursuing a Bachelor's degree in Information Technology. In Appwrite 1.5, he took the opportunity to make his first-ever feature contribution to Appwrite, which happens to be a new OAuth adapter for Zoho. Learn how Utkarsh felt about his experience proposing and contributing his first feature: > “Contributing to Appwrite's codebase was a great learning experience. The team's guidance on pull requests, OAuth Adapter tutorials, and Gitpod integration made development efficient.” - [Ben Humphries](https://github.com/iMacHumphries) Ben Humphries is a member of Appwrite’s GitHub community and an active open-source developer. He helped solve a build timeout issue with Appwrite Functions for slower building runtimes like Swift. Hear how Ben felt about his experience collaborating with his fellow Appwrite community members: > “Contributing to Appwrite has been a rewarding experience. The community’s support and collaboration have been exceptional, making it easy to share ideas and feel like part of the team. The highlight for me was playing a small part in making it easier to use Swift on the server side.” ### Thanks to all the contributors The latest version of Appwrite was built with the help of over 20 developers, truly showing off what building together can achieve and how the community comes together. We are sincerely grateful to our community members for their contributions: - [@benmccann](https://github.com/benmccann), who [helped upgrade prettier](https://github.com/appwrite/console/pull/725) - [@Ananya2001-an](https://github.com/Ananya2001-an), who [added paths-ignore in tests on GitHub Actions](https://github.com/appwrite/console/pull/464) - [@amanpatel23](https://github.com/amanpatel23), who [fixed certain pattern input validation](https://github.com/appwrite/console/pull/702) - [@programmingwithrp](https://github.com/programmingwithrp), who [updated links for the Cron syntax](https://github.com/appwrite/console/pull/639) - [@sourabpramanik](https://github.com/sourabpramanik), who [helped fix table overflow in mobile view](https://github.com/appwrite/console/pull/666) - [@sourabpramanik](https://github.com/sourabpramanik), who[ helped fix the console organization project count limit](https://github.com/appwrite/console/pull/699) - [@AmitGurbani](https://github.com/AmitGurbani), who [fixed clearing of the DateTime attribute](https://github.com/appwrite/console/pull/594) - [@sahalsaad](https://github.com/sahalsaad), who [added nullable option to the DateTime field](https://github.com/appwrite/console/pull/608) - [@FreSauce](https://github.com/FreSauce), who [fixed missing separator for keycodes comparison](https://github.com/appwrite/console/pull/585) - [@kunalvirk](https://github.com/kunalvirk), who [fixed duplicate message on document deletion](https://github.com/appwrite/console/pull/588) - [@KanniShashankh](https://github.com/KanniShashankh), who [updated client-side Cron regex](https://github.com/appwrite/console/pull/553) - [@nick2432](https://github.com/nick2432), who [fixed wrong validation of projectId](https://github.com/appwrite/console/pull/589) - [@FreSauce](https://github.com/FreSauce), who [helped add the option to update hostname for flutter web apps](https://github.com/appwrite/console/pull/541) - [@2002Bishwajeet](https://github.com/2002Bishwajeet), who [fixed enum type errors in Flutter and Dart](https://github.com/appwrite/sdk-generator/pull/775) - [@2002Bishwajeet](https://github.com/2002Bishwajeet), who [helped add support for enums generation from the OpenAPI spec](https://github.com/appwrite/sdk-generator/pull/674) - [@geisterfurz007](https://github.com/geisterfurz007), who [added unit tests for the Deno SDK](https://github.com/appwrite/sdk-generator/pull/735) - [@geisterfurz007](https://github.com/geisterfurz007), who [added unit tests for the PHP SDK](https://github.com/appwrite/sdk-generator/pull/737) - [@ThexXTURBOXx](https://github.com/ThexXTURBOXx), who [helped upgrade flutter_web_auth_2](https://github.com/appwrite/sdk-generator/pull/723) - [@fanksin](https://github.com/fanksin), who [added Turkish translations](https://github.com/appwrite/appwrite/pull/7276) - [@iMacHumphries](https://github.com/iMacHumphries), who [helped solve a build timeout issue with Appwrite Functions](https://github.com/appwrite/appwrite/pull/7350) - [@Souptik2001](https://github.com/Souptik2001), who [helped solve an issue with missing user activity logs](https://github.com/appwrite/appwrite/pull/7559) - [@GuptaPratik02](https://github.com/GuptaPratik02), who [updated multiple SDK Getting Started examples](https://github.com/appwrite/appwrite/pull/6826) Every single contributor and every single contribution made the product better for the next developer. That is what makes it all the more exciting, knowing that your PR actually has value for the next person. ### How you can contribute Appwrite is everything that it is because of the support of our community. Whether we look at our Discord community, GitHub contributors, or developers interacting with us on social media, everyone adds value and makes a difference. If you would like to increase your participation and play a more substantial role in the development of Appwrite, here are some ways you can make a difference: - Share your suggestions in the [issues](https://github.com/appwrite/appwrite/issues) listed on our GitHub repo. - Make bug fixes and feature contributions by creating [pull requests](https://github.com/appwrite/appwrite/pulls). - Raise feedback and new ideas about Appwrite in our [GitHub Discussions](https://github.com/appwrite/appwrite/discussions). - Participate with other Appwrite developers in our [Discord community](https://appwrite.io/discord). - Share your Appwrite project with our community on the [Built With Appwrite](https://builtwith.appwrite.io/) platform. - Add any Appwrite-related educational content on our [awesome-appwrite](https://github.com/appwrite/awesome-appwrite) repo. Together, let’s continue to build Appwrite through the power of open source, allowing every developer to have the capabilities of hundreds of developers through one platform. --- ## How to change regions in Appwrite Cloud using migrations https://appwrite.io/blog/post/change-regions-with-migrations With the launch of the **Appwrite Network**, Appwrite Cloud now gives you the ability to choose where your project is hosted. This means that you can bring your backend closer to your users, reducing latency, improving responsiveness, and aligning with local data residency laws if needed. But what if you've already built and launched your app in one region, and now you need to move it to another? Currently, there's no simple toggle to switch regions, but Appwrite provides a built-in migration tool that allows you to create a new project in your target region and move your data and services to it. In this guide, we'll walk through the process of migrating your Appwrite Cloud project to a new region, along with the steps to take after the migration is complete. #### What you need before starting Before we get started, make sure a few things are in place: - You have an Appwrite [Cloud account](https://cloud.appwrite.io/). - You have a project in Appwrite Cloud that's currently hosted in a region you want to move from. This is your **source project**. - You have permission to create new projects, and full access to the source project's settings. Once you've got all that, we can get started. #### Step 1: Create your destination project To get started, head over to the Appwrite Cloud console and create a new project. This is the one that will live in your new region. Give it a name that distinguishes it from the original, especially if you want to keep both projects running side-by-side for a while. When prompted to choose a region, pick the one you want to migrate into. This is where your project will be hosted going forward. Once you've finished setting it up, you'll have a clean project ready to receive everything from your current environment. #### Step 2: Set up the destination project for import Inside your new project, go to **Settings**, and look for the **Migrations** tab. ![Migrations tab](/images/blog/change-regions-with-migrations/migrations-tab.png) There you'll find the option to **Import project data**. Click the button to open the migration configuration screen. This is where you'll enter the credentials from your source project, so Appwrite can pull everything over. But first, we need to get those credentials. #### Step 3: Collect your source project credentials Back in the Appwrite Cloud console, switch over to your source project. You'll need three things from here: 1. A dedicated **API key** for migration. 2. The **Project ID** of the source project. 3. The **API endpoint** of the source project. Here's how to get each one: ##### Create an API key From your source project, go to **Settings → API Keys** and create a new key. Give it a name that'll make it easy to identify later. ![Create API key](/images/blog/change-regions-with-migrations/create-api-key.png) Set an expiration date. Since this key is likely to have access to a lot of data, you don't want it hanging around forever. When assigning scopes, either select them all, or make sure to include at least: `users.read`, `databases.read`, `storage.read`, `functions.read`, and anything else you know your app uses. Once the key is created, copy the secret for migration. ##### Copy the Project ID and API endpoint Still in the source project, head to **Settings**, and in the **Overview** tab, **API Credentials**. Here, you'll find: - The **Project ID**. - The **API Endpoint** With this, you have your API key, project ID, and API endpoint. These are the three things you'll need to migrate your project. #### Step 4: Start the migration Go back to your destination project, and in the **Settings** page, go to the **Migrations** tab. In **Migrations**, select **Import data** in the **Import project data** section. Choose **Appwrite** as the source platform. ![Migrations screen](/images/blog/change-regions-with-migrations/migrations-screen.png) Then fill in the details from the source project: - **Endpoint** → Paste the API endpoint URL. - **Project ID** → Paste the source project's ID. - **API Key** → Paste the secret key you created earlier. Click **Next**. Appwrite will now reach into the source project, check what's available, and return a list of resources it found: users, databases, collections, documents, storage buckets, files, functions, and so on. You'll be prompted to choose what you want to bring over. If you're planning to fully migrate the project, click **Select all**. You can also check top-level resources (like "Users", "Functions", "Databases") and then decide whether to include things like documents, teams, and environment variables. When ready, click **Create**. Now, Appwrite begins the actual data transfer. #### Step 5: Let the migration run and wait for it to complete You'll see a progress indicator as the migration runs. How long this takes depends on your data. If your project is lightweight, it might finish in under a minute. But if you have thousands of documents or large media files, it might take longer. When the migration is complete, you'll see an indication in the console. ![Migration complete](/images/blog/change-regions-with-migrations/migrations-complete.png) #### After migration: what to verify Once the migration is done, you can inspect the destination project to ensure everything transferred correctly. Here are some things to check (if applicable): - **Auth** → Confirm that users and teams are present. - **Databases** → Can you see your collections and documents? - **Storage** → Look inside your buckets and confirm that files are present. - **Functions** → Are your functions listed, and do they include deployments and any environment variables you selected? Spend a few minutes to confirm that the migration worked as expected. #### What Appwrite doesn't migrate and what to do next The migration tool brings over the majority of your project's data and config, but there are a few things you'll need to handle manually: ##### Update your app code This is an important step, since your app is still communicating with the *old* project. Update your codebase to point to the **new Project ID** and **new API Endpoint** from the destination region. That means updating SDK initialization code across your frontend, mobile app, backend scripts, or wherever your app communicates with Appwrite. Without this change, your app will continue talking to the source project. ##### Recreate any needed API keys The migration-specific API key doesn't stick around, and other existing keys are not carried over. So if your app uses long-lived API keys for backend access or integrations, you'll need to recreate them in the destination project. Then update your environments accordingly. ##### Re-register your platforms Your registered platforms (iOS, Android, Web, Flutter, etc.) also aren't migrated automatically. You'll need to add them again from **Overview → Integrations → Platforms** in the destination project. Pay close attention to this, especially if you use OAuth, custom domains, or push notifications. ##### Check project-level settings Some other configurations might also need to be manually re-applied, like: - SMTP settings for emails - Custom domains - Function execution permissions - Service limits (if you had them changed) It's worth going through the Settings page of both projects side-by-side to spot anything missing. #### Some final thoughts before you switch You don't need to shut down your source project immediately. You might want to run both in parallel for a short time while you test the migrated version. When you're ready to switch, make sure your app has been fully updated to use the new configuration, and that everything behaves as expected. Schedule a short maintenance window if needed, especially if you're updating backend services or deployment pipelines that rely on Appwrite. And as always, test thoroughly. Auth flows, file uploads, database reads/writes, scheduled functions need to be checked. #### Wrapping up Migrating a project between regions in Appwrite Cloud is quite straightforward once you know the steps. The import tool handles the heavy lifting, and with the right preparation (API key, project ID, endpoint) it can move most of your app's backend in minutes. Once the migration is complete, you'll have your Appwrite backend running where you want it, aligned with your new requirements, and ready to scale in the right place. If you run into issues, the Appwrite docs and Discord community are solid places to troubleshoot and get support. --- ## Build an offline AI chatbot with WebLLM and WebGPU https://appwrite.io/blog/post/chatbot-with-webllm-and-webgpu When you hear "LLM," you probably think of APIs, tokens, and cloud infrastructure. But what if we could remove the server completely? That your browser could download a model, run it on your device, and answer questions in real time, all using just JavaScript and GPU acceleration? Local LLMs running inside the browser were nearly impossible just a year ago. But thanks to new technologies like WebLLM and WebGPU, you can now load a full language model into memory, run it on your device, and have a real-time conversation, all without a server. In this guide, we'll build a local chatbot that runs entirely in the browser. No backend. No API keys. By the end, you should have a good understanding of [WebLLM](https://webllm.mlc.ai/) and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API), and will have built an app that looks and functions like this: ![WebLLM and WebGPU chat app demo](/images/blog/chatbot-with-webllm-and-webgpu/webllm-webgpu-chat-app-demo.gif) You can also try out the app using this [live URL](https://ebenezerdon.github.io/simple-webllm-chat/). To build this, we need to first understand two important pieces: **WebLLM** and **WebGPU**. #### What is WebLLM? WebLLM is an open-source project from the team at MLC (Machine Learning Compiler). It lets you run language models directly in your browser tab using GPU acceleration. The models are compiled into formats that your browser can understand and execute, no need to send data to a server. Why is this important? - It keeps user data private - It reduces latency - It works offline after the model download - It removes the need for API costs or rate limits Under the hood, WebLLM handles model loading, tokenization, execution, and streaming responses. It gives you a simple interface to load and chat with a model like LLaMA or Phi. But WebLLM can't do it alone. It needs hardware access, and that's where WebGPU comes in. #### What is WebGPU? WebGPU is a new browser API that gives JavaScript access to the system's GPU, not just for drawing graphics, but for running large-scale parallel computations like matrix operations and tensor math. In our case, WebGPU lets the browser perform the heavy math required to generate text from an LLM. Here's what WebGPU does for us: - **Performance**: Runs faster than JavaScript or even WebAssembly for these workloads - **GPU-first**: Designed from the ground up for compute, not just rendering - **Accessibility**: Available across different browsers, though support varies by platform. As of June 2025: - **Chrome/Edge**: Fully supported on Windows, Mac, and ChromeOS since version 113. On Linux, it requires enabling the `chrome://flags/#enable-unsafe-webgpu` flag - **Firefox**: Available in Nightly builds by default, with stable release tentatively planned for Firefox 141 - **Safari**: Available in Safari Technology Preview, with support in iOS 18 and visionOS 2 betas via Feature Flags - **Android**: Chrome 121+ supports WebGPU on Android For production applications, you should include proper WebGPU feature detection and provide fallbacks for unsupported browsers. Together, WebLLM and WebGPU allow us to do something powerful: load a quantized language model directly in the browser and have real-time chat without any backend server. With this understanding of WebLLM and WebGPU, we can now start building! #### Setting up the HTML for our AI chat app Before we write any JavaScript, we need a user interface. This will be the visible part of our app, the dropdown for selecting models, the chat area, and the input box. Here's the plan: - We'll create a container for everything - Add a select box for choosing the model - Include a progress bar for when the model is loading - Display the chat history - Create a form with a text input and a button to submit prompts To begin, create an `index.html` file and paste the following code inside it: ```html Browser LLM Chat Demo

    Chat with LLM (In your browser)

    Select a model and click "Load Model" to begin
    ``` In the HTML file, we've created a chat interface with controls for model selection and loading. The interface includes a chat output area, progress indicators, and an input form, which we need to let users interact with the AI model. ##### Model selection Notice that in the `div` with class `controls`, we have a `select` element for model selection and a `button` for loading the model. Here are the specifications for each model: | Model | Parameters | Q4 file size | VRAM needed | | ------------ | ------------ | ------------ | ----------- | | SmolLM2-360M | 360 million | ~270 MB | ~380 MB | | Phi-3.5-mini | 3.8 billion | ~2.4 GB | ~3.7 GB | | Llama-3.1-8B | 8.03 billion | ~4.9 GB | ~5 GB | When you're deciding which of these models to use in a browser environment with WebLLM, think first about what kind of work you want it to handle. - **SmolLM2-360M** is the smallest by a wide margin, which means it loads quickly and puts the least strain on your device. If you're writing short notes, rewriting text, or making quick coding helpers that run in a browser, this might be all you need. - **Phi-3.5-mini** brings more parameters and more capacity for reasoning, even though it still runs entirely in your browser. It's good for handling multi-step explanations, short document summarisation, or answering questions about moderately long prompts. If you're looking for a balance between size and capability, Phi-3.5-mini has a comfortable middle ground. - **Llama-3.1-8B** is the largest of the three and carries more of the general knowledge and pattern recognition that bigger models can offer. It's more reliable if you're dealing with open-ended dialogue, creative writing, or complex coding tasks. But you'll need more memory. Each of these models trades off size, memory use, and output quality in different ways. So choosing the right one depends on what your hardware can handle and what kind of prompts you plan to work with. All can run directly in modern browsers with WebGPU support. There are more models available at the [WebLLM repository](https://github.com/mlc-ai/web-llm), ranging from smaller models for mobile devices to larger ones for more capable systems. With the HTML in place, the next thing we'll do is work on the JavaScript implementation, then add some CSS to make it look nice. We're saving the CSS for the last step so we can focus on the core features. #### Using WebLLM and WebGPU to build the chatbot Our JavaScript will do four main things: 1. Load the selected model into memory 2. Track the loading progress and show feedback 3. Enable the chat form once the model is ready 4. Stream the response back as the assistant types it out We'll build this piece by piece. ##### Step 1: Import WebLLM We need to bring in WebLLM so we can access the model engine. ```js import { CreateMLCEngine } from '' ``` This gives us the function that will initialize the model. ##### Step 2: Get references to the DOM elements Let's wire up the interface. We'll grab the elements we need so we can update them later. ```js const output = document.getElementById('output') const form = document.getElementById('chat-form') const promptInput = document.getElementById('prompt') const submitButton = document.querySelector('button[type="submit"]') const modelSelect = document.getElementById('model-select') const loadModelButton = document.getElementById('load-model') const progressContainer = document.getElementById('progress-container') const progressFill = document.getElementById('progress-fill') const progressText = document.getElementById('progress-text') ``` We'll use these to display messages, show progress, and control the form state. ##### Step 3: Track the model engine We need a variable to hold the model once it's loaded. ```js let engine = null ``` We'll update this later when the user loads a model. ##### Step 4: Show progress When downloading and initializing the model, we want to keep the user informed. ```js const updateProgress = (percent) => { progressContainer.style.display = 'block' progressFill.style.width = `${percent}%` progressText.textContent = `${percent}%` } ``` This sets the visual width of the progress bar and updates the percentage text. ##### Step 5: Load the model Here's the key function. We call this when the user clicks the "Load Model" button. ```js const loadModel = async (modelId) => { try { output.textContent = 'Initializing...' promptInput.disabled = true submitButton.disabled = true loadModelButton.disabled = true progressContainer.style.display = 'none' ``` We first disable the interface to prevent interference while loading. Next, we make sure the browser supports WebGPU: ```js if (!navigator.gpu) { throw new Error('WebGPU not supported in this browser...') } ``` This check is crucial because WebGPU availability varies significantly across browsers and platforms. The code will gracefully fail if WebGPU isn't available, allowing you to show appropriate fallback content to users. Then we download and initialize the model: ```js engine = await CreateMLCEngine(modelId, { initProgressCallback: (progress) => { let percent = typeof progress === 'number' ? Math.floor(progress * 100) : Math.floor(progress.progress * 100) updateProgress(percent) output.textContent = `Loading model... ${percent}%` }, useIndexedDBCache: true, }) ``` Once complete: ```js output.textContent = 'Model ready! Ask me something!' promptInput.disabled = false submitButton.disabled = false loadModelButton.disabled = false } catch (error) { loadModelButton.disabled = false output.innerHTML += `
    Failed to load model: ${error.message}
    ` } } ``` Now the model is ready to use. ##### Step 6: Handle chat form submission When the user types a question and presses enter, this block sends it to the model: ```js form.addEventListener('submit', async (e) => { e.preventDefault() if (!engine) { output.innerHTML += `
    No model loaded...
    ` return } const prompt = promptInput.value.trim() if (!prompt) return output.textContent = `You: ${prompt}\\n\\nAssistant: ` promptInput.value = '' promptInput.disabled = true submitButton.disabled = true ``` Then we stream the assistant's response: ```js try { const stream = await engine.chat.completions.create({ messages: [{ role: 'user', content: prompt }], stream: true, }) for await (const chunk of stream) { const token = chunk.choices[0].delta.content || '' output.textContent += token output.scrollTop = output.scrollHeight } promptInput.disabled = false submitButton.disabled = false promptInput.focus() } catch (error) { output.innerHTML += `
    Error during chat: ${error.message}
    ` promptInput.disabled = false submitButton.disabled = false } }) ``` ##### Step 7: Trigger the model load on button click Finally, we hook up the "Load Model" button: ```js loadModelButton.addEventListener('click', async () => { await loadModel(modelSelect.value) }) ``` #### Adding CSS styling Now that we have the JavaScript in place, let's add some CSS to give the app a clean look. In the ` ``` With this, we get 2 groups of 2 lines, each with a randomized height and animation delay, creating a subtle, dynamic backdrop for our lockup. ### Expanding the theme: feature illustrations Of course, the Init lockup was not the only component of design work needed for Init. This is a week-long event jam packed with daily announcements. To make every day of Init feel special, we wanted to create a series of visual assets that expanded on the event’s theme while serving as great visual support for each announcement. We worked together with Appwrite engineers to understand how each new feature launched could be illustrated using the dynamic line art concept we developed. This resulted in a set of key illustrations we used across different channels such as campaign announcements on social media, website illustrations, blog covers, and animations. ![Day 0](/images/blog/designing-init/day0.png) ![Day 1](/images/blog/designing-init/day1.png) ![Day 2](/images/blog/designing-init/day2.png) ![Day 3](/images/blog/designing-init/day3.png) ![Day 4](/images/blog/designing-init/day4.png) This helped expand the concept we began developing with the Init lockup and made the whole event’s identity feel more expressive, diverse, and complete. ### Conclusion As a design team, our goal is to do work that sets a standard of excellence for not only ourselves, but also the industry as a whole. Here, we set out to build something that was visually appealing, pushed the quality from 1.0, was performant, and fit the themes of our event. Through a lot of hard work, learning, and iteration, we feel as though we managed to do all four. We’re constantly evolving, and will build on what we learned here to make our next events, and every other surface even better. Keep an eye out, and thanks for joining Init! - More about [Init](https://appwrite.io/init) - [How to create tickets for your digital event](https://appwrite.io/blog/post/how-to-build-your-digital-event-tickets) - Join us on [Discord](https://appwrite.io/discord) --- ## Designing the new Appwrite website https://appwrite.io/blog/post/designing-the-new-appwrite-website It is no surprise that the channel that gets the most traffic is our website. Not only that, it is where many of our developers start their journey of learning more about Appwrite, so we knew that it had to be one of the strongest case studies for our rebrand. It had to symbolize a big change in our journey, not only from a visual standpoint, but also in the presentation of its content. We started this process by doing a thorough analysis of our site’s structure, figuring out what was working, what could be better, but also keeping in mind that this was an opportunity to improve and add to the overall presentation of our content. There was still some much-needed information still missing from our website, and we knew this was the perfect time to start brainstorming, writing, editing, and designing the pages in which that content would live. ![Website wireframes](/images/blog/designing-the-new-appwrite-website/wireframe.png) Once the overall content and site map was defined, we built wireframes to further test the navigation across pages, and made adjustments to the content being shown according to our tests. It is important to keep in mind that this process was all done before we even started to think about how our rebrand would be applied to the site, so as to not distract us from what was most important at the moment: defining the content. ### Incorporating our brand identity The website played a major role in defining some aspects of our brand identity, as we used it as one of the main channels for testing out visual concepts, typefaces, colors, etc. We had to make sure that our new identity’s foundations worked inside the web environment before anywhere else. We were able to make many major decisions based on how those would fare on the web. If it did not work at all inside this context, it would most likely not be too valuable of an idea to implement. This is not by any means a golden rule for best branding practices, but it was valuable to us because we were able to maintain visual cohesiveness across our channels even in the most experimental phases of the project. One of the concepts that worked well in the website was the use of glass-like elements. It sparked many ideas on how we could apply textures, lighting, blurring in the UI in ways that were both interesting and purposeful. One of the team’s favorite examples of applications of this idea were the illustrated icons we designed for the suite of products we offer at Appwrite, each one combining our signature pink color with a glass-like layer that would make up the entire composition. ![Appwrite's pricing plans](/images/blog/designing-the-new-appwrite-website/plans.png) ### Working with animations We found that the rebrand was a great opportunity to breathe some life into our website. With this in mind, we hand-crafted unique animations that showcase our products’s functionalities while creating a pleasant page navigation experience for users. Working together with our engineering team, we were able to add a variety of details and happy moments while illustrating the power of Appwrite in our many use cases. These animations were coded from scratch, using powerful tools such as [Svelte’s transitions](https://svelte.dev/docs/svelte-transition) and [native web animation API libraries](https://motion.dev/), bringing interactivity and good performance together in harmony. {% video src="/images/blog/designing-the-new-appwrite-website/animations.mp4" autoplay=true /%} Designing the new Appwrite website was a very pleasurable and fun experience. It gave us the opportunity to improve areas that needed attention, to implement new ideas, and to foster experimentation every step of the way. Moving forward, we want the both our website and documentation to be living environments that will keep evolving as we expand our offerings and improve our products. --- ## 12 developer tools to supercharge your Appwrite project https://appwrite.io/blog/post/developer-tools-appwrite Any developer-focused product is only as good as the ecosystem of developer tools surrounding it. Fortunately, over the years, we have seen several outstanding tools developed by our community, as well as a few we have developed in-house to make development with Appwrite far more productive. ### Our favourite developer tools for Appwrite #### ngx-appwrite ngx-appwrite is a library designed to integrate Appwrite services seamlessly into Angular 16+ applications. It acts as a wrapper around the Appwrite Web SDK, simplifying the use of Appwrite's features within Angular projects. The library enhances developer productivity by providing RxJS streams, which facilitate reactive programming patterns commonly used in Angular applications. This integration allows developers to easily implement Appwrite's backend services, such as database, authentication, and storage while leveraging Angular's powerful framework for building dynamic web applications. Take a look at their [NPM package](https://www.npmjs.com/package/ngx-appwrite) and [GitHub repo](https://github.com/blackfan23/ngx-appwrite). #### Auth UI Auth UI offers a customizable login flow for applications, featuring a user interface that can be adapted to different authentication methods in Appwrite. The customizable UI allows developers to implement login mechanisms that align with their specific application requirements, providing flexibility in user management and security configurations. This project demonstrates the versatility of Appwrite in supporting diverse authentication needs across various projects. Take a look at their [live application](https://www.authui.site/) and [Built With Appwrite page](https://builtwith.appwrite.io/projects/6467cedd4502d0e29205/). #### adapter-appwrite adapter-appwrite is a tool designed for SvelteKit that enables developers to deploy SvelteKit applications as Appwrite functions. By using adapter-appwrite, developers can leverage SvelteKit's modern web development features, such as server-side rendering and static site generation, while utilizing Appwrite Cloud's infrastructure for their backend functionalities. Take a look at their [NPM package](https://www.npmjs.com/package/adapter-appwrite) and [GitHub repo](https://github.com/lukehagar/sveltekit-adapters). #### Appread Appread is a comprehensive guide focused on self-hosting Appwrite instances. The guide covers various aspects of setting up and managing Appwrite, including installation procedures, scaling techniques, security measures, and ensuring high availability. It provides detailed instructions for deploying Appwrite on both small servers and larger Docker Swarm setups. Appread serves as a valuable resource for developers and system administrators who want to leverage Appwrite's features while maintaining control over their own infrastructure, enabling them to optimize performance and reliability. Take a look at their [online guide](https://book.appread.io/). #### AppExpress AppExpress is a lightweight framework inspired by Express.js, designed specifically for use with Appwrite Functions. It simplifies the process of creating server-like functionalities within Appwrite by offering an intuitive API for routing and middleware integration. Developers can use AppExpress to build modular and maintainable code for handling HTTP requests, developing middleware, handling caching, and other backend operations. This framework makes it easier to develop and deploy serverless applications with Appwrite, leveraging familiar concepts from Express.js while optimizing for the Appwrite ecosystem. Take a look at their [GitHub repo](https://github.com/ItzNotABug/appexpress). #### Appwrite Utils Appwrite Utils is a collection of utilities and a command-line interface (CLI) aimed at simplifying the management of Appwrite projects. It provides tools for handling data migrations, schema updates, and data conversion tasks. The utilities help streamline the process of updating and maintaining the structure of databases and other backend components in Appwrite. This toolset is particularly useful for developers who need to make systematic changes to their Appwrite projects, ensuring smooth transitions and consistency across different environments and versions. Take a look at their [NPM package](https://www.npmjs.com/package/appwrite-utils) and [GitHub repo](https://github.com/ZachHandley/AppwriteUtils). #### AppwriteX AppwriteX is a Dart package that noninvasively extends the Appwrite Dart SDK with additional features. It enhances the standard SDK's capabilities, providing more functionalities and tools for developers working with Appwrite in Dart and Flutter environments. Take a look at their [Pub.dev package](https://pub.dev/packages/appwritex) and [GitHub repo](https://github.com/insightoptech/appwritex). #### AppwriteMigrator AppwriteMigrator is a .NET CLI tool that enables a code-first approach to managing your database schema with Appwrite. This tool simplifies the process of syncing and migrating your database schema between local and remote environments, ensuring consistency across development, staging, and production setups. By facilitating schema management through code, AppwriteMigrator helps maintain the integrity and consistency of database structures across different deployment stages, making it an invaluable tool for developers working with Appwrite in .NET environments. Take a look at their [NuGet package](https://www.nuget.org/packages/PinguApps.AppwriteMigrator) and [GitHub repo](https://github.com/PinguApps/AppwriteMigrator). #### fetch-appwrite-types fetch-appwrite-types is a tool designed to generate TypeScript types from Appwrite database collections. This utility simplifies the integration of Appwrite with TypeScript projects by creating type definitions that reflect the database schema. It enhances type safety and developer experience by providing accurate type information for database operations, reducing runtime errors, and improving code quality. Take a look at their [NPM package](https://www.npmjs.com/package/fetch-appwrite-types) and [GitHub repo](https://github.com/YsarocK/fetch-appwrite-types). #### Appwrite SDK Generator The Appwrite SDK Generator is a PHP library that automates the creation of SDKs for various programming languages, such as JavaScript/TypeScript, Dart, Python, Ruby, Kotlin, Swift, and more. It uses predefined language settings and templates to generate codebases based on Appwrite’s OpenAPI specification. This tool simplifies the process of creating and maintaining Appwrite’s SDKs, ensuring they are up-to-date with the latest API changes. Take a look at our [GitHub repo](https://github.com/appwrite/sdk-generator). #### Appwrite CLI The Appwrite Command Line Interface (CLI) is a tool for managing Appwrite servers and projects from the command line. It supports various tasks such as project setup, user management, database interactions, and deployment processes. The CLI facilitates automation and streamlines development workflows, making it easier for developers to interact with Appwrite services. Take a look at our [documentation](/docs/tooling/command-line/installation). #### Appwrite Playgrounds The Appwrite Playgrounds are a collection of simple example projects that help developers get started with Appwrite in various programming languages and frameworks. These playgrounds include setups for web, Python, React Native, Flutter, Android, PHP, Node.js, Dart, .NET, Kotlin, Swift, Ruby, Apple UIKit, Apple SwiftUI, and Deno. Each playground provides practical examples and boilerplate code to demonstrate how to integrate and use Appwrite's services in different environments, making it easier for developers to learn and implement Appwrite. Take a look at our [GitHub repos](https://github.com/orgs/appwrite/repositories?q=playground&type=all). ### Want to discover more such tools and projects? To explore more tools and projects that can help you build your applications quicker and better with Appwrite, visit the following links: - [Built with Appwrite](https://builtwith.appwrite.io/): Discover a wide range of projects and applications that utilize Appwrite's features. - [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite): A curated list of Appwrite-related resources, including libraries, tools, and projects. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Document vs relational databases: Finding the right fit for your AI project https://appwrite.io/blog/post/document-vs-relational-databases-vibecoding If you're building an application in 2026, the choice of database is one of the most critical technical decisions you'll make. The debate typically centers on two primary options: **SQL (Relational)** and **NoSQL (Document)**. Developers often have strong preferences, favoring either the strict structure of SQL or the flexibility of Document databases. However, neither approach is always superior. Each works best in specific scenarios, and understanding their different strengths is how you choose the right tool for your project. ### Relational databases (SQL) **SQL databases** (such as PostgreSQL, MySQL, and MariaDB) organize data into **tables** with defined rows and columns. This structured approach has been the industry standard for decades for good reason. #### Why choose SQL? * **Structure and consistency**: SQL enforces a predefined schema. You must define data types—integers, strings, dates—before storing records. This strictness ensures data integrity and predictability across your application. * **Complex relationships**: As the name implies, relational databases excel at managing interconnected data. Using **JOIN** operations, you can efficiently query and aggregate data across multiple tables in a single command. * **Transactional integrity**: SQL databases follow **ACID** properties (Atomicity, Consistency, Isolation, Durability). This ensures that transactions are processed reliably, ensuring data accuracy even during system failures—a critical requirement for financial and enterprise systems. #### Best use cases * **Financial systems**: Applications where data accuracy and transactional integrity are critical. * **Analytical reporting**: Projects requiring complex queries and calculations across highly connected data. * **Stable data models**: Applications where the data structure is well-understood and unlikely to have frequent, major changes. ### Document databases (NoSQL) **Document databases** (such as Appwrite Databases and MongoDB) store data as **documents**, typically in **JSON** format. This model focuses on flexibility and developer experience. #### Why choose document databases? * **Schema flexibility**: Unlike SQL, document databases allow you to update your data model without downtime. You can add new fields to a document without changing the entire collection, allowing rapid iteration. * **Developer experience**: Data is stored in the same format used in your application code (JSON objects). This removes the need for complex object-relational mapping (ORM) layers, reducing development complexity. * **Horizontal scalability**: Many document databases are designed to scale out across multiple servers, making them perfect for high-volume applications that need to handle massive amounts of data and traffic. #### Best use cases * **Content management**: Platforms like blogs, catalogs, and user profiles where data structures vary between records. * **Rapid prototyping**: Projects where the data model is evolving alongside the application features. * **High-scale applications**: Systems that require low-latency reads and writes at massive scale. ### The AI factor The rise of AI-assisted coding, or "vibecoding," has changed how many developers approach database selection. Document databases often align more naturally with AI-driven workflows. **Native JSON support**: Large Language Models (LLMs) are trained to understand and generate JSON. When an AI agent generates a data object, it can often be saved directly to a document database without the need for translation or mapping layers. **Fluid iteration**: AI development involves rapid experimentation. If an agent suggests adding a new attribute to a user profile, a document database accepts this change immediately. In a SQL environment, this would typically require a schema migration, introducing complexity to the creative process. So while modern SQL databases like PostgreSQL might offer strong JSON support, the document model remains the path of least resistance for many AI-focused workflows. ### At a glance | Feature | Relational (SQL) | Document (NoSQL) | | :--- | :--- | :--- | | **Data format** | Tables with rows and columns | JSON documents | | **Structure** | Rigid, requires migrations | Flexible, dynamic | | **Relationships** | Excellent (JOINs) | Good (often denormalized) | | **Reliability** | Strong consistency (ACID) | Configurable consistency | | **Scaling** | Typically vertical | Typically horizontal | | **Best for** | Structure, complex queries, integrity | Flexibility, speed, scale | ### Making the choice The right database depends entirely on your specific requirements. * Choose **SQL** if your data is highly relational, your structure is stable, and data integrity is your top priority. * Choose **Document** if you need flexibility, horizontal scaling, nested/hierarchical data, or are primarily building with AI agents. If you're looking for a solution that combines the flexibility of the document model with the ease of a backend-as-a-service, check out [Appwrite Databases](/docs/databases). It's designed to support fast, iterative development for modern applications. ### More resources * [SQL vs NoSQL: Choosing the right database for your project](/blog/post/sql-vs-nosql) * [Integrate SQL, NoSQL, Vector, Graph, or any database into your Appwrite project](/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project) * [Build a chat app with Appwrite and Gemini](/blog/post/build-a-chat-app-with-appwrite-and-gemini) --- ## Don’t blame the readers, write the documentation they need https://appwrite.io/blog/post/dont-blame-the-readers-write-the-docs-they-need You’ve seen this exact conversation in support threads if you're active in any developer community. "Have you read the docs?" "No" "Did reading the docs help?" "Yes" It’s convenient to just blame the developer for *not reading carefully*, but it’s also an opportunity to improve your developer experience. Over the past year, we’ve noticed conversations like this in Appwrite communities. We took it as an opportunity to grow. "Why don’t they read the docs?" We wanted to find out. ### Identifying the issues The Appwrite team has always valued documentation as a core part of the product. After all, most adoption begins at the documentation. The old documentation was minimalistic and to the point and reflects our product philosophy. You can skim through it in one sitting and learn how to start building with Appwrite. The old Appwrite docs are good but designed for a different audience and a different product. As Appwrite’s capabilities grow, so does the amount of documentation. More features simply need more words to explain, and the single-side navigation becomes more bloated with more information than we ever intended. Appwrite also gained popularity. While Appwrite’s early adopters are experienced open-source tinkerers who are comfortable operating with less guidance and enjoy the old documentation’s brevity, the growing number of learners and students need more help. You can expect experienced developers to understand the purpose of a tool intuitively, but learners need to know how and when to use Appwrite. Put simply, Appwrite’s documentation has to adapt to the changing needs of Appwrite’s growing capabilities and evolving audience. ### Getting feedback We view the Appwrite documentation as a product. So when we notice problems, we apply the same product design processes to address them. The Appwrite documentation needed an upgrade to adapt to Appwrite developers’ evolving needs. Unlike many other projects, we already know what the problems are. We receive feedback from the "State of Appwrite" surveys, the Appwrite [Discord community](https://appwrite.io/discord), issues on the [docs repo](https://github.com/appwrite/docs), Office Hours questions, developer interviews, UX research activities, [this discussion](https://github.com/appwrite/appwrite/discussions/5692), and more. The open-source community’s feedback gave us a head start. ### Planning the changes First, we laid out the requirements for the new documentation. We identified key friction points: lack of a search bar, insufficient navigation depth to abstract complex concepts, difficult for the community to contribute, and incomplete coverage of the discover, evaluate, learn, build, and scale stages of a typical developer journey. Then, we looked at other docs sites for inspiration. We liked various aspects of [Stripe](https://stripe.com/docs), [React](https://react.dev/learn), [Vue](https://vuejs.org/guide/essentials/reactivity-fundamentals.html), and [Datadog](https://docs.datadoghq.com/) docs, which inspired our design process. We notice how their approach to documentation avoids or generates friction points. We took what we liked, and left out what didn’t work for us. We also attend conferences like DevRelCon and Refactor DX to learn about the latest trends in developer experience, technical writing, and accessibility to evaluate our design's strengths and shortcomings. A bonus is the fresh eyes we pull aside every conference for in-person feedback, especially rare for a fully remote team like us. ### Building the new docs The new Appwrite docs need to cover the entire developer journey. This means it needs to help the developers discover Appwrite, evaluate its capabilities, learn to use Appwrite, build something useful, and scale it to support product growth. Appwrite’s new documentation has a navigation flow that aims to follow this journey. We introduced overview pages that introduce Appwrite’s capabilities, platform pages to help developers evaluate Appwrite’s design, more quick starts for frameworks to shorten the learning stage and build confidence, and expanded tutorials that help developers build and scale. ![Overview of Appwrite's new documentation](/images/blog/dont-blame-the-readers-write-the-docs-they-need/overview.png) Developers loved the simplicity of the old Appwrite documentation, and we didn’t want to lose that part of our identity. Each deep dive section still begins with the same one-page quick start, which may be enough information for veterans. More comprehensive documentation and suggested usage are available, but further down the navigation. ![Quick start in the Authentication docs page](/images/blog/dont-blame-the-readers-write-the-docs-they-need/quickstart.png) For learners and students, we added step by step tutorials to help you get to a working app faster and build confidence. ![A complete tutorial in the new docs](/images/blog/dont-blame-the-readers-write-the-docs-they-need/tutorials.png) With all the new content, the new Appwrite docs also introduce **search**. Search helps keep Appwrite docs easy to navigate. ![Search feature in new documentation](/images/blog/dont-blame-the-readers-write-the-docs-they-need/search.png) The new docs are written using an extended flavor of markdown made with [Markdoc and Svelte](https://github.com/TorstenDittmann/svelte-markdoc-preprocess), which makes community contributions easier. Markdown is easy to read and we can extend it with custom syntax like this multi-code selector. `````` {% multicode %} ```js console.log("I'm javascript"); ``` ```py print("I'm python") ``` {% /multicode %} `````` Which renders multi-select code fences like you find all over our docs. {% multicode %} ```js console.log("I'm javascript"); ``` ```py print("I'm python") ``` {% /multicode %} The documentation source is easy for contributors to read and allows us to extend its syntax with powerful features. ### Moving forward The most important part of these changes is to give us *room to grow*. There is more room for content, especially community-contributed tutorials, quick starts, and integration guides. Truth is, while we build the Appwrite Platform, the community has built more successful and production-ready projects. Even weeks after the launch of the new documentation, we’re already receiving a ton of help from the community through issues and PRs. With the power of open source, the new knowledge base for Appwrite developers will grow faster than ever before. Checkout the [Appwrite documentation on GitHub](https://github.com/appwrite/website) --- ## Improving user security in your web apps with email OTP auth https://appwrite.io/blog/post/email-otp-auth-sveltekit To discover a balance between security and user convenience, one growing trend we have seen recently is the implementation of passwordless authentication. Today, both small and large companies are transitioning to using passwordless authentication methods over traditional password-based ones, such as Expensify (whose transition was also covered in a Forbes [article](https://www.forbes.com/sites/quickerbettertech/2023/05/29/on-technology-expensify-forces-passwordless-on-its-users-and-good-for-them/?sh=397a7b017cac) in 2023). Appwrite has always maintained support for both types of authentication, featuring phone-based OTPs (one-time passwords) and magic URLs in our list of authentication methods. With the recent Appwrite 1.5 release, we added a new passwordless authentication method: email OTPs. In this blog, you’ll learn how email OTP authentication works and how you can implement it in a SvelteKit application. ### What is email OTP authentication? [Email OTP authentication](https://appwrite.io/docs/products/auth/email-otp) lets users create accounts using their email address and sign in using a 6-digit code delivered to their email inbox. This method is similar to [Magic URL login](https://appwrite.io/docs/products/auth/magic-url) but can provide a better user experience in some scenarios. #### Email OTP vs magic URL Email OTP authentication sends an email with a 6-digit code that a user needs to enter into the app, while magic URL authentication delivers a clickable button or link to the user's inbox. Both allow passwordless login flows with different advantages. | Benefits of email OTP | Downsides of email OTP | | --- | --- | | Doesn't require the user to be signed into their email inbox on the device | Expires quicker | | Doesn't disturb the application flow with a redirect | Requires more inputs from the user | | Doesn't require deep linking on mobile apps | | ### Implementing email OTP in a SvelteKit app Now that we have a basic understanding of email OTP authentication, let’s learn how we can implement it in a SvelteKit application. #### Set up Appwrite 1.5 You can use email OTP authentication either by [registering on Appwrite Cloud](https://cloud.appwrite.io/) or [self-hosting Appwrite](https://appwrite.io/docs/advanced/self-hosting) on your system or an external VM/VPS. You can self-host Appwrite using the following Docker command: ```bash 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:1.8.1 ``` Once that is done, [set up email delivery](https://appwrite.io/docs/advanced/self-hosting/email) on your self-hosted Appwrite instance. This can be done by visiting your `appwrite` directory and updating the `.env` file in a similar manner as follows: ``` _APP_SMTP_HOST=smtp.sendgrid.net _APP_SMTP_PORT=587 _APP_SMTP_SECURE=tls _APP_SMTP_USERNAME=YOUR-SMTP-USERNAME _APP_SMTP_PASSWORD=YOUR-SMTP-PASSWORD _APP_SYSTEM_EMAIL_ADDRESS=YOUR-SENDER-EMAIL ``` Don’t forget to run `docker compose up -d` after you update the environment variables to ensure the changes are implemented in your instance. Once your Appwrite instance is set up, create a project and copy both the Appwrite API endpoint and project ID. #### Create your SvelteKit web app In order to create your SvelteKit application, open your terminal and enter the command `npm create svelte@latest` to create a project. ```bash npm create svelte@latest create-svelte version 6.0.10 ┌ Welcome to SvelteKit! │ ◇ Where should we create your project? │ appwrite-email-otp │ ◇ Which Svelte app template? │ Skeleton project │ ◇ Add type checking with TypeScript? │ No │ ◇ Select additional options (use arrow keys/space bar) │ Add ESLint for code linting, Add Prettier for code formatting │ └ Your project is ready! ``` Once that is done, enter the directory and install any existing dependencies. ```bash cd appwrite-email-otp npm i ``` Also, create a file `.env` in the root directory and add your Appwrite API endpoint and project ID. ``` PUBLIC_APPWRITE_ENDPOINT= PUBLIC_APPWRITE_PROJECT_ID= ``` #### Install the Appwrite Web SDK and prepare the app logic Once your project is ready, install the Appwrite Web SDK using NPM: ```bash npm i appwrite ``` As soon as that’s done, we can start preparing our application logic. For that, visit the `src/lib` directory and create a file `appwrite.js`. Add the following code to the same. ```js import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from "$env/static/public"; import { Client, Account } from "appwrite"; const client = new Client() .setEndpoint(PUBLIC_APPWRITE_ENDPOINT) .setProject(PUBLIC_APPWRITE_PROJECT_ID); export const account = new Account(client); ``` In the same directory, create a file `user.js` and add the following code. ```js import { account } from "./appwrite"; import { ID } from "appwrite"; export const user = { createOtp: async (email) => { return await account.createEmailToken({ userId: ID.unique(), email, phrase: true }); }, verifyOtp: async (userId, secret) => { return await account.createSession({ userId, secret }); } } ``` #### Update the application UI Once the authentication logic is ready, we can create the web app’s user interface. To prioritize ease of understanding and simplicity, the example will not feature any styling-related code. In the `src/routes` directory, visit the file `+page.svelte` and replace it with the following code. ```html

    Enter Email

    Enter OTP

    {securityPhrase}

    ``` This UI features both steps of the email OTP flow, entering the email to send an OTP followed by entering the OTP to login. It also features the security phrase sent on the email along with the OTP so that the user can verify that a third party didn’t initiate the authentication flow. Once this is done, you can test the application by running the command `npm run dev` and opening the app URL in your browser. ### Next steps And with that, our demo application is ready. If you liked this project or want to investigate the full codebase, visit the [GitHub repository](https://github.com/adityaoberai/appwrite-email-otp-demo). For more information about Appwrite Authentication, visit the following resources: - [Appwrite Authentication Docs](https://appwrite.io/docs/products/auth): These docs provide more information on how to use the different methods offered under Appwrite Authentication. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Secure sensitive database fields with encrypted string attributes https://appwrite.io/blog/post/encrypted-attributes-for-sensitive-fields Modern applications often rely on personal, identifying, or internal data to function. Whether it is a user's name, a support message, or a private note added by an administrator, certain fields carry weight beyond their technical type. In Appwrite, these fields can now be stored with encryption at rest by using **encrypted string attributes**. This feature allows developers to protect data inside their database without requiring complex encryption logic on the client side. It is not end-to-end encryption, and it does not prevent project owners from reading data. However, it introduces a meaningful boundary between sensitive values and the infrastructure that holds them. This guide introduces encrypted string attributes, explains how they function, where they are appropriate, and what limitations they carry. ### A protection layer for sensitive data When a string attribute is marked as encrypted, Appwrite applies AES-128 encryption in Galois/Counter Mode (GCM) before writing it to the database. This means the data is never stored in plaintext at rest. It remains protected even in the case of unauthorized access to the underlying database. For many applications, that separation between what is readable in production and what is stored in the backend is enough to reduce risk and support stronger privacy standards. ### Using encrypted attributes in the Console Encrypted string attributes can be created or edited through the Appwrite Console. To create an encrypted attribute: 1. Open your Appwrite Console 2. Navigate to **Databases** and select your database 3. Choose a collection and open the **Attributes** tab 4. Click **Create attribute**, then select **String** ![create attribute modal](/images/blog/encrypted-attributes-for-sensitive-fields/create-attribute-modal.png) 5. In the attribute creation modal, enable the **Encrypted** checkbox ![string attribute modal](/images/blog/encrypted-attributes-for-sensitive-fields/string-attribute-modal.png) You may also configure standard settings, such as size limits, default values, whether the field is required, or whether it supports arrays. Note that encrypted string attributes require a **minimum size of 150 characters**. Once created, the attribute will appear in your schema with a lock icon indicating that its contents are encrypted at rest. ![encrypted attribute screen](/images/blog/encrypted-attributes-for-sensitive-fields/encrypted-attribute-screen.png) Note that the encrypted string attribute feature is available only on **Pro plans and higher**. ### What encryption changes, and what it does not Encryption changes how Appwrite stores a value, but it does not change how that value is accessed within the Console. Project owners and team members with sufficient access will still be able to read encrypted attribute values in plaintext. Encryption applies only to how the data is written to disk. Most importantly, **encrypted attributes cannot be queried**. You cannot filter, search, or sort based on encrypted values. {% info title="Important" %} If you encrypt a field that you later need to query, you will need to delete and recreate it. {% /info %} ### When encryption fits Encrypted attributes are most suitable for data that must be stored, but not queried through client applications. This may include: - **Personally identifiable information**, such as government-issued IDs, full legal names, or birth dates - **Private internal data**, such as administrator notes, moderation logs, or internal support messages - **Sensitive metadata**, including IP addresses, request contexts, or system diagnostics - **Free-form user inputs**, such as complaint text, optional comments, or custom configuration fields These are values that carry risk if stored openly, but which rarely need to be queried. In many applications, such fields accumulate gradually in the background. They are often forgotten, even though they hold meaningful traces of identity and trust. Encrypting them protects against exposures. Encryption is not appropriate for fields that you need to filter or search. For example, a product category field should remain unencrypted if you need to filter products by category. A practical approach could be to pair an unencrypted, queryable label with a separate encrypted field that holds more sensitive information. ### Why this is not end-to-end encryption End-to-end encryption ensures that only the sender and intended recipient can read the data. In such systems, even the server has no ability to decrypt the message. Appwrite's encrypted attributes are intended for situations where data must remain accessible within the system, but should not be stored in plaintext at rest. If you are building applications where zero-knowledge privacy is required, such as secure messaging, personal vaults, or encrypted note sharing, you will still need to manage encryption manually on the client side. In these scenarios, the server should never receive unencrypted data. ### Compliance and responsibility Encrypted attributes support stronger privacy practices, but they do not guarantee compliance. If your application operates under frameworks such as **GDPR**, **HIPAA**, **CCPA**, or internal data security guidelines, storing sensitive data encrypted at rest is often considered a foundational requirement. This feature makes it easier to meet that requirement without restructuring your entire application. Still, encryption must be paired with other controls. Who can access the data is just as important as how it is stored. If administrators or engineers can read encrypted fields in the Console without restriction, the protection is partial. Role-based access, logging, and appropriate review practices are still necessary. ### Before you enable encryption Once an attribute is marked as encrypted, that decision cannot be reversed. You cannot decrypt the field, change it back to plaintext, or modify its query behavior. For this reason, it is important to consider the implications before enabling encryption. Ask yourself: - Will I ever need to filter or sort by this field? - Should this value be visible in the client application, or only within the Console? - Who inside my project will have access to the plaintext version of this data? - Is this field storing information that carries legal, ethical, or reputational risk? If you are unsure, it is better to begin with unencrypted fields and migrate later once your needs are clearer ### Wrapping up Appwrite's encrypted string attributes provide a meaningful layer of protection for sensitive data at rest. They offer a thoughtful approach to data security that allows you to separate what should be searchable from what should simply be safe. While they're designed to work within your project's access structure rather than replacing end-to-end encryption for communication, they deliver exactly what many applications need: secure storage without complexity. --- ## Enhancing type safety in software development with enums https://appwrite.io/blog/post/enhancing-type-safety We, as software developers, constantly tackle the challenge of writing robust and maintainable code. As we make ensuring type safety a goal in this process, enums stand out as a significant contributor. This blog delves into enums, exploring their fundamentals, practical advantages, and their impact on code maintenance and readability. ### Understanding what enums are Enums are a data type that enables a variable to be a set of predefined constants. This concept, available in many programming languages, allows developers to group related constants under a single umbrella. For example, an enum named **`Day`** could include constants like **`MONDAY`**, **`TUESDAY`**, and so on. One common question that comes up is how enums differ from constants. While constants are standalone unchanging variables, enums group related constants. This grouping not only provides clarity but also prevents the misuse of unrelated constants. Enums play a crucial role in type safety by restricting the values a variable or function parameter can take. This eliminates errors like assigning an unrelated or invalid value to a variable, a common issue with primitive data types like integers or strings. ### How enums improve software quality There are several benefits that using enums brings: - **Type safety**: Enums provide type safety, ensuring that only valid values can be used. Since the acceptable values are well-defined, the code becomes less prone to errors. - **Improved code clarity**: Enums make code more readable and understandable since they represent the logical grouping as well as the intended object explicitly. - **Reduced errors**: Enums help in avoiding errors such as typos or invalid values. This is particularly useful in switch statements or conditional logic, where enums can prevent the use of an unintended value. - **Ease of maintenance**: Changes to enums are centralized. Adding a new constant to an enum affects all the places where the enum is used, making the code easier to update and maintain. - **Self-describing code**: Enums can serve as a form of documentation. The names of the enum constants can clearly convey their purpose and use. - **Logical grouping of constants**: Since related constants end up getting grouped under a single umbrella, it becomes easier to understand the relationships between the constants. - **Facilitates comparisons**: Comparing values is straightforward and less error-prone than comparing literal strings or numbers. - **Stronger code contracts**: Enums define a clear contract in function or method signatures. When an enum is used as a parameter or return type, it clearly communicates what values are expected or returned. Due to these benefits, enums are particularly useful in numerous cases, such as: - Defining sets of related constants, like error codes or API response codes. - Defining states in state machines. - Representing command options when parsing command-line arguments. - Consuming sets of options in configuration settings. - Representing different levels in permissions and access control. With that said, it is necessary to avoid the following pitfalls when you’re working with enums: - **Avoid overuse**: Use enums only for fixed sets of constants, not for dynamic values. - **Be cautious with extensibility**: Enums can't be extended in all languages, so consider other structures if future expansion is likely and your preferred language limits extensibility. - **Manage namespace pollution**: Place enums in appropriate scopes to avoid cluttering the namespace or package. - **Careful external mapping**: Ensure accurate and synchronized mapping with external systems like databases or APIs. - **Meaningful string representations**: In most languages, enums are backed by integers by default. If you intend to use enums for purposes such as logging, UI, etc., ensure they have clear and consistent string representations. - **Proper documentation**: Clearly document each enum’s purpose and expected use for better maintainability and clarity if not implicitly understood. ### How enums improve code maintainability Code maintainability is extremely important for any piece of software, which is impacted by how readable the code is. There are several ways in which enums significantly enhance code readability: 1. **Descriptive value names**: Enums allow the use of descriptive names instead of numeric or string literals. For example, **`Status.ACTIVE`** or `**Days.MONDAY**` is more readable and meaningful than just **`"active"`** or **`"monday"`**. This makes the code self-explanatory and easier to understand at a glance. 2. **Grouping related constants**: Enums group related constants together, which organizes the code logically. Seeing an enum like **`Color`** with values **`RED`**, **`GREEN`**, **`BLUE`** instantly tells you that these are related choices, something less clear when these are standalone constants. 3. **Reduced magic numbers and strings**: Magic numbers and strings (unnamed numerical or string constants) can make code hard to understand and maintain. Enums replace these with named constants, reducing ambiguity and making the purpose of the code clearer. 4. **Better autocompletion and tooling support**: Modern IDEs offer excellent support for enums, such as auto-completion and usage search, which not only speeds up the development process but also reduces the likelihood of typos and errors. 5. **Refactoring ease**: If you need to change the value of an enum constant, you only need to do it in one place. This makes refactoring and updating code easier and safer, as the change is automatically reflected wherever the enum is used. 6. **Clarifying function signatures**: When enums are used in function signatures, they make it clear what type of values are expected or returned. This enhances readability by providing more information about the function's operation and requirements. ### Enums and Appwrite With the latest release of Appwrite, we have taken steps to improve type safety within our SDKs by leveraging enums. We have replaced all magic literals, such as constants needed for OAuth adapters, with enums to simplify the developer experience for all those who are building with Appwrite. ```client-web import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); account.createOAuth2Session({ provider: OAuthProvider.Apple, success: '', failure: '', scopes: ['email', 'profile'] }); ``` ### Resources Visit our documentation to learn more about Enums SDK support, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/sdks#enums) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Ensuring security amidst the XZ Utils backdoor concern https://appwrite.io/blog/post/ensuring-security-amidst-xz-concern In the light of recent unsettling revelations regarding a backdoor discovered in the widely-used XZ Utils, a compression tool used in Linux environments, including Red Hat and Debian systems, the cyber-security landscape has been abuzz with concern. This discovery had a large potential impact on encrypted SSH connections, a backbone of secure communications in the tech world. At Appwrite, ensuring the security and trust of our developers and users is paramount. We understand the concerns that arise from such vulnerabilities and their potential implications. It's crucial for the Appwrite community to know that Appwrite's services **remain unaffected** by the XZ Utils backdoor. This issue affected beta and test versions of Red Hat and Debian distributions, which Appwrite **does not use**. ### What does this mean for self-hosting Appwrite? For our valued users who prefer the self-hosted route, leveraging Appwrite on affected operating systems (OS), we understand your concerns. Here are our recommendations to ensure your self-hosted Appwrite instances remain secure: - Immediate Update/Removal: The first and foremost step is to check if you have the affect versions (`5.6.0`,` 5.6.1`) of the XZ Utils installed. If so, downgrade to a safe version or remove the utility altogether. - Enhanced Monitoring: Keep a keen eye on network traffic and system logs for any unusual activity. - Employ Firewalls: Employ stringent firewall rules to limit inbound and outbound connections to the bare minimum required for your operations. This reduces the attack surface significantly. - Regular System Audits: Conduct thorough audits of your systems to ensure no unauthorized modifications have been made to the OS or installed utilities. - Stay Informed: Follow updates from your OS's security advisory to apply security patches as soon as they are released. ### Does this affect Appwrite Cloud developers? Appwrite Cloud users can rest assured that our cloud infrastructure is secure and unaffected by the XZ Utils backdoor. The Appwrite team has taken necessary measures to ensure that containers in our cloud environment do not have the affected versions of the XZ Utils installed. We also took further steps to restrict SSH access to our cloud infrastructure to reduce attack surfaces further. No actions are required from Appwrite Cloud developers at this time. In a world where cyber threats are evolving at an alarming pace, the Appwrite team is committed to ensuring the security and reliability of Appwrite Cloud's infrastructure, so you can build applications with peace of mind. The team will continue to monitor the situation closely, take necessary actions to mitigate any potential risks, and communicate any updates transparently to the community. For any further questions or concerns, please reach out through [email](/contact-us) or on [Discord](https://appwrite.io/discord). We're here to support you every step of the way. --- ## Effective use of enums in API design https://appwrite.io/blog/post/enums-api-design Any developer who has ever had to design APIs will validate that clear and error-free communication is paramount to ensure the APIs are usable. One key tool that assists this process is enums. This article delves into the purpose and significance of enums in API design, illustrating how they enable effective and robust communication between different software. ### Understanding enums in the context of APIs Enums, short for enumerations, are a data type that allows a variable to be one of a predefined set of values. These values are usually identifiers, making enums significantly different from other data types. While most data types allow for a range of values, enums are restricted to specific, predefined values, enhancing clarity and predictability in code. Unlike integers or strings, which can represent a wide range of values, enums provide a limited set, reducing ambiguity. For example, an enum named **`Day`** could include constants like **`MONDAY`**, **`TUESDAY`**, and so on. This limitation is particularly useful in API design, where clear and predictable interfaces are essential. ### Advantages of using enums in APIs The use of enumerations, or enums, in API design offers several significant advantages that can enhance the overall quality and usability of an API. Here are some key benefits: - **Type safety**: Enums provide type safety, ensuring that the values passed through the API are of the expected type. This safety net enables data validation by default, reducing runtime errors and enhancing the overall robustness of the API. - **Clarity and readability**: Enums make APIs more readable and understandable. By using named values, they help convey the intent of the code, making it more accessible to developers who are integrating with the API. This clarity is especially beneficial in complex systems where understanding the range and role of specific values is crucial. - **Error reduction**: By limiting values to a predefined set, enums reduce the chance of errors that might occur from using invalid or unexpected data. This characteristic is particularly useful in environments where data integrity and accuracy are paramount. - **Self-documentation**: Enums serve as their own form of documentation. The predefined values in an enum can be quickly understood and referenced, making it easier for developers to grasp what values are permissible without constantly referring to external documentation. - **Consistency**: Enums enforce consistency in the use of values across different parts of an API. This consistency is essential for maintaining a uniform interface, making the API more predictable and easier to work with. - **Improved debugging and maintenance**: With enums, debugging becomes more straightforward as the range of values is limited and well-defined. This clarity makes it easier to trace issues related to data values. It also simplifies maintenance, as the use of specific values is more structured and predictable. - **Compatibility with API documentation tools**: Enums are often well-supported by API documentation tools and specifications like Swagger or OpenAPI. This compatibility makes it easier to generate clear and detailed API documentation, enhancing the developer experience. ### Best practices for implementing enums in API design Implementing enums in API design can significantly enhance clarity and robustness. However, to fully reap their benefits, it’s essential to follow certain best practices: - **Descriptive and consistent naming**: Choose names for your enums and their values that are self-explanatory and consistent. This practice aids in readability and maintainability. For example, an enum for user roles should have names like **`ADMIN`**, **`USER`**, **`GUEST`**, clearly indicating their function. - **Avoid magic literals**: Use enums to replace magic numbers or strings in your code. This makes your code more readable and less error-prone, as it's clearer what each value represents. - **Plan for change with versioning and deprecation strategies**: Enums should be managed with the understanding that they might need to change over time. Implement versioning and deprecation strategies for your API to handle changes without breaking existing clients. - **Use enums for stable sets of values**: Enums work best for values that are not expected to change frequently. Avoid using enums for values that are likely to change often or are unbounded. - **Consider using string enums over numeric enums**: String enums can offer clearer and more descriptive values, especially when viewed in JSON format, which is common in RESTful APIs. - **Expose enums in API documentation**: Make sure that your API documentation clearly indicates where and how enums are used. This inclusion helps developers quickly understand what values are expected. - **Test enum implementations thoroughly**: Include enums in your testing strategy to ensure they behave as expected under various scenarios and that any errors are handled accordingly. ### Common pitfalls of using enums and how to avoid them Enums, while beneficial, can lead to several pitfalls if not used judiciously in API design. Here are some common issues and strategies to avoid them: - **Overusing enums**: - **Pitfall**: Enums can be overused for data sets that are too large or dynamic, leading to frequent updates and maintenance headaches. - **Avoidance**: Use enums only for data sets that are relatively stable and have a limited number of values. For dynamic data, consider other data structures or reference tables. - **Breaking changes and backward compatibility**: - **Pitfall**: Modifying existing enums (like removing or renaming values) can break backward compatibility, affecting clients using the API. - **Avoidance**: Treat enums as immutable once they are used in production. Add new values if necessary, but avoid removing or renaming existing ones. Use versioning for significant changes. - **Inflexibility in expansion**: - **Pitfall**: Enums can become inflexible if not planned correctly, making it difficult to accommodate new values or use cases. - **Avoidance**: Anticipate potential future expansions and design enums with extensibility in mind. Consider combining enums and configuration files or databases for highly dynamic sets. - **Misuse of user-generated values**: - **Pitfall**: Using enums for user-generated or frequently changing values can lead to an unsustainable number of updates. - **Avoidance**: Avoid using enums for data that is better suited to user input or where frequent changes are expected. Enums should represent a predefined set of constants. - **Poor naming conventions**: - **Pitfall**: Vague or inconsistent naming in enums can lead to confusion and errors in implementation. - **Avoidance**: Use clear, descriptive, and consistent naming conventions for enums and their values. Ensure the names make sense within the context of the API. - **Inadequate error handling for invalid values**: - **Pitfall**: Not handling invalid enum values properly can lead to uninformative errors and debugging difficulties. - **Avoidance**: Implement robust error handling that clearly communicates when an invalid enum value is used, providing guidance on acceptable values. - **Not considering API documentation tools**: - **Pitfall**: Enums not well represented in API documentation can lead to confusion for API consumers. - **Avoidance**: Ensure that your API documentation tools properly represent enums, making it clear what values are acceptable in each context. By being aware of these pitfalls and employing strategies to avoid them, you can effectively leverage enums in your API design, enhancing clarity and maintainability while avoiding common issues. ### How Appwrite is leveraging Enums With the latest release of Appwrite, we have taken steps to improve type safety within our SDKs by leveraging enums. We have replaced all magic literals, such as constants needed for OAuth adapters, with enums to simplify the developer experience and make it less error-prone for all those who are building with Appwrite. This change exemplifies our commitment to robust and user-friendly API design. ```client-web import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); account.createOAuth2Session({ provider: OAuthProvider.Google, success: '', failure: '', scopes: ['email', 'profile'] }); ``` ### Resources Visit our documentation to learn more about Enums SDK support, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/sdks#enums) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Everyone can do DevRel (but should they?) https://appwrite.io/blog/post/everyone-can-do-devrel-but-should-they One thing that I can say, having spent five years in communities and over two years as a Developer Advocate at Appwrite, is that Developer Relations (or DevRel) isn’t just a job but a philosophy. DevRel is something we can do by helping developers in our communities, sharing knowledge through our content, or writing code and building projects. That being said, should everyone be rushing towards a job in Developer Relations? Recently, I have observed a bubble developing around this space. I want to take this blog as an opportunity to share my opinion on whether everyone should chase DevRel as a career. Before deciding whether everyone should become a DevRel professional, let’s discuss what DevRel is. ### Understanding DevRel > Developer Relations, or DevRel, is a domain that focuses on maintaining relationships with the folks building on an organization's technologies or products. Essentially DevRel acts as the **bridge between the code and the community**. DevRel folks are often responsible for maintaining communication between organizations and developers to ensure a better information flow and feedback loop. Thus, both entities have a better experience and growth path. ![bridge](/images/blog/everyone-can-do-devrel-but-should-they/bridge.png) DevRel teams came into inception because as we move towards a more technologically-enabled world, we see more people developing solutions to problems using programming and computer science and more companies catering to such folks. With more and more organizations catering their services to developers, it became apparent that these organizations need people who can **build** with their platforms, **communicate** with developers, **educate** newcomers, **grow** their audience, etc. DevRel aims to fit this gap precisely. Now that we’ve discussed what DevRel is, let’s talk briefly about what skills and experience DevRel practitioners need to succeed. ### What experience should DevRel practitioners possess Considering that DevRel can be viewed as an amalgam of **code**, **content**, and **community**, most roles in this space need you to be a jack of all three trades while being a master of (at least) one. As a Developer Advocate at Appwrite, I provide technical assistance and contribute to our product aside from creating content for our blog and social media, developing new community initiatives, and so on. My experience has taught me that, while there is always space to learn, as a DevRel practitioner (for example, Developer Advocate), you should gain experience on all three fronts (whether through dedicated jobs or by alternate means) before rushing into this career, and here is why: #### Writing code The time I spent learning how to build projects and working on real-world solutions (through self-defined problems or my internship) has substantially helped me in my DevRel journey. Understanding the pros and cons of the product I advocate for from a technical standpoint allows me to build a better sense of developer empathy for our community members. If I couldn’t build with our product, it would be impossible for me to relate to their problems and help solve them. Moreover, being able to code allows me to experience the Developer Experience of our product and offer feedback to our engineers, which enables better product development. #### Creating content One key factor for any successful DevRel team is communicating with the brand and product’s audience; content enables that in the best way possible. It’ll allow you to advocate for your product more accurately and increase your likability and relatability, which also benefits your personal and organization’s brands. Therefore, being able to create and deliver content (written, audio, or video) is a skill that DevRel practitioners must develop. #### Building communities My journey toward Developer Relations started by volunteering in communities. From leading a hackathon community (among others) to working as a Community Intern at a coding education company, I gained experience in planning new initiatives, managing their execution, collaborating with external stakeholders, building an audience, sustaining engagement, and maintaining brand growth. This experience gave me insights into how companies perceive communities and why they are necessary for user feedback and audience growth. Like it or not, being a DevRel professional, you are contributing to the business at the end of the day. Aside from learning how to work with larger groups of people, building communities helps to evaluate how you can impact the growth of an organization. ### Is DevRel just another fad? While I firmly insist that DevRel isn’t a fad, currently, a bubble is beginning to exist. Far too many people are prematurely rushing toward this domain for three reasons: - DevRel might seem like an **easier, more accessible way into tech than software development** (in all honesty, this isn’t true at all!) - DevRel provides people with a **platform where their voice seems to matter more**. - DevRel work can help **grow someone’s personal brand**. Truthfully, anyone chasing a DevRel career in this manner often does not sustain here for long. This is because DevRel has so much beneath the tip of the iceberg. Aside from just speaking at stages and delivering pre-planned content or engaging with people in communities and social media, people in DevRel are often invested in various functions such as developer experience testing and feedback, sample application development, planning new community initiatives, technical and community thought leadership, quantifying the impact the community has on the product’s growth, and more depending on what their products need. Many of these aren’t always done publicly, so it is difficult to perceive them. It is necessary to evaluate whether these are functions you want to be involved in. Pre-maturely deciding whether DevRel is the space for you can also cause you to **burn out** rather severely and impact your career direction! ### Taking this decision Truthfully, the decision on whether you want to start a career in Developer Relations isn’t one you need to rush. I spent two years volunteering in communities and worked for five months as a Community Intern, two months as a User Research Intern, and two months as a Software Developer Intern (aside from other contractual obligations) before I made up my mind to apply for my first Developer Relations role. These experiences, when combined, allowed me to understand my skills and experiences, which allowed me to develop my aspirations in this space. The truth is that code, content, and community are vast spaces of their own; you can very well fall in love with any of these spaces individually and pursue your career there, or at least give yourself a chance to fail and move forward. As long as you spend time understanding the most suitable space for you to work in, you’ll be absolutely fine. ### A little about Appwrite [Appwrite](http://appwrite.io/) is an open-source Backend-as-a-Service (BaaS) platform that lets you add Authentication, Databases, Functions, and Storage to your product and build any application at any scale, own your data, and use your preferred coding languages and tools. Learn more about Appwrite: - [Appwrite Docs](https://appwrite.io/docs) - [Appwrite Discord](https://appwrite.io/discord) - [Appwrite GitHub](https://github.com/appwrite) - [Appwrite YouTube channel](https://youtube.com/@appwrite) --- ## Everything new in Next.js 16 https://appwrite.io/blog/post/everything-new-in-nextjs16 Next.js 16 is here, and it brings one of the most polished releases the framework has seen in a while. Instead of chasing big rewrites, this version focuses on the fundamentals, faster builds, predictable caching, smarter routing, and better developer visibility. And if you’re deploying on **Appwrite Sites**, you can start using Next.js 16 today. The latest release is fully supported, so you can build, test, and host your Next.js projects seamlessly on Appwrite’s open-source infrastructure. Many of the features introduced in earlier betas are now stable and production-ready, giving developers more control and consistency across their workflows. Here’s what’s new, what’s changed, and why it matters for anyone building with Next.js today. ### Cache components Caching in Next.js used to be a bit of a mystery. Sometimes your data was cached when you didn’t want it to be; other times it wasn’t cached at all. You’d deploy, see stale data, and wonder if the problem was in your code or in the framework. Next.js 16 fixes that by introducing **Cache Components:** A new, explicit caching model. Instead of Next.js deciding what to cache, you tell it what should be cached and when to revalidate. It’s built on top of **Partial Pre-rendering (PPR)**, which already blurred the line between static and dynamic pages. Together, they make rendering predictable and flexible. ### Next.js DevTools MCP One of the more forward-looking additions in this release is **Next.js DevTools MCP**. It hooks into the **Model Context Protocol (MCP)**, which lets AI tools and agents understand your project’s context, your routing, caching, and rendering behaviour. Why does that matter? Because AI that actually knows how your app works can give better debugging help, clearer explanations, and smarter suggestions. Instead of just dumping stack traces, it can reason about what’s happening under the hood. It’s still early, but it points toward a future where your dev tools don’t just react to errors, they understand them. ### proxy.ts The old `middleware.ts` file is now `proxy.ts`. That’s it. Same behaviour, better name. There has been a lot of confusion around how middlewares work in Next.js. These middlewares are not your typical middlewares. You’re at the mercy of any network calls you make in these middlewares, as a single slow network call may block the initial load of your entire webpage, which is not ideal. Middlewares in Next.js are used to do something lightweight, for example, redirecting users based on the authentication cookies stored. Since the term *middleware* continued to cause issues, the Next.js team finally decided to rename it to proxy so that the purpose of using it is clear. ### Logging improvements Next.js 16 also improves logs, and not in a superficial “we added colours” way. Build and dev logs now show where time is spent, breaking down compilation, rendering, and optimization steps. So if your build suddenly feels slower, you can immediately tell which parts are to blame. It’s a quiet but practical improvement that makes day-to-day development just a little less frustrating. ### Turbopack update **Turbopack** has officially graduated from beta and is now the default bundler for all new Next.js projects. It’s fast, like **2–5×** faster builds and up to 10× faster Fast Refresh. For large projects, filesystem caching (currently in beta) further cuts down on startup times by reusing compiled artifacts between sessions. You’ll notice the difference most if you’re in that constant build, refresh–iterate loop all day. It’s less about benchmarks and more about getting back into flow faster. ### Build Adapters API If you’ve ever had to deploy a Next.js app in an unusual environment, say, outside Vercel, this one’s for you. **Build Adapters**, now in alpha, let you hook into the build process and modify it without forking the framework. It’s especially handy for teams self-hosting or building custom pipelines. This update shows that Next.js is starting to take flexibility seriously for developers running the framework in different environments. ### React compiler support Next.js 16 now includes stable support for the **React Compiler**, which automatically memoizes components to cut down on unnecessary re-renders. You don’t have to sprinkle `useMemo` or `useCallback` everywhere. The compiler handles it. There’s a small tradeoff in build time since it uses Babel, but the performance gains in rendering make it worthwhile for complex UI-heavy apps. ### Routing and Prefetching Routing has seen a major under-the-hood upgrade. Prefetching is now more efficient thanks to layout deduplication. Shared layouts download once, even if you prefetch dozens of links. And incremental prefetching ensures that only what's missing gets fetched instead of entire pages. It even cancels prefetch requests when links leave the viewport. You won’t have to change your code to benefit. Everything just feels snappier. ### Refined caching APIs Caching APIs have been cleaned up and made more explicit. The updated `revalidateTag()` now takes a cache-life profile, letting you fine-tune how data revalidates. The new `updateTag()` API adds “read-your-writes” behavior, so when users make changes, they see them reflected right away. And `refresh()` handles refreshing uncached data like live counts or notifications. Together, these updates turn caching into a transparent system that behaves exactly how you expect, not how the framework guesses you want it to. ### React 19.2 and Core Updates Next.js 16 ships with **React 19.2**, which brings some nice quality-of-life improvements: - `useEffectEvent()` for cleaner effect logic - View Transitions for smoother navigation - The new `` component for managing background UI states The release also updates core requirements. You’ll need Node.js 20.9+, TypeScript 5.1+, and modern browsers. Old features like AMP support and the `next lint` command are gone, making the framework leaner overall. ### Wrapping up Next.js 16 isn’t a release that changes how you build. It’s one that changes how your build *feels*. Caching is now predictable. Builds are faster. Routing is leaner. Logs are clearer. And with [Appwrite Sites now supporting Next.js 16](/docs/products/sites/quick-start/nextjs), you can deploy and host your latest projects right away, from development to production, on fully open-source infrastructure. Whether you’re experimenting with Cache Components, testing Turbopack, or building production apps, everything runs smoothly on Appwrite Sites. ### More resources - [Deploy a Next.js app to Appwrite Sites](/docs/products/sites/quick-start/nextjs) - [Learn more about Appwrite Sites](/docs/products/sites) --- ## See what is new with Appwrite 1.5 https://appwrite.io/blog/post/everything-new-with-appwrite-1.5 Appwrite just finished its release announcements for version 1.5 with a week of celebration called Init_, so here's the TLDR on all the announcements we made last week. #### Day 0 - Messaging A new product that allows you to send SMS, email, and push notifications through a variety of 3rd party providers such as Twilio, APNS, Firebase cloud messaging, Vonage, Sendgrid, Mailgun, and more. With just a few lines of backend code, you can set up a full-functioning messaging service under one unified platform. ```ts messaging.createSms( '6541...ea30', 'Thanks for signing up!', ['65415281565570ca1f9c'], ); ``` With messaging, we also have a fully functional interface for organizing messages, recipients and providers all directly from the console ![Messaging Console](/images/blog/everything-new-with-appwrite-1.5/messaging-console.png) #### Day 1 - SSR Improved support for server-side rendering authentication patterns. Appwrite was optimized for CSR before the 1.5 release, and now we've added official methods and patterns for using Appwrite authentication with popular frameworks like NextJS, Svelte kit, Nuxt, and others like them. The big change was that Appwrite now allows you to generate and access sessions server side to set session cookies and use these sessions to authorize future requests. ```ts const session = account.createEmailPasswordSession({ email, password }) console.log(session.secret) // Output: 'eyJpZCI...sdfahfkjjy' ``` Using the new `setSession` method, we can now retrieve a session secret from our cookies and authorize users to perform authenticated request to our server. ```ts client.setSession(session.secret) const currentUser = await account.get() ``` #### Day 2 - 2FA With the addition of Two-factor authentication, you can now require users to provide a second authentication factor by choosing between a one-time code sent via phone, email, or TOTP with an authenticator app. Implementing Two-factor authentication is as easy as creating a challenge using the `createMFAChallenge` method, which will generate a one-time code, and solving the challenge with the `updateMFAChallenge` method using the one-time code you received. ```ts const challenge = await account.createMFAChallenge({ type: 'email' }); const challengeId = challenge.$id; await account.updateMFAChallenge({ challengeId, otp: 'challengeCode' }); ``` #### Day 3 - Database Operators Two new database operators for more control and flexibility when writing queries. - `contains` - partial text matches on string attributes, array element matching on array attributes - `or` - write logical OR queries ```ts Query.contains('content', ['happy', 'love']) Query.or([ Query.contains('name','ivy'), Query.greaterThan('age',30) ]) ``` In addition to these key announcements, Appwrite announced Enum SDK support and the addition of more updated runtimes to the Appwrite ecosystem. #### Content list You can find all the official release announcements and documentation for each day of the week below ##### Day 0: Messaging - [Announcement Video](https://youtu.be/w-izHSKXqtU) - [Product tour](https://youtu.be/QdDgPeuBZ1I) - [Announcement article](/blog/post/announcing-appwrite-messaging) - [Docs](/docs/products/messaging) ##### Day 1: SSR - [Announcement Video](https://youtu.be/jeL4cSovOBA) - [Product tour](https://youtu.be/7LN05c-ov_0) - [Announcement article](/blog/post/introducing-support-for-server-side-rendering) - [Docs](/docs/products/auth/server-side-rendering) ##### Day 2: 2FA & Enum SDK support - [Announcement Video](https://youtu.be/hpdUXOFay4M) - 2FA - [Product tour](https://youtu.be/OWRju8ZZuQ8) - 2FA - [Announcement article](/blog/post/announcing-two-factor-authentication) - 2FA - [Announcement article - Enum SDK support](/blog/post/introducing-enum-sdk-support) - [Docs - 2FA](/docs/products/auth/mfa) - [Docs - Enum SDK support](/docs/sdks#enums) ##### Day 3: Database Operators - [Announcement Video](https://youtu.be/73flN6mZqAs) - [Product tour](https://youtu.be/IMgl9f_iht4) - [Announcement article](/blog/post/introducing-new-database-operators) - [Docs](/docs/products/databases/queries) ##### Day 4: New runtimes - [Announcement article](/blog/post/introducing-enum-sdk-support) - [Docs](/docs/products/functions/runtimes) Join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## 10 best vibe coding examples you can learn from https://appwrite.io/blog/post/examples-of-vibe-coding Everyone seems to be talking about vibe coding lately. It's all over Twitter, YouTube tutorials, and weekend project threads. But beyond the hype, we were curious: what does it actually look like in practice? So we did some digging. We wanted to find real examples, projects people actually built in a few focused sessions that ended up being fully functional and worth sharing. Here are a few vibe coding examples that stood out. These are real-world projects/apps built by devs and non-devs. ### 1. Refetch Vibe coding tools used: Cursor, Appwrite This one's interesting. [Refetch](https://refetch.io/) is an open-source alternative to Hacker News controlled by YC. Using Refetch, you can explore the latest tech discussions and news. It's 100% vibe-coded and built on top of [Appwrite Cloud](https://appwrite.io/). It took [Eldad](https://x.com/eldadfux), builder of the platform, a net total of 15 hours to go from idea to fully functional application, all while watching Netflix and vibe coding alongside. Of course, Eldad, being an engineer himself, was able to build this in less time, but it really shows how quickly someone with technical know-how can ship something meaningful when the tools handle the heavy lifting. ### 2. Mealmuse Vibe coding tools used: Lovable and Cursor [Mealmuse](https://mealmuse.ai/) makes it easy for you to plan your meal and uses AI to create personalized meal plans based on what's in your fridge, your dietary preferences, and your schedule. You can snap a photo of your groceries or even your receipt, and it instantly identifies ingredients to generate meal plans, recipes, and shopping lists ### 3. Found My Focus Vibe coding tools used: Bolt Bored? “Found My Focus” takes that feeling and turns it into something useful. It's a psychology-based app designed to help people get unstuck, focus better, and be kinder to themselves while working. The app uses short, interactive experiments rooted in neuroscience and therapy practices to help users understand what's really behind their procrastination. Built by [Su](https://x.com/su_dreams), a coach exploring how compassion meets productivity, [Found My Focus](https://www.flowbound.app/) stands out for its thoughtful design and human touch. It's a great example of how vibe coding tools can help solo creators turn deeply personal ideas into working, beautifully crafted products. ### 4. Storypot Vibe coding tools used: Replit [Storypot](https://app.thestorypot.com/) is as wholesome as it sounds. You drag a few emojis into a pot, and it magically turns them into a short story. Built by [Akshan Ish](https://x.com/akshanish) on Replit for his kid, it started as a small weekend project and ended up being a family favourite. It's simple, creative, and a great reminder that sometimes the best projects come from just building for the people around you. ### 5. Timeless memories Vibe coding tools used: Lovable [Timeless Memories](https://timelessmemories.me/), built by [Oren Saban](https://www.linkedin.com/in/oren-saban/) turns old family photos into short AI-generated videos that bring moments back to life. Upload a photo, choose a style, and within minutes, you get a moving video that feels like watching history replay itself. It has a simple interface with no complex editing, no tech skills needed. Just a few clicks to turn scanned wedding portraits or faded childhood photos into something you can actually *feel* again. ### 6. ChoresAI Vibe coding tool used: v0 and Claude Code Getting kids to actually finish their chores, and then checking if they really did, can feel like a full-time job. That's the everyday problem [Ben Ogren](https://x.com/benogren) is trying to fix with [ChoresAI](https://www.chores-ai.com/). You snap a quick photo of the completed task, and the AI verifies it on its own. It even assigns age-appropriate chores, tracks rewards, and manages payments automatically. Parents stay in control, kids stay motivated, and the whole process feels a lot less like nagging and more like teamwork ### 7. Mixcard Vibe coding tool used: Cursor [Mixcard](https://mixcard.me/) turns your Spotify playlists into physical postcards, like modern-day mixtapes you can actually hold. You drop in a playlist link, add a message, and it prints a vintage-style postcard complete with a QR code to listen instantly. Built by [Alfred Megally](https://alfredmegally.com/), it's a small but charming project that bridges the gap between digital and physical. Film and music enthusiasts will love the nostalgic touch, a tangible way to share what you love with someone far away. ### 8. Stories of Life Vibe coding tool used: Bolt [Stories of Life](https://stories-of-life.vercel.app/) helps parents create personalized bedtime stories that grow with their child. Each story adapts to the child's age, interests, and emotions, turning bedtime into a moment of connection and calm. Built by [Harshitha P.](https://www.linkedin.com/in/harshitha-p-991776149/), it's a heartfelt use of AI that focuses on emotional growth and family bonding. The app even tracks how stories influence a child's mood over time, making it feel part storybook, part gentle parenting tool. ### 9. Nora AI Vibe coding tool used: Bolt [Nora AI](https://noratutor.xyz/) makes studying feel more natural by letting you learn through simple video conversations. You can talk to an AI tutor, ask questions, and get real-time transcriptions and summaries from each session. It also helps you stay organized by creating study plans and reminders based on what you've discussed. Built using Bolt, it's a straightforward example of how AI tools can make learning a bit more personal and easy to manage. ### 10. CareerCloud Vibe coding tool used: Replit [CareerCloud](https://careercloud.io/) helps job seekers create and refine resumes using AI. You can upload your resume or start fresh, and the platform automatically optimizes it for ATS systems, matches it to job descriptions, and gives clear feedback on how to improve. It also offers templates for different roles, from technical to creative, and tracks your application performance so you know what's working. ### Frequently asked questions (FAQs) **1. What are some real examples of vibe coding projects?** There are tons, but some standouts include [**Refetch**](https://refetch.io/), an open-source Hacker News alternative built with Appwrite and Cursor in just 15 hours; [**Mealmuse**](https://mealmuse.ai/), which turns fridge photos into meal plans; and [**Found My Focus**](https://www.flowbound.app/), a psychology-based productivity app built with Bolt. These projects show how both developers and non-developers are turning ideas into live products fast using AI-assisted workflows. **2. How long does it usually take to build something with vibe coding?** It depends on your skill level and project scope, but most examples we've seen, from side apps to working prototypes, come together in **a few focused sessions or a weekend**. The key is that you're not writing every line from scratch; you're guiding the AI with intent, feedback, and structure, which speeds up iteration dramatically. **3. What makes a good vibe coding project idea?** The best projects start simple: a clear goal, a limited scope, and a problem you personally care about. Tools like Cursor, Lovable, or Bolt work best when you give them focus—think **small apps, utilities, or creative experiments** that can evolve fast. **4. Can non-developers build real apps with vibe coding tools?** Absolutely. Many of the examples above were built by non-engineers or solo creators using AI-driven builders. The key isn't knowing every syntax detail. It's knowing how to communicate your intent clearly and refine the results. Vibe coding lowers the barrier between *idea* and *execution*, letting anyone with curiosity and persistence build something real. ### Wrapping up What all these projects have in common is how quickly and smoothly they went from idea to something real. From personal side projects to thoughtful family apps, vibe coding tools are making it easier than ever to build functional, polished products without weeks of setup or boilerplate. ### More resources - [Comparing the best vibe coding tools](/blog/post/comparing-vibe-coding-tools) - [20 vibe coding security best practices](/blog/post/vibe-coding-security-best-practices) - [Choosing the right AI database for your application in 2025](/blog/post/choosing-the-right-ai-database) --- ## Fixing OAuth2 authentication issues in Appwrite https://appwrite.io/blog/post/fixing-oauth2-issues-in-appwrite-cloud When integrating OAuth2 authentication with Appwrite, you might face an issue where authentication appears to succeed, but users remain logged out. The OAuth flow completes, the app redirects back as expected, yet when the app checks for an authenticated session, no user data is found. This issue can be frustrating because: - **It works in some browsers but fails in others (e.g., Brave).** - **It works on some devices but fails on others, or works sometimes and not others.** - **It correctly redirects after login but doesn't recognize the user session.** If this sounds familiar, you're not alone. Let's break down why this happens and how to fix it. ### What's causing the issue? The root cause is related to how modern browsers handle third-party cookies. When your app domain differs from Appwrite's domain, for example `cloud.appwrite.io` in Appwrite Cloud, browsers treat Appwrite's session cookies as third-party cookies and may block them. There are two ways to solve this issue: 1. **Use OAuth2 Tokens (Recommended)**: A modern, privacy-friendly approach that doesn't rely on third-party cookies 2. **Use Custom Domains**: Configure Appwrite to use your domain for all operations ### Solution 1: Use OAuth2 tokens The most straightforward solution is to use Appwrite's OAuth2 token-based authentication. This approach: - Works across all browsers, including those that block third-party cookies - Provides better security and privacy - Doesn't require additional infrastructure setup #### How OAuth2 token authentication works Instead of using the traditional `createOAuth2Session`, we use `createOAuth2Token` which implements a more privacy-friendly flow: 1. User clicks "Sign in with Provider" (e.g., Google, GitHub) 2. App calls `createOAuth2Token` which redirects to the provider's consent screen 3. After consent, provider redirects back with a token 4. App creates a session using this token 5. Session is established without relying on third-party cookies #### Implementation example Here's a generic example of how to implement OAuth2 token authentication: ```jsx // 1. Initialize your Appwrite client const client = new Client() .setEndpoint('') .setProject('YOUR_PROJECT_ID') const account = new Account(client) // 2. Implement the sign-in function const signInWithGoogle = () => { const baseUrl = window.location.origin // Instead of createOAuth2Session, use createOAuth2Token account.createOAuth2Token( 'google', // or any other provider `${baseUrl}/auth/callback`, // your callback URL `${baseUrl}/auth`, // failure URL ) } // 3. Implement the callback handler const handleCallback = async (userId, secret) => { try { // Create a session using the OAuth2 token await account.createSession({ userId, secret }) // Get the user data const user = await account.get() // User is now authenticated! return user } catch (error) { console.error('Authentication failed:', error) throw error } } ``` #### Why this solution works - **No Third-Party Cookies**: The flow doesn't rely on cookies from external domains - **Modern Security**: Uses token-based authentication, following OAuth2 best practices - **Universal Compatibility**: Works across all browsers and devices - **Simple Implementation**: Requires minimal code changes to implement ### Solution 2: Use custom domains If you prefer to use session-based authentication or need to use custom domains for other reasons, you can configure Appwrite to use your domain for all operations. #### Step 1: Set up a custom domain for your Appwrite project 1. **Go to the Appwrite Console → Project Settings.** 2. **Add your custom domain (e.g., `appwrite.your-app.com`).** 3. **Follow the DNS setup instructions (CNAME or A record) and verify the domain.** Once your custom domain is verified, Appwrite will allow you to use it instead of `cloud.appwrite.io` for API requests. #### Step 2: Update your Appwrite client to use your custom domain Modify your Appwrite client configuration so that all API requests, including authentication, use your custom domain instead of `cloud.appwrite.io`. ```jsx import { Client, Account } from 'appwrite' const client = new Client() .setEndpoint('') // Use your custom domain .setProject('YOUR_PROJECT_ID') const account = new Account(client) ``` Now, all authentication requests, including session management, happen on your custom domain. Cookies set by Appwrite will now be first-party cookies, eliminating the cross-domain issue that was causing authentication failures. #### Step 3: Modify the OAuth callback URL in your OAuth provider 1. **Go to your OAuth provider and update the "Authorized redirect URIs"**: - **Before (default Appwrite Cloud domain)**: ``` ``` - **After (your custom domain)**: ``` ``` 2. Save changes and deploy. By making this change, your OAuth provider will now redirect users back to **your custom domain** after authentication. This ensures the OAuth flow remains within your domain and avoids session cookies being stored under `cloud.appwrite.io`. ### Which solution should you choose? Both solutions are valuable and can be used independently or together, depending on your needs. Here's how to think about them: #### OAuth2 tokens (Solution 1) - Quick to implement - Works across all browsers - Modern and privacy-friendly - No infrastructure changes needed #### Custom domains (Solution 2) - Complete control over your domain - Consistent branding - First-party cookies support The solutions aren't mutually exclusive - they solve the same problem from different angles and can complement each other if needed. ### Summary Appwrite offers OAuth2 tokens and custom domains to solve authentication issues in modern browsers. OAuth2 tokens provide immediate, cross-browser compatibility without infrastructure changes, while custom domains give you complete control over your authentication flow. Need extra help? Check the [documentation](https://appwrite.io/docs/products/auth) or ask for support in the Appwrite [Discord community](https://appwrite.io/discord). ### Further reading - [Building custom authentication flows with Appwrite](https://appwrite.io/blog/post/building-custom-auth-flows?doFollow=true) - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react?doFollow=true) - [A modern developer's guide to user authentication](https://appwrite.io/blog/post/guide-to-user-authentication?doFollow=true) --- ## How to setup the Flutter starter template on Appwrite Sites https://appwrite.io/blog/post/flutter-starter-sites Most web hosting platforms don't support Flutter Web out of the box, often forcing developers to jump through hoops just to get their apps online. This lack of native support can make deploying Flutter Web projects unnecessarily complex and time-consuming. Appwrite Sites changes that by offering built-in support for Flutter Web, making it easy to host and scale your applications. Alongside Flutter, Appwrite also provides starter kits for popular frameworks like Next.js, React, Vue, Nuxt, Angular, and SvelteKit. In this blog, you'll learn how to set up the Flutter starter template and deploy it to [Appwrite Sites](/products/sites). ### Overview of the starter template Flutter Web is a part of the Flutter framework that allows developers to build responsive, high-performance web applications using a single Dart codebase. Appwrite's Flutter starter template includes: - A clean, single-page UI - Integration with Appwrite's SDK - Pre-configured deployment settings for Appwrite Sites' Static rendering strategy ![Deployed app](/images/blog/flutter-starter-sites/deployed.png) ### Deploy the starter template on Appwrite Firstly, you must head to Appwrite Cloud and [create an account](https://cloud.appwrite.io/console/register) if you haven't already (or [self-host Appwrite 1.7](https://appwrite.io/docs/advanced/self-hosting)). Next, create your first project, which will lead you to the project overview page. ![Add platform](/images/blog/flutter-starter-sites/add-platform.png) Head to the **Sites** page from the left sidebar, click on the **Create site** button, and select the **Clone a template** option. This will take you to the Appwrite Sites templates listing, where you should search `Flutter starter` and click on the template. ![Starter template](/images/blog/flutter-starter-sites/template.png) After selecting the template, you can choose to connect a GitHub repository now or at a later time. If you choose to connect a repository, ensure you select a production branch (leave the root directory as is). Then, review the preset environment variables, update the domain name if you want, and click on the **Deploy** button. You can watch the deployment logs as the site is built. {% info title="Alternative method to deploy starter template" %} As an alternative to the Appwrite console, you can create and deploy websites using the [Appwrite CLI](/docs/products/sites/deploy-manually#cli). Create your Flutter starter using the following shell command and configuration: ```bash appwrite init sites ? What would you like to name your site? Flutter starter ? What ID would you like to have for your site? unique() ? What framework would you like to use? Flutter (flutter) ? What specification would you like to use? 0.5 CPU, 512MB RAM ``` You can then make any edits to the website and deploy it using the following command: ```bash appwrite push sites ``` {% /info %} ### Test the starter template After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. You can then either choose to view the site by clicking on the **Visit site** button or view the site configuration (deployments, logs, domains, usage, and settings) by clicking on the **Go to dashboard** button. ![Congratulations](/images/blog/flutter-starter-sites/congrats.png) ### Next steps And with that, the Flutter starter kit is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. For more information about Appwrite Sites: - [Appwrite Sites product docs](/docs/products/sites) - [Quick start to deploy any Flutter Web app](/docs/products/sites/quick-start/flutter) - [Appwrite Discord server](/discord) --- ## Flutter vs React Native: Which framework is best for your app in 2024? https://appwrite.io/blog/post/flutter-vs-react-native Choosing between **Flutter** and **React Native** for mobile app development is more than just comparing features. Each framework comes with its own strengths, limitations, and unique use cases, making the decision impactful in several ways. This choice affects: - Your app's long-term performance - Ease of maintenance - Speed of development Let's break down the practical differences between these two frameworks and discuss some key factors you should consider as a developer. ### What's cross-platform development about? The core purpose of both Flutter and React Native is to solve a common problem in mobile development: managing two separate codebases for iOS and Android. Developing native apps has long been the gold standard for performance and user experience. However, the traditional native development process has some significant downsides: - **Increased cost**: You have to maintain two separate codebases. - **Complicated workflows**: Teams need to duplicate their efforts. - **Feature roll-out delays**: Releasing updates across platforms can be slow. #### Early solutions and their shortcomings Before Flutter and React Native, there were early cross-platform solutions like **Cordova** and **Ionic**. These frameworks used web technologies to create mobile apps, but they often fell short in two key areas: - **Performance**: Web-based solutions couldn't match the speed and responsiveness of native apps. - **User Experience**: Web technology layers made the apps feel sluggish and less polished. These shortcomings opened the door for modern cross-platform solutions, like Flutter and React Native, which promise: - **Native-like performance** without maintaining two codebases - **Faster development** times by sharing most of the codebase between platforms However, both frameworks introduce their own sets of challenges, and understanding them is key to making the right choice. #### The appeal of cross-platform development Cross-platform frameworks like Flutter and React Native provide several attractive benefits: - **Cost-efficiency**: A unified codebase means lower development costs. - **Faster time to market**: Since the code is shared between platforms, you can roll out updates quicker. - **Consistency**: Design consistency across platforms becomes easier to achieve. However, despite these benefits, the **reality of cross-platform development** is more complex, and each framework introduces specific trade-offs. Understanding where each excels and where each struggles will help you make an informed choice. ### Choosing between Dart and JavaScript One of the primary differences between Flutter and React Native is the programming language each uses. This choice of language significantly impacts how easy it is to onboard developers and what the long-term performance of your app will be. - **Flutter** uses **Dart**, a relatively newer language developed by Google. - **React Native** relies on **JavaScript**, one of the most widely used languages in web development. #### Dart (used by Flutter) **Dart** comes with several notable benefits: - **Ahead-of-time (AOT) compilation**: Dart compiles your code into native machine code ahead of time. This allows Flutter apps to start quickly and run efficiently, which is especially important for data-intensive apps or apps with complex animations. - **Just-in-Time (JIT) compilation**: During development, Dart uses JIT, allowing for fast development cycles through **hot reloads**. You can see changes in your app almost instantly without restarting. Dart's ability to compile directly into native code can significantly improve performance, especially for apps that: - Handle a lot of data - Use complex animations - Require high responsiveness even under heavy loads **Drawbacks of Dart**: - **Learning curve**: Dart isn't as widely adopted as JavaScript, so development teams familiar with JavaScript will face a learning curve. This could slow down the initial phase of your project as developers become familiar with Dart. #### JavaScript (used by React Native) JavaScript's main advantage is its widespread use. Most development teams are already familiar with it, which makes **React Native** a logical choice for teams with JavaScript experience. - **Widespread adoption**: JavaScript is used in a variety of development environments, making it easier to find developers with the necessary skills. - **Easy onboarding**: Teams already working with **React** on the web will find the transition to React Native relatively straightforward. **Challenges with JavaScript**: - **Performance bottlenecks**: React Native uses a **JavaScript bridge** to communicate with native components. This introduces some overhead, especially for complex user interfaces or animations. The need to pass through this bridge can slow down performance. - **Optimization efforts**: React Native introduced the **Hermes engine** to improve startup times and optimize JavaScript execution. While this engine helps, heavy apps with many complex interactions may still require additional performance tuning. #### Key takeaways on languages - **Go with Dart (Flutter)** if performance is a top priority and you're willing to invest in learning a new language. Dart's ability to compile into native code gives it an advantage in apps with complex functionality. - **Choose JavaScript (React Native)** if you need to quickly get up to speed and are looking for ease of use with existing JavaScript knowledge. It's also ideal for rapid prototyping. ### **UI Control: What level of customization do you need?** Another significant difference between Flutter and React Native is how each framework handles the **user interface (UI)**. UI design plays an important role in how users interact with your app, and each framework offers different levels of control and customization. #### Flutter's approach to UI: Impeller rendering engine - Flutter uses the **Impeller rendering engine** by default on iOS, optimizing for smoother animations and reduced "jank" during interactions. - Flutter provides **Material** and **Cupertino widgets** that align with Android and iOS styles, minimizing the need to mimic native behavior while allowing for a consistent UI across platforms. This control is particularly valuable for: - Apps that require **custom designs** or branding that doesn't conform to standard UI elements. - Apps with **unique layouts** or complex animations that need fine-tuning. **Downsides** of Flutter's UI approach: - Since Flutter doesn't use native UI components directly, the app may not feel as “native” as a React Native app. - Flutter provides **widgets** that mimic native components, but these aren't identical, and users might notice slight differences in look and behavior. #### React Native's approach to UI: Native components - React Native uses **actual native components** to render the UI. - On iOS, buttons are actual **UIButtons**. - On Android, buttons are native **Buttons**. This ensures that React Native apps feel more **integrated with the platform** and deliver a more familiar experience for users. **Advantages** of this approach: - Apps feel **native** from the start. - You don't need to mimic native behavior; React Native handles that for you. **Drawbacks**: - For highly customized UIs, you may need to build **custom components** or use third-party libraries. This adds complexity to the project. - The reliance on native components can sometimes limit how much control you have over the design. #### Key takeaways on UI customization - **Flutter** offers more customization control but sacrifices some native feel. - **React Native** provides a more natural native experience but limits your ability to customize UI elements without additional effort. ### **Ecosystem and libraries** Both Flutter and React Native have strong ecosystems, but each has its own strengths and limitations. #### Flutter's ecosystem - Flutter's ecosystem is **growing quickly**, supported by Google. Many packages for core functionalities, like state management and navigation, are available through **pub.dev** and receive frequent updates. - However, since Flutter is relatively new compared to React Native, there may be **gaps in the ecosystem**, particularly for niche functionalities. - In some cases, you might need to write custom solutions or wait for the ecosystem to catch up. #### React Native's ecosystem - React Native benefits from the vast **npm ecosystem**, which has been around for much longer. - You'll find packages for almost anything you need, from authentication to third-party API integration. - **Fragmentation** can be an issue, as some packages are not actively maintained or may not work well with the latest versions of React Native. - It can be challenging if you rely on a package that is no longer supported. #### Key takeaways on ecosystem - React Native's larger ecosystem gives you more options but comes with the risk of fragmentation. - Flutter's ecosystem is newer but growing rapidly, with high-quality packages backed by strong community and corporate support. ### **Development experience** Both frameworks provide a smooth development experience, but their approaches differ slightly. #### Flutter's development experience - **Hot reload** allows you to see changes instantly without restarting the app. - Flutter has **excellent integration** with popular IDEs like Visual Studio Code and Android Studio. - Debugging tools are robust, making development smooth and efficient. #### React Native's development experience - **Hot reloading** is also available, making it easy to see changes as you code. - For web developers familiar with **React**, the transition to React Native feels natural, thanks to shared syntax and concepts. - However, **native code** may need to be accessed more often to optimize performance, which can slow down development if you're not familiar with the native platforms. #### Key takeaways on development - **Flutter** offers a more integrated experience with tighter IDE support. - **React Native** is easier for web developers but may require more native platform knowledge for optimization. ### Comparison table: Flutter vs React Native | **Aspect** | **Flutter** | **React Native** | |--------------------------|-------------------------------------------|------------------------------------------------------| | **Programming Language** | Dart | JavaScript | | **Compilation** | Ahead-of-Time (AOT), Just-in-Time (JIT) | JavaScript Bridge (with Hermes engine) | | **Performance** | High due to native code compilation | Slower due to JavaScript bridge overhead | | **UI Customization** | Full control (Impeller rendering engine on iOS) | Native UI components | | **Learning Curve** | Higher (Dart is less common) | Lower (JavaScript is widely used) | | **Ecosystem** | Growing, high-quality packages on pub.dev | Large but fragmented npm ecosystem | | **Development Tools** | Strong IDE integration, robust debugging | Good tools, but native code often needed | | **Hot Reload** | Yes | Yes | | **Best for** | High performance, custom UI | Fast time-to-market, leveraging JS skills | ### Conclusion The choice between Flutter and React Native depends on your specific project requirements and the trade-offs you're willing to make. - **Go with Flutter** if performance is a top priority or if your app requires a custom, polished user interface. Flutter's ability to compile directly to native code gives it a performance advantage that React Native might struggle to match without significant optimizations. - **Choose React Native** if you want to leverage your team's existing JavaScript knowledge, get to market quickly, or build an app that relies heavily on native UI components. React Native allows for fast development with a wide range of packages and tools, though it may need additional performance tuning for complex apps. ### More resources - [Get started with Appwrite Realtime for Flutter](http://appwrite.io/blog/post/appwrite-realtime-for-flutter) - [How to build cross-platform applications with React Native](https://appwrite.io/blog/post/building-cross-platform-applications-with-react-native) - [SSR vs CSR with Next.js](https://appwrite.io/blog/post/csr-vs-ssr-with-nextjs) --- ## Free Angular hosting with Appwrite Sites - Simplified deployment and scalability https://appwrite.io/blog/post/free-angular-hosting Angular remains a top choice for building scalable and dynamic web applications, offering a robust framework with powerful tooling for both client-side and server-side rendering. While deploying Angular applications can be straightforward, finding a hosting solution that combines affordability, security, and developer-centric features can be challenging. [Appwrite Sites](/products/sites) provides a seamless hosting solution tailored for modern web applications, including Angular, with built-in performance optimizations, security features, and workflow integrations. This article highlights how Appwrite Sites streamlines Angular deployments, enhances development workflows, and stacks up against other hosting services. ### Why Appwrite Sites is a great choice for Angular developers #### 1. Free hosting with no upfront investment Budget constraints can be a major factor for individual developers and startups. Appwrite Sites removes this hurdle by offering free hosting, allowing teams to build, test, and iterate without worrying about costs. As your project scales, Appwrite provides a seamless upgrade path to accommodate growing needs. #### 2. Supports both static and dynamic rendering Angular applications can be built as static sites or dynamically rendered apps using Angular Universal. Appwrite Sites supports both approaches, ensuring fast global content delivery for static assets while enabling server-side rendering (SSR) through **Appwrite Functions** for dynamic applications. This makes it an ideal solution for everything from single-page applications (SPAs) to enterprise-grade web platforms. #### 3. Performance-driven global CDN Speed is essential for modern applications, impacting both user experience and SEO. Appwrite Sites leverages a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to serve assets efficiently, reducing latency and ensuring fast load times regardless of user location. #### 4. Built-in security and DDoS protection Security is a top priority when deploying web applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), mitigating malicious traffic to keep your site online. Additionally, it offers **custom domain support with automatic SSL encryption**, ensuring secure connections and user data protection without requiring extra configuration. {% call_to_action title="Host your Angular app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for seamless collaboration Every update pushed to Appwrite Sites generates a **deployment preview link**, allowing teams to review changes in a staging environment before releasing them to production. This feature simplifies collaboration and helps ensure quality assurance before updates go live. #### 6. Flexible deployment options Appwrite Sites makes it easy to deploy Angular applications with multiple methods: - [Git-based deployments](/docs/products/sites/deploy-from-git): Push updates directly from a connected repository. - **CLI-based deployment**: Deploy manually in seconds using the Appwrite CLI: ```bash appwrite deploy site ``` - **Manual file uploads**: Drag and drop your build files directly in the Appwrite Console. For new users, Appwrite provides **starter templates** and example projects, making it easier to get up and running quickly. #### 7. Integrated backend services for full-stack Angular applications Many Angular applications require authentication, databases, and backend functions. Appwrite Sites integrates seamlessly with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, enabling developers to build full-stack applications without relying on third-party backend providers. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other platforms that rely on external integrations for authentication, databases, and backend logic, Appwrite Sites offers a unified ecosystem, reducing complexity and improving performance. For Angular developers seeking a cost-effective, high-performance hosting solution with built-in backend services, Appwrite Sites is an excellent choice. Explore more with our [Angular Quick Start Guide](/docs/quick-starts/angular) and start deploying your Angular applications by visiting [Appwrite Sites](/products/sites). --- ## Free Astro hosting with Appwrite Sites - Deploy effortlessly https://appwrite.io/blog/post/free-astro-hosting Astro has emerged as a powerful framework for building ultra-fast, content-driven websites by leveraging its hybrid rendering approach and optimized build process. While deploying Astro applications is straightforward, finding a hosting platform that balances cost, scalability, and developer-friendly features can be challenging. [Appwrite Sites](/products/sites) offers a tailored hosting solution for modern web applications, including Astro, with built-in performance, security, and collaboration enhancements. This article explores how Appwrite Sites simplifies deployment, integrates with Astro's capabilities, and compares to other hosting options. ### Why Appwrite Sites is an ideal fit for Astro #### 1. Free hosting with no upfront costs For developers and small teams, keeping hosting expenses low is important. Appwrite Sites provides free hosting, eliminating financial barriers while ensuring a high-performance infrastructure. This allows you to focus on building and refining your application without worrying about costs as you scale. #### 2. Optimized for static and hybrid rendering Astro shines with its ability to generate highly optimized static sites while enabling selective hydration for dynamic elements. Appwrite Sites fully supports static exports, ensuring your site benefits from fast, globally cached delivery. Additionally, **Appwrite Functions** can extend Astro's capabilities by adding dynamic backend functionality where necessary. #### 3. Performance-boosting global CDN Speed is a key factor in both user experience and SEO. Appwrite Sites uses a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to efficiently cache and serve your static assets. This results in reduced latency, faster page loads, and improved site performance across all regions. #### 4. Security-first hosting with built-in DDoS protection As websites grow, security risks increase. Appwrite Sites comes with [DDoS protection](/docs/products/network/ddos), ensuring your application remains online by detecting and mitigating malicious traffic. Additionally, it includes **custom domain management** with automatic SSL encryption, securing your site with minimal setup. {% call_to_action title="Host your Astro app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for streamlined collaboration Appwrite Sites makes team collaboration easier by generating **deployment preview links** for every update. These previews allow you to test and review changes before going live, providing a seamless workflow for developers and stakeholders to verify updates in a staging environment. #### 6. Flexible deployment options Deploying an Astro application with Appwrite Sites is straightforward, offering multiple methods to suit different workflows: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically push updates from your repository's main branch. - **CLI-based deployment**: Deploy manually with a single command: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static files directly through the Appwrite Console. For new users, Appwrite provides **starter templates** and example projects, making it easy to get started with minimal configuration. #### 7. Integrated backend services for enhanced functionality While Astro focuses on static site generation, many projects require authentication, databases, and serverless functions. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, eliminating the need for third-party backend services and streamlining development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | Hybrid rendering | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No third-party | No third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting solutions that require third-party integrations for backend features, Appwrite Sites provides an all-in-one ecosystem with built-in authentication, databases, and serverless functions. For developers seeking a hassle-free hosting solution that simplifies deployment and minimizes dependencies, Appwrite Sites is a compelling option. Explore more with our [Astro Quick Start Guide](/docs/quick-starts/astro) and start deploying your Astro applications by visiting [Appwrite Sites](/products/sites). --- ## Free Flutter Web hosting with Appwrite Sites - Deploy and scale seamlessly https://appwrite.io/blog/post/free-flutter-web-hosting Flutter for Web allows developers to build rich, interactive web applications using the same Dart codebase as their mobile apps. However, finding a hosting platform that supports Flutter's web-optimized build while providing a cost-effective, scalable, and developer-friendly solution can be a challenge. [Appwrite Sites](/products/sites) offers an easy-to-use hosting solution tailored for modern web applications, including Flutter Web, with built-in performance optimizations, security features, and workflow integrations. This article explores how Appwrite Sites simplifies Flutter Web deployment, enhances performance, and compares to other hosting solutions. ### Why Appwrite Sites is a great choice for Flutter Web developers #### 1. Free hosting with no initial costs For independent developers and startups, hosting expenses can be a concern. Appwrite Sites removes this barrier by offering free hosting, allowing you to test, iterate, and launch applications without upfront investment. As your project scales, you can seamlessly upgrade to meet growing demands. #### 2. Optimized for Flutter's web architecture Flutter Web applications rely on a unique rendering model that outputs HTML, CSS, and JavaScript while maintaining a pixel-perfect UI. Appwrite Sites ensures fast performance, efficient asset delivery, and seamless rendering, making it an ideal choice for hosting Flutter-based web applications. #### 3. Global CDN for improved performance Performance plays a crucial role in user experience and SEO rankings. Appwrite Sites leverages a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve your application's static assets efficiently, reducing latency and improving load times across different regions. #### 4. Built-in security and DDoS protection Security is critical when hosting production applications. Appwrite Sites provides [DDoS protection](/docs/products/network/ddos), preventing malicious traffic from affecting your app's availability. Additionally, it offers **custom domain support with automatic SSL encryption**, ensuring secure communication between users and your application. {% call_to_action title="Host your Flutter app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for better collaboration Every deployment on Appwrite Sites generates a **preview link**, enabling teams to review changes before publishing them to production. This feature streamlines the development process by allowing testing and validation in a staging environment before going live. #### 6. Multiple deployment options Appwrite Sites provides flexible deployment workflows for Flutter Web applications: - [Git-based deployments](/docs/products/sites/deploy-from-git): Connect your repository to automatically deploy updates with every push. - **CLI-based deployment**: Deploy manually with a single command: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload your compiled Flutter Web build directly via the Appwrite Console. For new users, Appwrite also provides **starter templates** and example projects, making it easy to get up and running with minimal setup. #### 7. Integrated backend services for full-stack Flutter applications Many Flutter applications require backend features like authentication, databases, and server-side logic. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, eliminating the need for third-party services and simplifying full-stack development. ### How Appwrite Sites compares to other hosting solutions | Feature | Appwrite Sites | Firebase Hosting | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Optimized for Flutter Web | Yes | Yes | No | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | No | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | Firebase Auth | No | | Database | Appwrite Databases | Firestore | No Third-party | | Serverless functions | Appwrite Functions | Cloud Functions | AWS Lambda | | Storage | Appwrite Storage | Firebase Storage | No | | Cloud messaging | Appwrite Messaging | Firebase Messaging | No | | Realtime database | Appwrite Realtime | Firestore | No | Unlike Firebase and Netlify, Appwrite Sites provides an all-in-one solution with backend services, reducing the need for multiple integrations. It is designed to work seamlessly with Flutter Web applications, offering optimized performance and security while maintaining a simple deployment process. For developers looking for an efficient, high-performance hosting solution tailored for Flutter Web, Appwrite Sites is a great option. Explore more with our [Flutter Quick Start Guide](/docs/quick-starts/flutter) and start deploying your Flutter Web applications by visiting [Appwrite Sites](/products/sites). --- ## Best free hosting platforms in 2025 https://appwrite.io/blog/post/free-hosting-platform Web hosting has come a long way since the early days of shared servers and static sites. Today, developers expect more than just a place to put their files, they want speed, scalability, modern deployment workflows, and flexibility to grow from a free project into a full-scale production app. For **startups, agencies, and independent developers**, free hosting has become the gateway to innovation. It allows teams to test, build MVPs, and deploy client projects with little upfront cost. But not all free hosting platforms are created equal. In this guide, we’ll break down the **best free hosting, best web hosting, best frontend hosting, and best frontend deployment platforms in 2025**. We’ll compare providers like **Appwrite, Vercel, Netlify, Firebase, Render, and Railway**, and explain how to choose the right one for your needs. #### What makes a hosting platform the best? When evaluating hosting platforms, technical leaders such as CTOs and co-founders often weigh more than just price. Here’s what sets the best apart: ##### Performance & scalability * [Content Delivery Networks (CDNs)](/docs/products/network/cdn) to ensure global performance. * Edge computing for lower latency. * Auto-scaling infrastructure to handle traffic spikes. ##### Ease of deployment * Git-based workflows: push your code, and it’s live. * CI/CD pipelines for continuous delivery. * One-click deployment integrations. ##### Free tier benefits * SSL certificates for secure connections. * Bandwidth, storage, and generous limits. * Custom domains for professional branding. ##### Developer experience * Well-documented APIs and SDKs. * Active community and support channels. * CLI tools for automation. ##### Path to scale * Clear upgrade paths from free to enterprise. * Transparent pricing without hidden costs. #### Best free hosting platforms in 2025 Here are the top players offering free hosting solutions: ##### 1. Appwrite Sites Appwrite is an **all-in-one development platform with web hosting and backend deployment**. Unlike competitors, it doesn’t just provide hosting; it provides a full developer stack, including authentication, databases, realtime, [serverless functions](/products/functions), [messaging](/products/messaging), and more. * **Best for:** Full-stack apps, SaaS MVPs, teams who want backend + web hosting on one platform. * **Strengths:** Integrated backend services, scalability, strong open-source ecosystem. * **Free tier:** Generous limits with databases, APIs, storage, and hosting included. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} ##### 2. Vercel Vercel is the go-to platform for **Next.js** projects but supports other frameworks as well. It’s optimized for frontend-first workflows with global edge delivery. * **Best for:** React/Next.js developers. * **Strengths:** Lightning-fast deployments, edge functions, great for frontend-heavy teams. * **Free tier:** 100 GB bandwidth/month, SSL, deploy previews, and Hobby project support. ##### 3. Netlify Netlify popularized the **Jamstack** architecture and remains a leader in frontend hosting with serverless support. * **Best for:** Jamstack projects, agencies working with static sites. * **Strengths:** Functions, build previews, Git-based workflows. * **Free tier:** 125k requests/month, 100 GB bandwidth, free SSL, and deploy previews. ##### 4. Firebase Hosting (Google) Firebase offers **hosting for SPAs and PWAs** with easy integration into the Google ecosystem. * **Best for:** Teams already using Google Cloud or mobile-first apps. * **Strengths:** Built-in auth, real-time database, analytics. * **Free tier:** 10 GB storage, 10 GB/month data transfer, SSL, custom domain support. ##### 5. Render Render supports both **frontend and backend hosting**. * **Best for:** Developers needing both static hosting and backend APIs. * **Strengths:** Deploy web services, databases, and static sites with one provider. * **Free tier:** Free static sites and free instance types; 750 free instance hours per workspace/month. Included outbound bandwidth: 100 GB/month in the Hobby workspace (overage billed or services suspended without a payment method). ##### 6. Railway Railway supports full-stack apps with a usage-based model. * **Best for:** Developers needing managed deploys for web services and databases. * **Strengths:** Usage-based compute with automatic sleep on idle; serverless-like ergonomics. * **Plan notes:** No always-free runtime tier. Hobby/Pro are usage-based; trials may be available. Check current pricing before positioning it as “free.” #### Best web hosting platforms beyond free tiers Not every project can run forever on free hosting. For startups moving into production or enterprises with compliance needs, here are the main categories: ##### Shared hosting * Cheap but limited. * Often used for personal websites, not scalable SaaS apps. ##### VPS hosting * Dedicated resources on shared servers. * Good for mid-sized apps. ##### Cloud-native hosting (AWS, GCP, Azure) * Fully scalable, enterprise-grade, but requires DevOps expertise. * Ideal when compliance, SLAs, and high availability are essential. **When to move beyond free tiers:** * You hit bandwidth or storage limits. * You need enterprise support or compliance (HIPAA, SOC 2, GDPR). * Your app requires high availability and redundancy. #### Best frontend hosting options Frontend hosting is a different category than full-stack hosting. It’s focused on static sites, SPAs, and server-rendered apps. ##### React, Vue, and Angular apps * **Vercel** and **Netlify** excel with these frameworks. * **Appwrite** supports frontend hosting with backend integrations. ##### Next.js projects * **Vercel** is the best fit. * Alternatives: Netlify and Appwrite (with serverless support). ##### Static sites vs serverless deployment * Static sites: pre-rendered, simple, fast. * Serverless deployment: dynamic, scalable, more backend flexibility. #### Best frontend deployment workflows Modern deployment is more than FTP uploads, it’s automated, scalable, and tightly integrated with developer workflows. ##### GitHub/GitLab/Bitbucket integration * Push-to-deploy workflow. * Automatic build and preview on every commit. ##### CI/CD pipelines * Zero downtime deployments. * Automated testing and rollbacks. ##### Example: Deploying a React app in 5 minutes with Appwrite 1. Connect your GitHub repo. 2. Configure build settings. 3. Push your code. 4. Appwrite builds and deploys instantly. #### Free hosting with added features you should consider When comparing free hosting platforms, look beyond just “is it free.” Critical features include: * **SSL Certificates**: Built-in HTTPS for security. * **Custom Domains**: Branding for professional apps. * **Database + API Hosting**: For full-stack projects. * **Authentication**: User management out of the box. * **Serverless Functions**: Extending apps without managing servers. #### Side-by-Side Comparison of Free Hosting Platforms | Platform | Free Tier Limits | Best For | Custom Domains | Backend/API Support | Upgrade Path | | ------------------ | ------------------------------- | ------------------ | -------------- | ------------------- | ----------------- | | **Appwrite** | Generous full stack hosting | Full-stack apps | Yes | Yes | Scale plan | | **Vercel** | 125 GB bandwidth/month | React/Next.js | Yes | Limited (functions) | Pro & Enterprise | | **Netlify** | 125k requests, 100 GB bandwidth | Jamstack sites | Yes | Functions | Business tier | | **Firebase** | 1 GB storage, 10 GB transfer | SPAs/PWAs | Yes | Yes | Blaze plan | | **Render/Railway** | Free static + backend hosting | Backend + frontend | Yes | Yes | Usage-based tiers | #### How to pick the right hosting for your team ##### For startups * Prioritize free tiers that scale easily (Appwrite, Netlify, Vercel). * Avoid lock-in by choosing open-source or flexible platforms. ##### For agencies * Look for multi-project hosting. * Git-based workflows and CI/CD integrations are critical. ##### For enterprises * Focus on compliance, SLAs, and reliability. * Likely to use AWS, GCP, or Azure — but Appwrite offers on-prem or cloud flexibility. #### Conclusion & recommendations In 2025, the **best free hosting platforms** combine performance, scalability, and developer experience. * **Appwrite**: Best for full stack developers who need both frontend and backend in one platform. * **Vercel**: Best for Next.js and frontend-first projects. * **Netlify**: Best for Jamstack and static sites. * **Firebase**: Best for mobile-first teams in the Google ecosystem. * **Render/Railway**: Best for balanced frontend + backend deployment. If you’re building a SaaS MVP, managing multiple client projects, or running a growing startup, **Appwrite provides the best mix of free hosting + frontend deployment + backend APIs** — all without lock-in. #### Frequently Asked Questions (FAQs) **What is the best free hosting platform in 2025?** Appwrite, Vercel, and Netlify are the top contenders. Appwrite stands out for full-stack projects, while Vercel and Netlify dominate frontend hosting. **Can I host both frontend and backend for free?** Yes. Platforms like Appwrite, Firebase, and Railway allow you to host both frontend apps and backend services for free tiers. **Which hosting is best for Next.js or React projects?** Vercel is the best fit for Next.js, while Netlify and Appwrite also support React and other frontend frameworks. **Is free hosting reliable for production apps?** Free hosting is excellent for MVPs, testing, and early-stage startups. For production workloads, it’s best to upgrade to a paid plan for better reliability, SLAs, and scaling. **How does Appwrite compare to Firebase, Netlify, and Vercel?** * Firebase: tightly coupled with Google’s ecosystem. * Netlify: great for Jamstack. * Vercel: ideal for Next.js. * Appwrite: combines web hosting plus backend services, giving developers more flexibility. --- ## Free Next.js hosting with Appwrite Sites - Deploy and scale with ease https://appwrite.io/blog/post/free-nextjs-hosting Next.js is widely adopted for building modern web applications due to its ability to handle server-side rendering (SSR), static site generation (SSG), and API routes efficiently. While deploying Next.js applications can be straightforward, finding a reliable and cost-effective hosting solution with built-in scalability and developer-friendly features can be challenging. [Appwrite Sites](/products/sites) provides a hosting environment tailored for modern web applications, including Next.js, with built-in features designed to optimize performance, security, and collaboration. This article explores how Appwrite Sites enhances the deployment process, integrates with Next.js capabilities, and compares to other hosting solutions. ### How Appwrite Sites enhances Next.js deployments #### 1. Hosting without cost barriers Hosting costs can be a deciding factor for individual developers and startups. Appwrite Sites offers free hosting, removing an initial financial barrier while maintaining a robust infrastructure. This allows developers to test, iterate, and deploy applications without worrying about upfront expenses while providing a scalable pathway for growing projects and grow as they require. #### 2. Optimized for static and server-side rendering (SSR) Next.js supports multiple rendering strategies, including SSR and SSG. Appwrite Sites accommodates both, ensuring that static sites benefit from global caching while SSR applications can dynamically generate content as needed. This flexibility enables use cases ranging from blogs and documentation sites to complex web applications requiring real-time content updates. Additionally, Appwrite's **serverless functions** can be used to enhance SSR applications with dynamic capabilities. #### 3. Performance-driven global CDN Application speed significantly impacts user experience and search engine rankings. Appwrite Sites uses a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve static assets efficiently. This setup reduces latency and improves load times, ensuring applications perform optimally regardless of the user's location. #### 4. Built-in DDoS protection and security Security is a critical concern for web applications, especially as they scale. Appwrite Sites includes built-in [DDoS protection](/docs/products/network/ddos), safeguarding applications from denial-of-service attacks by automatically detecting and mitigating malicious traffic. This ensures uptime and stability without requiring additional security configurations from developers. Additionally, Appwrite Sites integrates **custom domain management**, allowing users to host applications on their own domains with SSL encryption included. {% call_to_action title="Host your Next.js app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Deployment previews for enhanced collaboration Appwrite Sites integrates into your development workflow by generating **deployment preview links** for every update. These previews allow you to share changes with your team before pushing to production, improving collaboration and ensuring quality control throughout the development process. This makes it easier to review and test features in staging environments before releasing them to users. #### 6. Multiple deployment options Appwrite Sites provides multiple ways to deploy your Next.js application: - [Git integration](/docs/products/sites/deploy-from-git): Connect a Git repository to automatically deploy changes when you push to your main branch. - **CLI deployment**: Use the Appwrite CLI to deploy manually with a single command: ```bash appwrite deploy site ``` - **Manual deployment**: Upload your static files directly via the Appwrite Console. Additionally, Appwrite provides a set of **starter templates** and example projects to help you get started when you sign up for **Appwrite Cloud**, allowing developers to quickly bootstrap their applications with minimal configuration. #### 7. Integrated backend services for dynamic applications Many Next.js applications rely on authentication, databases, and cloud functions. Appwrite Sites integrates natively with Appwrite's backend services, enabling seamless authentication (via Appwrite Auth), database management, and function execution without requiring external third-party services. This reduces dependencies and simplifies backend configuration, making it easier to build full-stack applications. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static exports | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No third-party | No third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other solutions that rely on third-party integrations for authentication, databases, or serverless functions, Appwrite Sites provides these natively, reducing reliance on external services and ensuring better integration within a unified ecosystem. For developers exploring an end-to-end solution that minimizes external dependencies while optimizing performance, Appwrite Sites presents a practical option. Explore more with our [Next.js Quick Start Guide](/docs/quick-starts/nextjs) and start deploying your Next.js applications by visiting [Appwrite Sites](/products/sites). --- ## Free Nuxt hosting with Appwrite Sites - Deploy and scale effortlessly https://appwrite.io/blog/post/free-nuxt-hosting Nuxt.js is a powerful framework built on Vue.js that simplifies the development of modern web applications with features like server-side rendering (SSR), static site generation (SSG), and API integrations. While deploying Nuxt applications can be straightforward, selecting the right hosting provider that balances cost, performance, and ease of use can be challenging. [Appwrite Sites](/products/sites) provides an optimized hosting environment tailored for modern web applications, including Nuxt, with built-in tools for security, scalability, and developer-friendly deployment workflows. This article explores how Appwrite Sites simplifies Nuxt hosting, enhances performance, and compares with other solutions. ### Why Appwrite Sites is the perfect hosting solution for Nuxt #### 1. Free hosting with no upfront costs For developers and startups, hosting expenses can be a concern. Appwrite Sites provides free hosting, allowing developers to test, iterate, and launch their Nuxt applications without financial barriers. As your project scales, Appwrite offers seamless upgrade options to accommodate growing traffic and resources. #### 2. Supports static and server-side rendered Nuxt applications Nuxt's flexibility allows developers to generate fully static sites or use SSR for dynamic applications. Appwrite Sites supports both models: - **Static Generation (SSG)**: Pre-render pages at build time for fast, cached delivery. - **Server-Side Rendering (SSR)**: Leverage **Appwrite Functions** to handle server-side logic, dynamic API calls, and real-time data rendering. This flexibility ensures that Nuxt applications of any complexity can be hosted efficiently on Appwrite Sites. #### 3. Global CDN for fast performance Performance plays a key role in user experience and SEO. Appwrite Sites uses a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve Nuxt applications efficiently. This reduces latency and ensures optimal performance for users across different locations. #### 4. Built-in security and DDoS protection Security is a major concern when deploying web applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), safeguarding your Nuxt app from malicious traffic and potential downtime. Additionally, it offers **custom domain management with automatic SSL encryption**, ensuring secure communication between users and your application. {% call_to_action title="Host your Nuxt app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for improved collaboration Appwrite Sites generates **deployment preview links** for every update, enabling teams to review and test changes in a staging environment before pushing them to production. This workflow enhances collaboration, making it easier to validate updates before they go live. #### 6. Multiple deployment options Deploying a Nuxt application on Appwrite Sites is simple and flexible: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy changes when you push to your repository. - **CLI-based deployment**: Deploy manually using the Appwrite CLI: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static files directly through the Appwrite Console. Additionally, Appwrite provides **starter templates** to help developers get up and running quickly with minimal setup. #### 7. Integrated backend services for full-stack Nuxt applications Nuxt applications often require authentication, databases, and server-side logic. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, eliminating the need for third-party backend services and simplifying full-stack development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | No | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting providers that require third-party integrations for backend services, Appwrite Sites offers an all-in-one platform with built-in authentication, databases, and serverless functions, making deployment and management easier. For developers seeking an efficient, high-performance hosting solution optimized for Nuxt applications, Appwrite Sites is an excellent choice. Explore more with our [Nuxt Quick Start Guide](/docs/quick-starts/nuxt) and start deploying your Nuxt applications by visiting [Appwrite Sites](/products/sites). --- ## Free React hosting with Appwrite Sites - Deploy and scale effortlessly https://appwrite.io/blog/post/free-react-hosting React is one of the most popular JavaScript libraries for building dynamic, interactive web applications. While deploying React apps is generally straightforward, choosing the right hosting platform that balances cost, performance, and developer-friendly features can be a challenge. [Appwrite Sites](/products/sites) provides an optimized hosting environment tailored for modern web applications, including React, with built-in features that enhance performance, security, and deployment workflows. This article explores how Appwrite Sites simplifies React deployment, improves efficiency, and compares with other hosting solutions. ### Why Appwrite Sites is an excellent choice for React developers #### 1. Free hosting with no upfront costs For developers and small teams, minimizing hosting expenses is essential. Appwrite Sites offers free hosting, removing financial barriers while ensuring a robust infrastructure. As projects grow, Appwrite provides an easy upgrade path to accommodate increased demands. #### 2. Optimized for static and dynamic React apps React applications can be built as fully static sites or dynamically rendered apps using frameworks like Next.js. Appwrite Sites supports both approaches, allowing static exports to leverage global caching while enabling server-side functionality through **Appwrite Functions**, making it easy to add API endpoints or dynamic content generation. #### 3. Performance-focused global CDN Application speed significantly impacts user experience and SEO. Appwrite Sites leverages a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to ensure React apps load quickly, reduce latency, and deliver a smooth browsing experience worldwide. #### 4. Built-in security and DDoS protection Security is a crucial consideration for production applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), preventing attacks that could compromise your app's availability. Additionally, it offers **custom domain support with automatic SSL encryption**, ensuring secure communication with users. {% call_to_action title="Host your React app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for seamless collaboration Every deployment on Appwrite Sites generates a **preview link**, making it easy to share, review, and test changes before pushing them to production. This workflow simplifies collaboration and enhances the quality assurance process. #### 6. Multiple deployment options Appwrite Sites provides several flexible methods for deploying React applications: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy updates when changes are pushed to the main branch. - **CLI-based deployment**: Deploy manually with a simple command: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static assets directly via the Appwrite Console. Additionally, Appwrite provides **starter templates** and example projects to help developers get up and running with minimal setup. #### 7. Integrated backend services for full-stack React applications Many React applications require authentication, databases, and serverless functions. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, enabling developers to build full-stack applications without relying on third-party services. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support (via Functions) | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting solutions that depend on third-party integrations for backend functionality, Appwrite Sites provides an all-in-one solution with built-in authentication, databases, and serverless functions, reducing complexity and improving performance. For developers looking for a hassle-free, high-performance hosting solution tailored for React applications, Appwrite Sites is an excellent choice. Explore more with our [React Quick Start Guide](/docs/quick-starts/react) and start deploying your React apps by visiting [Appwrite Sites](/products/sites). --- ## Free React Native for Web hosting with Appwrite Sites - Simplified deployment and scalability https://appwrite.io/blog/post/free-react-native-hosting React Native for Web enables developers to build cross-platform applications using a single codebase, allowing React Native apps to run seamlessly on web browsers. While this provides a powerful way to extend mobile applications to the web, finding a hosting solution that is cost-effective, scalable, and optimized for React Native for Web can be challenging. [Appwrite Sites](/products/sites) offers a dedicated hosting platform for modern web applications, including React Native for Web, with built-in features that optimize performance, security, and developer workflows. This article explores how Appwrite Sites enhances the deployment process, integrates with React Native for Web's architecture, and compares with other hosting solutions. ### Why Appwrite Sites is an ideal choice for React Native for Web #### 1. Free hosting with no upfront costs For startups and independent developers, hosting costs can be a major concern. Appwrite Sites provides free hosting, eliminating financial barriers while maintaining high performance. As your project scales, Appwrite allows you to upgrade seamlessly without any downtime. #### 2. Optimized for web-based React Native applications React Native for Web brings mobile-style components and layouts to the browser. Appwrite Sites ensures smooth rendering, asset optimization, and fast content delivery, making it an ideal fit for applications that rely on React Native's component-driven architecture. #### 3. Performance-enhancing global CDN Speed is critical for both user experience and SEO rankings. Appwrite Sites uses a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve assets efficiently, reducing latency and ensuring optimal performance no matter where users are located. #### 4. Built-in security and DDoS protection Security is a top priority for web applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), safeguarding applications from denial-of-service attacks by mitigating malicious traffic. Additionally, it provides **custom domain support with automatic SSL encryption**, ensuring a secure browsing experience for users. {% call_to_action title="Host your React Native app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for smoother collaboration Every update to your site generates a **deployment preview link**, making it easy to review changes before going live. This feature streamlines development workflows, allowing developers and stakeholders to verify updates in a staging environment before deploying to production. #### 6. Flexible deployment methods Appwrite Sites provides multiple ways to deploy your React Native for Web application: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy updates when pushing changes to your repository. - **CLI-based deployment**: Deploy manually with a single command: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload your compiled build files directly through the Appwrite Console. Appwrite also provides **starter templates** and example projects to help developers get up and running quickly with minimal configuration. #### 7. Integrated backend services for full-stack React Native applications Many React Native for Web applications require authentication, databases, and cloud functions. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, eliminating the need for third-party backend providers and streamlining development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | Web-optimized React Native | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting solutions that require third-party integrations for backend features, Appwrite Sites provides an all-in-one ecosystem with built-in authentication, databases, and serverless functions, reducing complexity and ensuring seamless integration. For developers looking for a streamlined hosting solution that simplifies deployment and minimizes external dependencies, Appwrite Sites is an excellent choice. Explore more with our [React Native Quick Start Guide](/docs/quick-starts/react-native) and start deploying your React Native for Web applications by visiting [Appwrite Sites](/products/sites). --- ## Free Remix hosting with Appwrite Sites - Deploy and scale seamlessly https://appwrite.io/blog/post/free-remix-hosting Remix is a modern full-stack web framework designed to optimize user experience through server-side rendering (SSR), progressive enhancement, and efficient data loading. While deploying Remix applications is straightforward, finding a hosting platform that balances cost, performance, and developer-friendly features can be challenging. [Appwrite Sites](/products/sites) provides an optimized hosting solution tailored for modern web applications, including Remix, with built-in tools for security, scalability, and flexible deployment workflows. This article explores how Appwrite Sites enhances Remix hosting, simplifies development, and compares to other hosting providers. ### Why Appwrite Sites is a great hosting solution for Remix #### 1. Free hosting with no upfront costs For independent developers and startups, keeping hosting expenses low is essential. Appwrite Sites offers free hosting, removing financial barriers while providing a scalable infrastructure. As your application grows, Appwrite ensures a smooth upgrade path to meet increasing traffic demands. #### 2. Full support for static and server-side Remix applications Remix enables both static and server-side rendering, making it suitable for various use cases. Appwrite Sites supports: - **Static exports**: Deliver pre-rendered pages with global caching for instant load times. - **Server-side rendering (SSR)**: Use **Appwrite Functions** to handle API requests, server-side data fetching, and real-time updates seamlessly. This flexibility makes Appwrite Sites a powerful hosting option for any Remix application. #### 3. Performance-focused global CDN Speed plays a crucial role in user engagement and SEO rankings. Appwrite Sites utilizes a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to efficiently cache and serve your Remix application, ensuring fast load times across all regions. #### 4. Built-in security and DDoS protection Security is a priority for production applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), helping to mitigate malicious traffic and prevent downtime. Additionally, **custom domain management with automatic SSL encryption** ensures secure communication between users and your application. {% call_to_action title="Host your Remix app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for enhanced collaboration Every update deployed to Appwrite Sites generates a **preview link**, allowing developers and teams to review and test changes in a staging environment before pushing them to production. This simplifies the workflow and improves quality assurance. #### 6. Multiple deployment options Deploying a Remix application on Appwrite Sites is flexible and straightforward: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy changes when pushing updates to your repository. - **CLI-based deployment**: Deploy manually using the Appwrite CLI: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static assets directly through the Appwrite Console. Additionally, Appwrite provides **starter templates** to help developers get started quickly with minimal configuration. #### 7. Integrated backend services for full-stack Remix applications Remix applications often require authentication, databases, and backend functionality. Appwrite Sites integrates seamlessly with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, removing the need for third-party backend services and simplifying full-stack development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | No | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare Workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting platforms that require external services for backend functionality, Appwrite Sites offers a fully integrated solution with built-in authentication, databases, and serverless functions, making Remix deployments more efficient and scalable. For developers looking for an all-in-one hosting solution tailored for Remix, Appwrite Sites is a perfect choice. Start deploying your Remix applications today by visiting [**Appwrite Sites**](/products/sites). --- ## Free Svelte and SvelteKit hosting with Appwrite Sites - Deploy and scale effortlessly https://appwrite.io/blog/post/free-svelte-and-sveltekit-hosting Svelte and SvelteKit have gained traction among developers due to their efficient compilation, minimal runtime overhead, and built-in support for server-side rendering (SSR) and static site generation (SSG). While deploying Svelte applications is straightforward, choosing a hosting solution that balances affordability, scalability, and developer-friendly features can be a challenge. [Appwrite Sites](/products/sites) provides a streamlined hosting experience tailored for modern web applications, including Svelte and SvelteKit. It offers features designed to enhance performance, security, and team collaboration. This article explores how Appwrite Sites simplifies deployment, integrates with Svelte's capabilities, and compares to other hosting options. ### Why Appwrite Sites is ideal for Svelte and SvelteKit #### 1. Free hosting without limitations For individual developers and startups, hosting costs can be a significant factor. Appwrite Sites removes this barrier by offering free hosting, allowing you to launch, test, and iterate on projects without upfront expenses. As your application scales, Appwrite provides a seamless path to grow alongside it. #### 2. Optimized for both static and server-side rendering SvelteKit supports SSR and SSG, making it versatile for various use cases. Appwrite Sites accommodates both approaches, ensuring static sites benefit from fast global caching while SSR applications dynamically generate content when required. This flexibility allows developers to build anything from blogs to complex web applications with real-time content. Additionally, **Appwrite Functions** can add dynamic functionality to SSR applications. #### 3. Speed-enhancing global CDN Fast load times are crucial for user experience and SEO. Appwrite Sites leverages a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve static assets efficiently, reducing latency and ensuring optimal performance no matter where your users are located. #### 4. Built-in security and DDoS protection Security is paramount for modern web applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), safeguarding applications against attacks by monitoring and mitigating malicious traffic. It also offers **custom domain management** with built-in SSL encryption, ensuring secure connections without additional configuration. {% call_to_action title="Host your Svelte app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 5. Preview deployments for smoother collaboration Every update to your site generates a **deployment preview link**, making it easy to share and review changes before going live. This feature streamlines teamwork, allowing developers and stakeholders to validate updates in a staging environment before final deployment. #### 6. Multiple deployment options Deploying your Svelte or SvelteKit application with Appwrite Sites is simple and flexible: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy updates when pushing changes to your main branch. - **CLI-based deployment**: Deploy manually with a single command: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static assets directly via the Appwrite Console. Appwrite also provides **starter templates** and example projects to help you get up and running quickly with minimal setup. #### 7. Integrated backend services for dynamic applications Many Svelte applications require authentication, databases, and cloud functions. Appwrite Sites natively integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, eliminating the need for external backend services and simplifying development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | Deprecated | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other platforms that depend on third-party integrations for authentication, databases, and serverless functions, Appwrite Sites provides these services natively, reducing complexity and ensuring tighter integration. For developers looking for a streamlined hosting solution that minimizes dependencies while delivering excellent performance, Appwrite Sites is a compelling choice. Explore more with our [SvelteKit Quick Start Guide](/docs/quick-starts/sveltekit) and start deploying your Svelte applications by visiting [Appwrite Sites](/products/sites). --- ## Free Vue.js hosting with Appwrite Sites - Deploy and scale effortlessly https://appwrite.io/blog/post/free-vuejs-hosting Vue.js is a popular JavaScript framework for building interactive and scalable web applications, offering a balance of simplicity and performance. While deploying Vue applications is straightforward, choosing the right hosting platform that offers cost-effective scalability, security, and developer-friendly features can be a challenge. [Appwrite Sites](/products/sites) provides an optimized hosting environment tailored for modern web applications, including Vue.js, with built-in performance enhancements, security measures, and flexible deployment options. This article explores how Appwrite Sites streamlines Vue hosting, enhances development workflows, and compares to other hosting solutions. ### Why Appwrite Sites is the perfect hosting solution for Vue.js #### 1. Free hosting with no upfront costs For developers and startups, hosting expenses can be a concern. Appwrite Sites provides free hosting, allowing you to build, test, and deploy Vue applications without financial barriers. As your project scales, Appwrite offers seamless upgrade options to accommodate growing traffic and resource demands. #### 2. Supports both static and dynamic Vue applications Vue applications can be deployed as static sites or as dynamic apps with server-side rendering (SSR). Appwrite Sites supports both models: - **Static Vue apps**: Leverage **global caching** to deliver blazing-fast performance for Single Page Applications (SPAs). - **Server-side rendering (SSR)**: Use **Appwrite Functions** to manage backend processes and dynamic content updates efficiently. This flexibility ensures Vue applications of any complexity can be hosted efficiently on Appwrite Sites. #### 3. Performance-focused global CDN Performance is crucial for web applications, influencing both user experience and SEO rankings. Appwrite Sites utilizes a globally distributed [Content Delivery Network (CDN)](/docs/products/network/cdn) to cache and serve Vue applications efficiently, reducing latency and improving page load times worldwide. {% call_to_action title="Host your Vue.js app for free with Appwrite Sites" description="Benefit from a complete platform to develop, deploy, and build your websites and web apps." point1="Open source and no vendor lock-in" point2="Built-in security and DDoS protection" point3="Fully managed cloud solution" point4="Global CDN for improved performance" cta="Get started for free" url="https://cloud.appwrite.io/" /%} #### 4. Built-in security and DDoS protection Security is a priority when deploying web applications. Appwrite Sites includes [DDoS protection](/docs/products/network/ddos), safeguarding Vue applications from malicious traffic and potential downtime. Additionally, it offers **custom domain support with automatic SSL encryption**, ensuring secure communication between users and your application. #### 5. Preview deployments for enhanced collaboration Every deployment on Appwrite Sites generates **preview links**, allowing developers and stakeholders to test changes before they go live. This improves collaboration and ensures thorough testing before final production deployment. #### 6. Multiple deployment options Deploying a Vue application on Appwrite Sites is flexible and straightforward: - [Git-based deployments](/docs/products/sites/deploy-from-git): Automatically deploy changes when pushing to your repository. - **CLI-based deployment**: Deploy manually using the Appwrite CLI: ```bash appwrite deploy site ``` - **Manual file uploads**: Upload static files directly through the Appwrite Console. Additionally, Appwrite provides **starter templates** and example projects to help developers get up and running quickly with minimal setup. #### 7. Integrated backend services for full-stack Vue applications Many Vue applications require authentication, databases, and backend logic. Appwrite Sites seamlessly integrates with **Appwrite Auth**, **Appwrite Databases**, and **Appwrite Functions**, reducing the need for third-party backend providers and simplifying full-stack development. ### How Appwrite Sites compares to other hosting platforms | Feature | Appwrite Sites | Vercel | Netlify | | --- | --- | --- | --- | | Free hosting | Yes | Yes | Yes | | Static site support | Yes | Yes | Yes | | SSR support | Yes | Yes | Yes | | Global CDN | Yes | Yes | Yes | | DDoS protection | Yes | Yes | Yes | | Deployment previews | Yes | Yes | Yes | | Custom domains | Yes | Yes | Yes | | Authentication | Appwrite Auth | No | No | | Database | Appwrite Databases | No Third-party | No Third-party | | Serverless functions | Appwrite Functions | Cloudflare workers | AWS Lambda | | Storage | Appwrite Storage | Cloudflare R2 | No | | Cloud messaging | Appwrite Messaging | No | No | | Realtime database | Appwrite Realtime | No | No | Unlike other hosting platforms that require third-party integrations for backend functionality, Appwrite Sites provides an all-in-one solution with built-in authentication, databases, and serverless functions, making Vue.js deployments easier and more efficient. For developers looking for a cost-effective, high-performance hosting solution tailored for Vue applications, Appwrite Sites is an excellent choice. Explore more with our [Vue Quick Start Guide](/docs/quick-starts/vue) and start deploying your Vue applications by visiting [Appwrite Sites](/products/sites). --- ## From student to developer - How open source can launch your career https://appwrite.io/blog/post/from-student-to-developer-how-open-source-can-launch-your-career You might still be a student, still learning, or still wondering where you belong in tech. You scroll through GitHub, see contributors building amazing things, and it feels like they are miles ahead. But here is the truth: **every great developer you admire once made their first pull request too.** And that first contribution was not their end point. It was their beginning. ### What Is Hacktoberfest? Hacktoberfest is a month-long celebration of open source. Each October, developers from all over the world make their first contributions, fixing bugs, improving documentation, or adding new features to projects that anyone can use. For many, it is their first step into real-world collaboration. It is how they gain hands-on experience, connect with other developers, and start building the foundation of their careers. But whether you joined Hacktoberfest or contributed to an open source project on your own, the message is the same: **you have already started your journey.** ### This is the start of your story Your first pull request, no matter how small, proves something powerful. You can collaborate, communicate, and contribute to something bigger than yourself. My story started with open source back in 2011-2012, where I joined a community and helped out in different areas. This has led me to endless opportunities and helped me land jobs. That is what open source really gives you: - Real-world teamwork in global, asynchronous environments - Peer reviews and mentorship from experienced engineers - Communication practice through documentation and issue discussions - A public record of your progress and growth Every contribution, even one, builds credibility. It is your foundation. The practice of collaboration with global teams in open source helped me with personal growth and improved my communication skills that prepared me for professional careers. Without open source, I don't know where I'd be now. ### Quality > Quantity As someone who has interviewed developers, I do not care how many PRs you have made. I care about *what they show.* One thoughtful, well-written contribution tells me: - You understand the project context - You respect guidelines and feedback - You collaborate professionally in reviews - You care about the craft of software AI tools can assist you, but your thought process, curiosity, and voice are what make your work stand out. Don't get me wrong, AI is a HUGE help and can write better code than experienced engineers. But make sure you understand what it's doing. **That is what employers notice.** ### Your GitHub is your portfolio Do not just treat GitHub as a storage space. It is your portfolio. Curate it like one. Add open source work to your resume or LinkedIn profile. Be specific: > Improved database query efficiency by 30% through indexing optimization. A clean, consistent GitHub profile says more about your skills than any list of buzzwords. Visibility matters. Your next opportunity might begin with someone reviewing your code. ### Learn and build in public Share your journey online, not just your wins but what you learned. Even a simple post like: > I just merged my first pull request! Here is what I learned about React hooks. This is not self-promotion. It is reflection. It helps others who are starting out feel less alone, and it helps future employers see your growth mindset. ### Grow beyond code Writing great code is important, but so is helping others write theirs. Try: - Reviewing pull requests from new contributors - Writing documentation or tutorials - Answering questions in community chats These are leadership skills in disguise. And the same qualities that make someone a great open source contributor, empathy, clarity, and initiative, make them a great teammate too. ### Keep going Whether you joined Hacktoberfest or just submitted your first contribution this week, do not stop now. Your journey has already begun. Keep showing up. Keep learning. Keep building. Because open source is not just about code. It is about people, opportunity, and growth. And the next great creator, maintainer, or innovator might be **you.** ### Watch my talk If you want to go deeper into this topic, you can watch the Hacktoberfest Closing Ceremony talk where I also share my journey, **“How Open Source Can Help You Build Your Career,”** here: 🎥 [Watch on YouTube](https://www.youtube.com/watch?v=_5EN8hHVRvQ) --- ## Chat with your favorite fictional character using OpenAI and Appwrite Functions https://appwrite.io/blog/post/function-chat-fictional-character Have you ever wondered what it would feel like to interact with your favorite fictional characters, such as Superman, Hermione Granger, Gandalf, or Snow White? As a part of an internal hackathon at Appwrite recently, my team developed an Appwrite Function that you can use to chat with any popular fictional character you like (we really wanted to talk to Batman!) In this blog, let’s learn how you can build this Appwrite Function using OpenAI’s GPT-4 API. ![Prototype of Bruce Wayne chat](/images/blog/function-chat-fictional-character/prototype.png) ### Setting up the OpenAI platform To develop this project, you first need an OpenAI API Key, for which you must create an account on the [OpenAI platform](https://platform.openai.com/). Once your account is set up, visit their [API keys](https://platform.openai.com/account/api-keys) page and create an API Key. Ensure you copy and save this key in a safe place, as the OpenAI platform will not let you view the key after it is created. ![OpenAI API Keys](/images/blog/function-chat-fictional-character/openai.png) > Note: To use the GPT-4 API, your account must be upgraded to the Usage tier 1. To learn more, visit their [Usage tiers documentation](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-one). ### Initializing the Appwrite Function Now that we have our OpenAI API Key, let us get the function ready on [Appwrite](https://cloud.appwrite.io/). Head over to your Appwrite project and visit the Functions page. From there, we will use the Node.js starter template and create a function. ![Appwrite Functions](/images/blog/function-chat-fictional-character/functions.png) Once the function is ready, we must visit the Settings tab on the Function page and add the following environment variables: - `OPENAI_API_KEY`: API Key from our OpenAI account - `OPENAI_MAX_TOKENS`: Maximum number of tokens that the OpenAI response should contain (we’ll set this as `512`) Once that is done, visit the function’s GitHub repository and clone the project. ### Developing the function logic To develop the function, we must first install the `openai` npm package. Open your terminal in the project directory and run the following command: ```bash npm i openai ``` Once that is done, visit the `src/main.js` file and replace the entire code with the following: ```js import { OpenAI } from 'openai'; export default async ({ req, res, log, error }) => { try { const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); var prompt = `You are ${req.body.character}.\nRespond to the following question in first-person: ${req.body.question}\n${req.body.additionalPrompt}` const response = await openai.chat.completions.create({ model: 'gpt-4', max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: prompt }], }); const completion = response.choices[0].message?.content; log(completion); return res.json({ ok: true, answer: completion }, 200); } catch (err) { error(err.message); return res.json({ ok: false, error: err.message }, 500); } }; ``` This function will accept the name of a character, the question from a user, and any additional prompt you might optionally like to give. For example, in our hackathon project, we wanted to interact with Bruce Wayne and ensure that his Batman alter-ego was not directly given away, so here’s what our inputs looked like: | Character name | Question | Additional prompt | | --- | --- | --- | | Bruce Wayne | Are you Batman? | Ensure that you don't reveal your Batman alter-ego but you can tip-toe around it. | ### Testing the function Once you’ve completed all the aforementioned steps, you can push the code to the generated GitHub repository, at which point Appwrite Cloud will automatically deploy the changes to your function. You can test the function by sending it a cURL request from your terminal or any other API testing client. ```bash curl --location '' \ --header 'Content-Type: application/json' \ --data '{ "character": "Bruce Wayne", "question": "Are you Batman?", "additionalPrompt": "Ensure that you don'\''t reveal your Batman alter-ego but you can tip-toe around it." }' ``` ![Thunder Client on VS Code](/images/blog/function-chat-fictional-character/http.png) ### Next steps And with that, our fictional character chat function is ready! If you liked this project and/or want to investigate the function code, visit the [GitHub repository](https://github.com/adityaoberai/CharacterChat). For more information about Appwrite Functions, visit the following resources: - [Appwrite Function Docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Build an intelligent chatbot with ChatGPT and Appwrite Functions https://appwrite.io/blog/post/function-template-prompt-chatgpt Function templates are pre-built Appwrite Functions that can be integrated into your Appwrite project with just a few clicks. Using them, you can easily incorporate new features and integrations into your app without writing additional code or managing infrastructure. One such integration you can implement using Appwrite Functions is an **intelligent chatbot** using **ChatGPT**. In this blog, you will learn how you can use an Appwrite Function Template to create an interactive bot that will answer any of your questions using OpenAI’s GPT-3.5 API. #### Setting up the OpenAI Platform To get an OpenAI API Key, you must create an account on the [OpenAI platform](https://platform.openai.com/). Once your account is set up, visit their [API keys](https://platform.openai.com/account/api-keys) page and create an **API Key**. Ensure you copy and save this key in a safe place, as the OpenAI platform will not let you view the key after it is created. ![OpenAI Platform](/images/blog/function-template-prompt-chatgpt/openai-api-keys.png) #### Preparing the Appwrite Function Now that we have our OpenAI API Key, let us get the function ready on **[Appwrite](https://cloud.appwrite.io)**. Head over to your Appwrite project and visit the **Functions** page. From there, we will select the **Templates** tab, search for and select the **Prompt ChatGPT** function template. ![Function Templates](/images/blog/function-template-prompt-chatgpt/templates.png) This function requires **1 environment variable** to setup: - `OPENAI_API_KEY`: API Key from our OpenAI account After you have configured the environment variables, you must connect your Appwrite account with GitHub, select **Create a new repository** (this will generate a GitHub repository for you with the function), and leave the production branch and root settings as default to create this function. Once the function is ready, visit the **Domains** tab on the function page and copy the domain URL to test the function. ![Function Domain](/images/blog/function-template-prompt-chatgpt/domains.png) #### Testing the Function Once all the aforementioned steps are complete, it is time to test the function! To consume this function, you must send the function URL a `POST` HTTP Request with a **JSON Body** in the following format: ```json { "prompt": "" } ``` If successful, you will receive a response in the following format: ```json { "ok": true, "completion": "" } ``` Here’s a screenshot of a test prompt that was sent to the function using Postman: ![Postman](/images/blog/function-template-prompt-chatgpt/postman.png) #### Next steps We’ve covered the basics, and now it’s your time to shine! With a few changes, you should be able to extend this template to fit your app. Be sure to check out the other available Function Templates. We’ve created many that could be of use in your projects. You can find the [templates GitHub repository here](https://github.com/appwrite/templates). For more information about Appwrite and Appwrite Functions: - [Appwrite Function Docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Functions Announcement](https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f): Read the full announcement on Functions 1.4. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Send WhatsApp messages with Vonage and Appwrite Functions https://appwrite.io/blog/post/function-template-whatsapp-vonage Function templates are pre-built Appwrite Functions that can be integrated into your Appwrite project with just a few clicks. Using them, you can easily incorporate new features and integrations into your app without writing additional code or managing infrastructure. One such integration you can implement using Appwrite Functions is **WhatsApp messaging** via **Vonage**. In this blog, you will learn how you can use an Appwrite Function Template to create a Whatsapp bot that will programmatically respond to incoming WhatsApp messages via Vonage. #### Setting up Vonage The first step is to create a Vonage account. Head over to Vonage’s [website](https://www.vonage.com/) and [sign up](https://dashboard.nexmo.com/sign-up?icid=tryitfree_homepage_nexmodashbdfreetrialsignup_tile&utm_campaign=bizdirect&attribution_campaign=bizdirect&cjregion=429207&adobe_mc=MCMID%3D34270386780739068404296704713035591008%7CMCORGID%3DA8833BC75245AF9E0A490D4D%2540AdobeOrg%7CTS%3D1663266957) to use Vonage Communication APIs. On the Vonage API Dashboard, visit the **[API Settings](https://dashboard.nexmo.com/settings)** tab and copy the **API Key**, **API Secret**, and **Signature Secret** from there. ![Vonage API Dashboard](/images/blog/function-template-whatsapp-vonage/api-settings.png) After that, visit the **[Messages API Sandbox](https://dashboard.nexmo.com/messages/sandbox)** and enable **WhatsApp** to set up a test environment for our Appwrite Function template. Make sure to copy the **Phone Number** provided there (and join the WhatsApp channel with the instructions on the page. Once the Appwrite Function is deployed, we will return here to add the Function URL as the **Inbound Webhook** link. ![Vonage Messages API Sandbox](/images/blog/function-template-whatsapp-vonage/messages-sandbox.png) #### Preparing the Appwrite Function Now that we have all the necessary details from our Vonage account, let us get the function ready on **[Appwrite](https://cloud.appwrite.io)**. Head over to your Appwrite project and visit the **Functions** page. From there, we will select the **Templates** tab, search for and select the **WhatsApp with Vonage** function template. ![Functions Templates](/images/blog/function-template-whatsapp-vonage/templates.png) This function requires **4 environment variables** to setup: - `VONAGE_API_KEY`: API Key from our Vonage account *(copied from the API Settings)* - `VONAGE_API_SECRET`: API Secret from our Vonage account *(copied from the API Settings)* - `VONAGE_API_SIGNATURE_SECRET`: Secret used to sign and verify the JWT token sent by Vonage *(copied from the API Settings)* - `VONAGE_WHATSAPP_NUMBER`: Vonage WhatsApp number to send messages from *(copied from the Messages API Sandbox)* After you have configured the environment variables, you must connect your Appwrite account with GitHub, select **Create a new repository** (this will generate a GitHub repository for you with the function), and leave the production branch and root settings as default to create this function. Once the function is ready, visit the **Domains** tab on the function page and copy the domain to add to the **Inbound Webhook** link on the **Messages API Sandbox** page on your Vonage account. ![Function Domain](/images/blog/function-template-whatsapp-vonage/domains.png) #### Testing the Function Once all the aforementioned steps are complete, it is time to test the function! Open the WhatsApp app on your phone, join the Vonage WhatsApp Channel (via the steps mentioned on the *Messaging API Sandbox* on *Vonage*), and send any message to the WhatsApp number. You shall receive a message in the format: `Hi there! You sent me: ` ![WhatsApp](/images/blog/function-template-whatsapp-vonage/whatsapp.png) #### Next steps We’ve covered the basics, and now it’s your time to shine! With a few changes, you should be able to extend this template to fit your app. Be sure to check out the other available Function Templates. We’ve created many that could be of use in your projects. You can find the [templates GitHub repository here](https://github.com/appwrite/templates). For more information about Appwrite and Appwrite Functions: - [Appwrite Function Docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Functions Announcement](https://dev.to/appwrite/serverless-your-way-unleashing-appwrite-functions-true-potential-2l4f): Read the full announcement on Functions 1.4. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Local serverless function development with the new Appwrite CLI https://appwrite.io/blog/post/functions-local-development-guide What if you could develop serverless functions quickly and effectively without the need for constant cloud deployment? If you've been stuck waiting for deployments or struggling with limited offline capabilities, there's good news. The new Appwrite CLI provides a seamless local development experience for serverless functions, enabling you to develop, test, and iterate rapidly on your functions directly from your local machine. In this guide, we'll walk through setting up and developing Appwrite Functions locally using the new Appwrite CLI. ### Why local development? Local development offers several advantages when working with serverless functions: 1. **Faster iteration:** Developing functions locally allows you to test changes quickly without the need to deploy them to a live environment. This speeds up the development process and enables rapid iteration. 2. **Offline development:** With local development, you can work on your functions even when you're offline. This is especially useful when you're traveling or in areas with limited internet connectivity. 3. **Debugging:** Local development provides better debugging capabilities, allowing you to troubleshoot issues more effectively before deploying your functions to a live environment. This can help you avoid catastrophic errors by identifying and fixing problems early, without the risk of affecting your live application. 4. **Cost efficiency:** Running functions locally helps keep development setup costs low, as you don't need to continuously deploy functions to a cloud environment for testing. Let's see how to set up and develop Appwrite Functions locally using the new CLI which we’ll reveal more of tomorrow. ### Installing Appwrite CLI Start by installing the latest version of the Appwrite CLI: ```bash npm install -g appwrite-cli@latest ``` ### Setting up your project Before developing functions, you need to set up a project. First, log in to your Appwrite account using the CLI: ```bash appwrite login ``` If you’ve already worked with the Appwrite CLI, you can use `appwrite whoami` to check if you are signed in. This command will display your account details. Next, initialize your project in the directory where you want to develop your function: ```bash appwrite init project ``` You will be prompted with options to either create a new project or link to an existing one: ```bash How would you like to start? (Use arrow keys) ❯ Create new project Link directory to an existing project ``` If you choose "Create new project," you'll need to select your organization, name your project, and give it an ID. ### Initializing functions With your project set up, you can now initialize your functions. Run the following command: ```bash appwrite init functions ``` You'll be asked to provide details about your function like its name, ID, and runtime: ```bash ? What would you like to name your function? New Awesome Function ? What ID would you like to have for your function? new-awesome-function ? What runtime would you like to use? (Use arrow keys) ❯ Node.js (node-16.0) Node.js (node-18.0) PHP (php-8.0) Ruby (ruby-3.0) Python (python-3.9) Python (ML) (python-ml-3.11) Dart (dart-2.17) ``` The CLI will generate the function's code in a directory within your project. This directory will be named after the function name you provided. You can open it to see the source code and start developing your function. Appwrite commands need your appwrite.config.json file to work, which is located in the root directory of your project. However, if you run Appwrite commands from the function directory, the CLI will automatically look for the `appwrite.config.json` file in the parent directories. So, commands from the function directory should work without any issues. This way, you don't have to worry about switching directories when you need to do commands like `npm install` (which needs to be done in the function directory) or `appwrite deploy` (which needs the `appwrite.config.json` file). ### Developing locally You can start developing and testing your function locally. To do this, ensure you have Docker installed on your system. You can download Docker from [here](https://www.docker.com/products/docker-desktop). Next, run the following command to start your function locally: ```bash appwrite run function ``` You'll be prompted to select the function you want to run locally: ```bash appwrite run function ? Which function would you like to develop locally? ``` To avoid this question repeatedly, you can specify the function id directly: ```bash appwrite run function --function-id=YOUR_FUNCTION_ID ``` Select the function you want to run, and the CLI will start the local server. You'll see a message like this: ```bash Build finished. ℹ Info Starting function using Docker ... ℹ Info Permissions, events, CRON and timeouts don't apply when running locally. ℹ Info 💡 Hint: Function automatically restarts when you edit your code. ✓ Success Visit http://localhost:3000/ to execute your function. ``` By default, the server runs on port 3000, but it will use the first available port above that if 3000 is occupied. You can customize the port using the `--port` flag: ```bash appwrite run function --port=4000 ``` The command will keep running, watching for any code changes and restarting the function accordingly. If you want to prevent this behavior, you can run: ```bash appwrite run function --no-reload ``` ### Environment variables By default, environment variables from your remote instance won't be loaded during local development. This is to prevent any unintended usage of remote configuration. To include these variables, use the `--with-variables` flag: ```bash appwrite run function --with-variables ``` Additionally, environment variables can also be read from a `.env` file in your function folder during local development. ### Impersonating users You can impersonate a user during local function executions to automatically set the correct values for `x-appwrite-user-id` and `x-appwrite-user-jwt` headers: ```bash appwrite run function --user-id="YOUR_USER_ID" ``` ### Conclusion Local development with the new Appwrite CLI provides a seamless experience for developing and testing serverless functions. By following the steps outlined in this guide, you can set up your project, initialize functions, and start developing locally with ease. This approach allows you to iterate quickly, debug effectively, and develop functions offline, enhancing your overall development workflow. ### More resources: - [Appwrite Functions Documentation](https://appwrite.io/docs/functions) - [Join the Appwrite Community on Discord](https://appwrite.io/discord) - [More about Init](https://appwrite.io/init) --- ## GDPR compliance for mobile apps: a developer’s guide https://appwrite.io/blog/post/gdpr-compliance-mobile-apps-alternative-firebase If you're developing a mobile app for European users or collecting any data from EU residents, the General Data Protection Regulation (GDPR) is something you need to understand and follow closely. Whether your app collects user information for analytics, login purposes or personalizes the user experience, GDPR compliance is essential. In this guide, we’ll break down what GDPR is, why it matters, and how you can ensure your mobile app stays compliant. ### What is GDPR? The General Data Protection Regulation (GDPR) is a privacy law implemented in 2018 that sets strict guidelines on how personal data of EU residents is collected, stored, and processed. It applies to any company or app—regardless of its location—that handles the personal information of people living in the European Union. GDPR is all about giving users control over their [personal data](https://gdpr-info.eu/issues/personal-data/) and ensuring that organizations treat this data with care and transparency. ### Why it’s important GDPR is important because it protects user rights. As developers, our users trust us with their personal information—things like email addresses, names, location data, and more. GDPR aims to make sure this data is handled responsibly and securely. For developers and businesses, complying with GDPR isn’t just about avoiding penalties (though that’s important); it’s also about building trust with your users, which can lead to better engagement and loyalty over time. ### Penalties for non-compliance The penalties for failing to comply with GDPR can be severe. If your app is found to violate GDPR rules, fines can be as high as [€20 million or 4% of your company’s global annual revenue](https://gdpr-info.eu/issues/fines-penalties/) —whichever is greater. Beyond the financial costs, non-compliance can harm your app's reputation, leading to a loss of user trust. This is why it's essential to take GDPR compliance seriously from the start of development. ### Understanding data controller, processor, and subject Before diving into how to make your app GDPR-compliant, it’s important to understand three key roles defined under the regulation: - **Data controller**: This is the entity (person, company, or app) that determines the purpose and methods of processing personal data. If you own the app and decide what data is collected and why, you are the data controller. - **Data processor**: This is the entity responsible for processing data on behalf of the controller. For example, if you use a third-party service that stores user data, like a BaaS platform, that service acts as a processor. - **Data subject**: This is the individual whose personal data is being collected. In the context of your app, it’s your user. Both the data controller and processor have to follow GDPR, but the controller carries more responsibility for making sure everything is compliant. That said, processors aren’t off the hook—if there’s a data breach or they don’t follow the rules, they can be held directly accountable too. That’s why it’s important to make sure any third-party tools that interact with your user data are also GDPR-compliant. ### How to turn your mobile app GDPR-compliant #### 1. Conduct a data audit The first step in making your app GDPR-compliant is to understand the data it collects and processes. Conduct a full audit of the data flow within your app. Ask yourself these questions: - What personal data are you collecting (names, emails, location, etc.)? - How are you collecting this data (through forms, cookies, etc.)? - Why are you collecting this data, and is it necessary? - Where and how is the data being stored? This audit helps identify unnecessary data collection and ensures that you're only handling data you truly need, a principle known as **data minimization**, which is a key component of GDPR. #### 2. Build the feature to obtain explicit user consent One of the central principles of GDPR for mobile apps is **explicit user consent**. You cannot collect or process personal data unless the user has clearly agreed to it. For mobile apps, this typically means: - **Pre-consent notices**: Before any data is collected, you need to ask for permission, explaining what data will be collected, why, and how it will be used. - **Opt-in mechanisms**: Use clear and transparent opt-in forms rather than pre-checked boxes or ambiguous language. In many cases, the mobile OS will take care of this with a pre-developed box. - **Withdrawal of consent**: Users must have a simple way to withdraw consent at any time, such as disabling tracking in app settings. For example, if your app uses cookies or other tracking technologies to monitor user activity, you must get consent before activating those trackers, even in a mobile environment. #### 3. Provide access to user data GDPR gives users rights over their personal data, including the **right to access** and the **right to be forgotten**. Your app needs to make it easy for users to: - View the data you've collected about them. - Request corrections if the data is inaccurate. - Delete their data upon request (right to be forgotten). You can implement a user-friendly dashboard within your app where users can manage their data or offer a simple contact method where they can request data access or deletion. #### 4. Implement privacy by design Privacy by design means integrating data protection into your app’s development from the start. It’s not something you can add on later; it should be built into every part of your app. Key privacy-by-design practices include: - **Data minimization**: Collect only the data you need and avoid storing unnecessary information. - **Anonymization**: When possible, anonymize or pseudonymize user data to reduce risk in case of a data breach. - **Secure default settings**: Ensure that the most privacy-protective settings are enabled by default for users. By adopting these principles, you create an app that prioritizes user privacy and security at its core. #### 5. Update your privacy policy GDPR requires that you have a transparent and detailed privacy policy that explains how personal data is collected, processed, and stored. This policy should include: - What personal data is collected. - Why the data is collected. - How the data is processed and stored. - How users can access or delete their data. - Who the data is shared with (if any third parties are involved). Your privacy policy should be easy to find and written in clear, simple language, avoiding legal jargon. Make sure users can access this information easily before they give consent. #### 6. Ensure secure data storage and transfer Data security is a critical component of GDPR. You need to protect user data from unauthorized access, breaches, or leaks. To achieve this: - **Encrypt personal data** both in transit and at rest to prevent unauthorized access. - **Use secure servers** and ensure that any third-party services or cloud providers you use are GDPR-compliant. - **Regularly update your security protocols** to stay ahead of potential vulnerabilities. Implementing these practices not only helps with GDPR compliance but also adds a layer of trust for your users. #### 7. Ensure third-party services are GDPR-compliant — or find alternatives Under GDPR, it’s your responsibility to make sure any third-party data processors are also GDPR-compliant. Third-party data processors can include cloud storage providers, analytics platforms, or payment processors — essentially, any external service that handles your users' data. For example, if you’re using a backend platform like Firebase that handles your databases or authentication, you need to keep in mind that it’s not fully GDPR-compliant out of the box. Firebase’s data handling, particularly with data transfers outside of the EU, can make meeting GDPR requirements tricky and put extra burden on you to ensure compliance. Appwrite provides a GDPR-compliant alternative to Firebase. Appwrite is an open-source, cloud development platform that gives you full control over where your data is stored and processed. With Appwrite, you can ensure that all user data stays within the EU, making GDPR compliance much simpler. ![GDPR-compliant alternative to Firebase](/images/blog/gdpr-mobile-apps-guide/1.png) ### Final thoughts Turning your mobile app GDPR-compliant involves more than a few technical tweaks—it requires a thoughtful approach to how you collect, use, and protect user data. By adhering to GDPR's principles of transparency, security, and user control, you can not only avoid hefty fines but also build trust with your users, creating a more secure and responsible app experience. Learn more about how to build a GDPR-compliant app and Appwrite’s approach to security and compliance: - [7 practical steps to build GDPR-compliant software](https://appwrite.io/blog/post/7-steps-to-achieve-gdpr-compliance-for-startups) - [How Appwrite handles GDPR compliance](https://appwrite.io/docs/advanced/security/gdpr) --- ## Measuring Appwrite's Go runtime performance https://appwrite.io/blog/post/go-function-benchmarks It is undeniable that Go has grown to become one of the most popular programming languages among developers worldwide. Recently, during Init, we announced the [new Golang (or Go) runtime](https://appwrite.io/blog/post/announcing-go-support) for Appwrite Functions. However, it is one thing for us to claim that our Go functions runtime is performant, it is a whole other thing for us to justify the same. To do so, we planned a benchmark to test the performance of our Go runtime in comparison with other Appwrite Functions runtimes. ### Setting up the benchmark To test the raw performance of our Go functions, we created several functions with different programming languages using [Open Runtimes](https://github.com/open-runtimes/open-runtimes), a set of open-source serverless function runtimes developed by Appwrite. We also prepared a benchmarking system for these functions. Here’s how we achieved all of these. #### Creating our benchmark test functions We created three different types of functions to benchmark different aspects of the function runtimes’ performance. ##### Hello World The Hello World function is the simplest benchmark for understanding build speed, cold-start speed, and memory consumption in a minimal function. It features a single path and responds with text to any HTTP request sent to it. ```go package handler import ( "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func Main(Context openruntimes.Context) openruntimes.Response { if Context.Req.Path == "/ping" { return Context.Res.Text("Pong"); } return Context.Res.Text("Hello"); } ``` ##### Fibonacci The Fibonacci benchmark allows us to understand the concurrency model of each runtime for CPU-heavy operations. ```go package handler import ( "sync" "github.com/open-runtimes/types-for-go/v4/openruntimes" ) func fibonacci(n int) int { if n <= 1 { return n } return fibonacci(n-1) + fibonacci(n-2) } func Main(Context openruntimes.Context) openruntimes.Response { if Context.Req.Path == "/ping" { return Context.Res.Text("Pong"); } var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() fibonacci(30) }() } wg.Wait() return Context.Res.Text("OK") } ``` An important note is that certain runtimes may feature worse performance than others, implying that they likely didn't use all CPU cores. This can be achieved with additional code, but the benchmark is designed to focus on the runtime's native approach. ##### Scraper The Scraper benchmark tests the build speed and cold-start speed of a bigger function with multiple libraries. ```go package handler import ( "bytes" "github.com/open-runtimes/types-for-go/v4/openruntimes" "github.com/go-resty/resty/v2" "github.com/go-faker/faker/v4" "github.com/PuerkitoBio/goquery" ) type FakerTags struct { Name string `faker:"name"` } func Main(Context openruntimes.Context) openruntimes.Response { if Context.Req.Path == "/ping" { return Context.Res.Text("Pong"); } if Context.Req.Path == "/faker" { fakeData := FakerTags{} err := faker.FakeData(&fakeData) if err != nil { return Context.Res.Text(err.Error(), Context.Res.WithStatusCode(500)) } return Context.Res.Text("

    " + fakeData.Name + "

    "); } client := resty.New().SetCloseConnection(true) resp, err := client.R().Get("http://127.0.0.1:3000/faker") if err != nil { return Context.Res.Text(err.Error(), Context.Res.WithStatusCode(500)) } reader := bytes.NewReader(resp.Body()) doc, err := goquery.NewDocumentFromReader(reader) if err != nil { return Context.Res.Text(err.Error(), Context.Res.WithStatusCode(500)) } data := doc.Find("#title").Text() return Context.Res.Text(data); } ``` Some interpreted languages may underperform in this benchmark, and this could be solved by introducing a library with a build step to minify the code into a single file. However, this benchmark focuses on the runtime’s native approach, so these improvements were not made. You can find the benchmark functions for all the tested languages in this [GitHub repo](https://github.com/Meldiron/open-runtimes-benchmark/). #### Preparing the benchmarking system For our benchmarking system, we created a VM on DigitalOcean with the following specs: - 8 Intel vCPUs - 16 GB RAM - 480 GB Disk memory - Ubuntu 24.04 (LTS) x64 OS. We then installed Docker to run the functions, `hyperfine` to benchmark the function builds and cold starts, and `k6` to benchmark the function executions (view the [preparation instructions](https://github.com/Meldiron/open-runtimes-benchmark/blob/main/PREPARE.md)). Our scripts to set up and run the benchmarks are available in our [GitHub repo](https://github.com/Meldiron/open-runtimes-benchmark). ### Running the benchmarks Through the benchmarks, we explored several aspects of the function runtimes. #### Builds First, we observed the builds of our function. We reviewed both build sizes and build times, and here is what we discovered: ##### Build size In the simpler functions with fewer dependencies (Hello World and Fibonacci), Go had a larger build size than Node, Bun, Deno, .NET, and Dart. For the Scraper function, Go remained competitive and had a smaller build size than Node, Bun, Python, and Ruby. ![Build size](/images/blog/go-function-benchmarks/build-size.png) Interestingly, .NET had the smallest build size across all the benchmark functions compared to Go and other runtimes. ##### Build time Overall, Go was a stable choice with moderate performance. It was not the fastest, but it offered consistent build times as compared to runtimes like Python, Dart, and Ruby. ![Build time](/images/blog/go-function-benchmarks/build-time.png) We must give special shoutouts to Bun and PHP, which showed exceptionally fast built times as compared to any other runtime. These distinctions were even more visible in the Scraper function, showcasing the efficiency of their package managers. #### Memory usage We observed the memory usage of each function before running the benchmarks (i.e., as soon as the functions were set up). Across all three functions, Go consistently used less memory, making it our most suitable function runtime for applications where memory resources are limited, followed by Dart as our next best option. ![Memory usage](/images/blog/go-function-benchmarks/memory-usage.png) Our JavaScript runtimes, Python, Ruby, and .NET, have higher base memory usage, which can be a downside for some. However, we noticed that the memory usage of our .NET function did not increase much with the addition of libraries (in the Scraper function), so that might be the best candidate from the aforementioned list. #### Cold-starts In our benchmark to verify the lowest cold starts, Go emerged as one of the fastest runtimes, particularly when compared to Python, Ruby, and .NET, which showed significantly higher cold-start times. Go's performance was notably better for more complex functions like Scraper, suggesting its suitability for more complex projects with multiple dependencies. ![Cold-starts](/images/blog/go-function-benchmarks/cold-starts.png) As observed, PHP was the best choice for very simple, quick-execution tasks, with the best cold-start times in both the Hello World and Fibonacci functions. Among the compiled runtimes, Dart showed similar performance to PHP in the Hello World and Fibonacci functions with better performance in the Scraper function, making it a more suitable alternative to Go. #### Function executions Next, we tested warmed functions to determine their throughput and the runtimes' ability to handle multitasking. Some runtimes only allow single-threaded operations out of the box, and could show improved performance by installing relevant libraries; however, the benchmarks were intended to demonstrate the runtimes’ native performance. ##### Total requests per second Go consistently showed the highest performance in requests per second across all benchmarks and with higher CPU usage, indicating that it effectively utilizes multiple cores for both I/O-bound and CPU-bound tasks. ![Total requests per second](/images/blog/go-function-benchmarks/total-requests.png) Additionally, .NET was the only runtime other than Go that was able to utilize all CPU cores natively, i.e., without the need to use special libraries or frameworks. Python happened to be an outlier with substantially low throughput; however, this could have been caused by the runtime environment and can be optimized in the future. ##### Requests per second for a single CPU core We also compared each runtime's single-core performance. Go showed balanced performance across different scenarios, excelling particularly in tasks involving high computational needs (e.g., the Fibonacci calculation). ![Requests for the Fibonacci](/images/blog/go-function-benchmarks/requests-per-core-fibonacci.png) While it was not always the top performer in every scenario (Bun, PHP, and .NET demonstrated better performance in the Hello World function), Go's consistency across different types of workloads showed its versatility and reliability. ![Requests per second for a single CPU core](/images/blog/go-function-benchmarks/requests-per-core.png) The data for all the graphs shared above is available in our [GitHub repo](https://github.com/Meldiron/open-runtimes-benchmark/blob/main/RESULTS_SIMPLIFIED.md). ### Conclusion These benchmarks consistently showed us why the Go runtime is one of the best runtimes for Appwrite Functions. It is highly performant, multitasks well, and is highly memory efficient. If you liked these benchmarks, you can find our scripts and test functions for the same in our [GitHub repo](https://github.com/Meldiron/open-runtimes-benchmark). Learn more about Appwrite Functions and Go by reading: - [Appwrite Functions docs](https://appwrite.io/docs/products/functions) - [Appwrite Go quick start](https://appwrite.io/docs/quick-starts/go) - [3 things you can build with the Go runtime](https://appwrite.io/blog/post/3-things-you-can-build-with-go-runtime) - [Appwrite Discord server](https://appwrite.io/discord) --- ## Rethinking password security: say goodbye to plaintext passwords https://appwrite.io/blog/post/goodbye-plaintext-passwords Recently, we came across a report by [BleepingComputer](https://www.bleepingcomputer.com/news/security/misconfigured-firebase-instances-leaked-19-million-plaintext-passwords/), which shared how misconfigured Firebase projects led to the leakage of 19 million plaintext passwords on the public internet. This was primarily caused by missing or incorrectly configured security rules on Firebase instances that consequently permitted read access to databases, resulting in a massive data leak that exposed: - Names: 84,221,169 - Emails: 106,266,766 - Phone numbers: 33,559,863 - Passwords: 20,185,831 - Billing info (bank details, invoices, etc.): 27,487,924 For passwords, the problem was a lot worse because 98% of them, or 19,867,627 to be exact, were in plaintext. While this problem is not inherently a Firebase bug, it does signify a massive lack of understanding around authentication tooling and password security. Therefore, this blog will discuss why plaintext passwords are bad for your security and alternative solutions. ### The dangers of plain text passwords Storing passwords in plaintext presents significant security risks and vulnerabilities for both users and organizations. Here are several vital points illustrating why plaintext passwords are bad: - **An easy target for hackers**: Plaintext passwords can be easily read and exploited by anyone who gains unauthorized access to the storage database, making them prime targets for hackers. - **Lack of confidentiality**: Storing passwords in plaintext fails to protect the confidentiality of user information. If a security breach occurs, users’ passwords are exposed without any layer of protection. - **Non-compliance with security standards**: Many industry standards and regulations, such as GDPR, PCI DSS, and HIPAA, require that personal data, including passwords, be adequately protected, and storing passwords in plaintext can result in non-compliance. - **Increased risk of insider threats**: Plaintext passwords are vulnerable to external threats and insider threats. Employees with access to the database can view and potentially misuse this information, leading to internal security breaches. - **Higher impact of data breaches**: When passwords are stored in plaintext, the impact of a data breach is magnified. Attackers can access the compromised system and use the same credentials to attempt access to other services, exploiting the common habit of password reuse. - **No defense against brute force attacks**: Plaintext passwords offer no defense against brute force attacks. While hashed passwords can also be vulnerable, hashing makes decoding user passwords a lot more difficult. - **Damage to reputation**: A company found to be storing passwords in plaintext can suffer significant damage to its reputation. Users and clients lose trust in a business that doesn’t prioritize their information’s security. ### Alternatives to plaintext passwords Fortunately, modern authentication systems are built with the drawbacks of plaintext passwords kept in mind. Two major solutions that have been developed to solve this problem are as follows: #### Password hashing Password hashing is a security technique that converts a plaintext password into a fixed-size string of characters, which is virtually impossible to reverse. This process uses a hash function to transform the original password, ensuring that even if the data storage is compromised, the actual passwords are not easily deciphered. Benefits of password hashing compared to storing passwords in plaintext include: - **Enhanced security**: Hashed passwords are extremely difficult to reverse-engineer, providing a higher level of security against data theft. - **Data breach protection**: In the event of a breach, hashed passwords significantly reduce the risk of passwords being used for unauthorized access. - **Compliance with standards**: Hashing passwords helps organizations comply with data protection and privacy regulations. - **User trust**: By securing passwords effectively, businesses can maintain and even boost user trust by demonstrating a commitment to privacy and security. - **Mitigation of reuse attacks**: Hashed passwords make it harder for attackers to use stolen credentials on other sites, reducing the impact of credential reuse attacks. - **Scalability and future-proofing**: Hashing algorithms can be updated and strengthened over time, providing flexibility as security standards evolve. You can read our [blog on password hashing algorithms](/blog/post/password-hashing-algorithms) to learn more. #### Passwordless authentication Passwordless authentication eliminates the need for users to create and remember passwords, aiming to enhance both security and user experience. Instead of relying on a traditional password, passwordless methods verify identity through alternative means. This approach can significantly reduce the risk of phishing attacks and password theft, as there’s no password to steal or guess. Some common passwordless authentication methods are: - **Biometric verification**: Uses unique biological characteristics, such as fingerprints or facial recognition, for identification. - **Magic links**: Sends a one-time use URL to the user’s registered email address for login. - **One-time passwords (OTPs)**: Generates a single-use code sent via SMS or email. - **Authentication apps**: Generates time-based codes or push notifications for user approval. - **Smart cards and security tokens**: Physical devices that the user must possess to gain access. You can read our [blog on passwordless authentication](/blog/post/improve-ux-passwordless-auth) to learn more. ### How Appwrite solves this problem [Appwrite Authentication](/docs/products/auth) makes building secure and robust authentication easy and supports many authentication methods. Currently, the following authentication methods are available on Appwrite to combat plaintext passwords: - Email and password login with `Argon2id` password hashing - OTP-based login via SMS and email - Magic URL login via email - 30+ external OAuth2 providers, including Google, Facebook, GitHub, LinkedIn, and Apple - JSON Web Token ([JWT](https://jwt.io/)) creation to extend user authentication to server-side functions - Custom token login to allow integration with any external authentication solution of your choice - Two-factor authentication to add an additional layer of security to user accounts Additionally, to prevent unauthorized data access, Appwrite features a [robust permissions system](/docs/advanced/platform/permissions) that is thoroughly coupled with Appwrite Authentication. The most significant benefit of Appwrite’s permissions system is that any developer needs to offer read or write access to any user intentionally. No default rule provides open access to your application’s client, and the process of setting permissions is simplified by using Appwrite’s console UI. Appwrite also enforces the setting of authorized clients to communicate with an Appwrite project for all supported client platforms (web, Android, Apple, and Flutter). This way, no unauthorized clients can access data, adding an extra layer of protection. ### Moving forward No matter what rules and systems any platform provides, combating plaintext passwords is a responsibility shared by all application developers. If you, as a developer, care about the security and privacy of your user accounts and data, you must take the necessary actions to safeguard their authentication information. Appwrite takes this problem very seriously and will continue to add new tools and improve our existing ones to make user authentication simpler and safer for all developers and their users. Learn more about Appwrite Authentication through our [docs](/docs/products/auth) and join our [Discord server](https://appwrite.io/discord). --- ## Implementing Google OAuth with Expo Router https://appwrite.io/blog/post/google-oauth-expo In this article, you will learn how to implement Google authentication in your React Native apps that use Expo Router. We will go through the following steps in the article. 1. Create an Expo app 2. Set up the bundle ID and package name 3. Set up Google Authentication in Appwrite 4. Configure Appwrite in your Expo App 5. Create a custom `AppwriteProvider` to handle auth 6. Build a UI and fetch user data ### Create an Expo app Let’s start by creating a new Expo app. Run the following command in your terminal to begin creating a new app. ```bash npx create-expo-app ``` Feel free to replace `` to any name you desire. Proceed with the instructions provided by the CLI and create your app based on your configuration. We will use TypeScript for our app, which is the default one installed by the CLI. Feel free to remove the types and use the same code for JavaScript projects. ### Set up the bundle ID and package name To support deep-linking back to our app after successful authentication, you need to give your app a bundle ID (for iOS) and package name (for Android). To set those, open `./app.json` in your Expo project. Modify the `ios` and `android` properties in the file to be similar to the following. ```json { ... "ios": { "supportsTablet": true, "bundleIdentifier": "[BUNDLE_ID]" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", "backgroundColor": "#ffffff" }, "edgeToEdgeEnabled": true, "package": "[PACKAGE_NAME]", "versionCode": 1 }, ... } ``` Replace the `[BUNDLE_ID]` and `[PACKAGE_ID]` to be your desired IDs. A common way to construct such an ID is `com.company.appname`. In this case, we’ve used `com.example.expo-google-oauth` as our bundle ID and package name. However, this only matters in production when your apps are built; while developing, Expo uses its own identifiers to simplify things, and we will look at how to detect and handle these conditions gracefully later in this tutorial. ### Set up Google authentication in Appwrite Follow the steps in our [Google authentication guide](/blog/post/setting-up-google-signin) to set up Google OAuth in Appwrite console. Doing so will enable Google authentication for your Appwrite project. Now, you must set up platforms for your Android and iOS apps inside your project in the Appwrite console. - Go to the **Overview** tab in your project. - Under **Integrations**, click on **Add Platform**. - Select **Android**. In the next step, enter the app name and package name that you entered in your Expo app. Click on **Create Platform**. - Do the same for **iOS**. Doing this will allow your app to use Appwrite services without any problems. ### Setting up Appwrite in your Expo app Now, let’s install the Appwrite React Native SDK. Run the following command in your terminal. ```bash npm install react-native-appwrite ``` In the project root, create a new folder called `lib`. Inside this folder, create a new file called `appwrite.ts` and have the following contents. ```tsx import { Account, Client } from "react-native-appwrite"; const client = new Client(); client .setEndpoint("") .setProject("") .setPlatform(""); export const account = new Account(client); ``` Make sure to replace the configuration variables with your project details! This will enable you to use Appwrite services throughout your app. ### Create a custom `AppwriteProvider` to handle authentication Now, you need to create a provider to help you access authentication status and methods anywhere in your app. This will ensure that every screen in your app can access the logged-in user's information. Inside the `./components` folder, create a new file called `AppwriteProvider.tsx` and have the following contents. We will look at the code in more detail below. ```tsx import * as Linking from "expo-linking"; import { Href, router } from "expo-router"; import * as WebBrowser from "expo-web-browser"; import { createContext, useContext, useEffect, useState } from "react"; import { Platform } from "react-native"; import { Models, OAuthProvider } from "react-native-appwrite"; import { account } from "../lib/appwrite"; const UserContext = createContext<{ current: Models.User | null; login: () => Promise; logout: () => Promise; }>({ current: null, login: async () => {}, logout: async () => {}, }); export function useUser() { return useContext(UserContext); } export function AppwriteProvider(props: { children: React.ReactNode }) { const [user, setUser] = useState(null); async function login(redirectPath: string = "") { try { const redirectUri = Linking.createURL(redirectPath); const response = await account.createOAuth2Token( OAuthProvider.Google, redirectUri, redirectUri ); if (!response) { console.error("No OAuth URL returned from Appwrite"); return; } const result = await WebBrowser.openAuthSessionAsync( response.toString(), redirectUri ); if (result.type === "success" && result.url) { const url = new URL(result.url); const secret = url.searchParams.get("secret"); const userId = url.searchParams.get("userId"); await account.createSession(userId!, secret!); const user = await account.get(); if (Platform.OS === "ios") { router.replace(redirectPath as Href); } setUser(user); } } catch (error) { console.error("OAuth error:", error); } } async function logout() { await account.deleteSession("current"); setUser(null); } async function init() { try { const loggedIn = await account.get(); setUser(loggedIn); } catch (err) { setUser(null); } } useEffect(() => { init(); }, [user]); return ( {props.children} ); } ``` In the above code, we are doing the following: - Creating a context called `UserContext` that will be used for storing authentication information and related methods at the application level. - Creating the `useUser()` hook that will allow us to access the methods and information stored in the context in any of the components in the app. - Creating an `AppwriteProvider` component that wraps around our entire app in the `_layout.tsx` file. We will do that next. - Storing the user data in a state called `user` and initialising the state every time the app is opened by using the `init()` function in a `useEffect()`. The `init()` function calls `account.get()` from the Appwrite SDK to get the current logged-in user’s information. - Creating a `login()` function - We are creating a deep-link path that, by default, links to the homepage of the app, it can be modified by passing function parameters. - We are using the `createOAuth2Token()` method from Appwrite SDK to get the Google OAuth URL for the user to perform sign-in. We are also passing the redirect URI so that we can be redirected back to the desired page. **Note that `createOAuth2Session()` does not work on React Native.** This is because deep-linking is currently not handled by the method and `createOAuth2Token()` should be used to get a token which in turn can be used to set a session. - We are using the `openAuthSessionAsync()` method to open the browser for the authentication session. The reason we are using this specific function and not any other browser opening function is that this is built for authentication, and for platforms like iOS, this will respect the deep-link and redirect back to the app, which won’t be the case in a regular browser. - After getting back into the app using the deeplink, we will have the user ID and secret from Appwrite. Now, we can simply call `createSession()` method to sign in using the user ID and secret. The Appwrite React Native SDK will take care of the rest, including persisting the credentials in storage. - Deeplink redirect URIs only actually redirect to your desired page if you’re using Android. For iOS, you need to do this manually. So we place a special code block for iOS devices to handle the redirection to the desired screen. - Creating the `logout()` function that will be responsible for deleting the current session. Now, you have a solid foundation for authentication ready, and you can use this anywhere in your app. Now let’s look at an example implementation for the `useUser()` hook. ### Build a UI and fetch user data Remove the `(tabs)` folder, and create a new file called `index.tsx` in the `app` directory. Also, go to the `_layout.tsx` file and make sure your index file is mentioned: ```tsx ``` Now, go to `index.tsx` file and use the content from our [GitHub repository](https://github.com/atharvadeosthale/appwrite-expo-google-oauth/blob/main/app/index.tsx). Remember, this is an example, and from here on out, you can implement the `useUser()` hook with your own UI and implementation. This should help you test your authentication flow. In this file, we have imported the `useUser()` hook and used functions like `login()` and `logout()` on buttons, and also displayed the user's email. To add additional functionality, you can create more functions under `AppwriteProvider`. ### Final results The final app, when first opened, shows a button to sign in with Google. ![App login splash screen](/images/blog/google-oauth-expo/login-screen.png) Once you sign in with your Google account, you will receive the following greeting in your app. ![App login successful](/images/blog/google-oauth-expo/login-successful.png) ### More resources This article taught you how to implement Google OAuth authentication into your Expo router apps. Appwrite makes it much easier for you to add authentication to your apps. You can use similar approach for any other OAuth provider supported by Appwrite. If you have any questions or suggestions, please join our [Discord server](https://appwrite.io/discord). [React Native quickstart on Appwrite documentation](/docs/quick-starts/react-native) [How to build cross-platform applications using React Native](/blog/post/building-cross-platform-applications-with-react-native) [Setting up “Sign in with Google” in your app](/blog/post/setting-up-google-signin) --- ## A modern developer’s guide to user authentication https://appwrite.io/blog/post/guide-to-user-authentication User authentication is a fundamental process that ensures only authorized individuals can access specific systems, applications, or data. Every time you log in to your email, bank account, or social media, you're going through this process. As a developer, setting up user authentication properly is crucial to maintaining security and trust in your application. But authentication is more than just checking passwords — it’s about balancing security, usability, and scalability. So, what should you know when implementing user authentication? Let’s break it down. ### The basics of user authentication At its core, [authentication](/docs/products/auth) verifies a user’s identity by comparing the credentials they provide (such as a username and password) with a trusted data source. The goal is simple: ensure the user is who they say they are. But how you go about implementing this can range from basic to highly sophisticated. The foundation of any authentication system relies on **secure handling of credentials** and ensuring that the process is both reliable and user-friendly. Without a strong authentication system, your app becomes an easy target for attacks. ### Authentication vs. authorization: what’s the difference? Authentication and **authorization** are often confused, but they serve distinct roles in application security. - **Authentication** answers the question *"Who are you?"* It's the process of verifying the user's identity (e.g., logging in with a username and password or using an OAuth token). - **Authorization** answers the question *"What are you allowed to do?"* Once the user is authenticated, authorization defines their permissions and access to resources (e.g., roles determining which API endpoints or data they can interact with). Think of it like this: **authentication** is about getting the user through the front door (proving their identity), while **authorization** decides which areas of the building they can access (determining their level of permissions based on roles, policies, etc.). ### Common types of user authentication There are several ways systems can authenticate users, ranging from the familiar to the more advanced. #### Password-based authentication This is the most traditional form of authentication, where users submit a password that’s compared against a hashed version stored in your database. However, passwords are vulnerable to attacks like brute force or credential stuffing, especially if users choose weak or reused passwords. This is why password-based authentication alone is no longer considered sufficient in many modern apps. #### Two-factor authentication (2FA) 2FA adds an extra layer of security beyond the password by requiring a second form of verification, typically a one-time code sent via SMS, email, or an authenticator app. As a developer, implementing 2FA means integrating libraries or services that handle generating and validating these codes, or relying on APIs. By adding this "second bouncer," you significantly reduce the risk of unauthorized access, even if the user's password is compromised. #### Biometric authentication Biometric methods like fingerprint scanning, facial recognition, or voice ID are increasingly common, especially with mobile devices. As a developer, you can integrate biometric authentication through native device APIs (e.g., Android’s Fingerprint API, iOS Face ID) or cross-platform libraries like WebAuthn for web apps. Biometric data isn’t stored as raw images but as encrypted representations, adding complexity but enhancing security. Since it's hard for attackers to replicate physical traits, this method offers a strong, user-friendly alternative to passwords. #### Magic links Magic links are a convenient passwordless authentication method, where users receive a link via email to log in. Clicking the link verifies their identity, bypassing the need for a password altogether. This method can be particularly user-friendly since it reduces password fatigue and makes the login process seamless. As a developer, implementing magic links requires setting up a secure link generator and handling token expiration to prevent unauthorized access if the link is compromised. #### OTP SMS method With [OTP (One-Time Password) SMS](https://appwrite.io/blog/post/should-you-stop-using-otp-sms), users receive a temporary numeric code sent to their mobile device via SMS, which they enter to authenticate. This method is popular due to its simplicity and accessibility for users. To set it up, developers can leverage SMS APIs, such as Twilio, to automate OTP generation and delivery. However, ensure that codes expire quickly and avoid SMS for sensitive applications if possible, as it can be vulnerable to SIM swapping attacks. ### 7 best practices for great user authentication Here are the key components you should consider for an outstanding and secure user authentication experience. **Password security**: - **Password hashing**: Never store plain-text passwords! Instead, use strong hashing algorithms like bcrypt, Argon2, or PBKDF2 to store password hashes. [Password hashing](https://appwrite.io/blog/post/password-hashing-algorithms) is essential to prevent common password attacks like rainbow table attacks. - **Password policies**: Implement strong [password policies](https://appwrite.io/blog/post/password-protection), but avoid being too restrictive. Encourage users to create complex passwords without making them impossible to remember. For example, consider enforcing a minimum length but allow passphrases for easier memorability. **Rate limiting and brute-force protection**: - To prevent brute-force attacks, where an attacker repeatedly guesses passwords, implement **rate limiting** or **login throttling**. After a few failed login attempts, you can introduce a cooldown period or lock the account temporarily. - Integrate tools like CAPTCHA for additional protection after multiple failed login attempts, ensuring you aren't sacrificing UX for security. **Two-factor authentication (2FA)**: - One of the easiest ways to add an extra layer of security to your app is by implementing **2FA**. This can involve sending a code via SMS, email, or using an authenticator app (e.g., Google Authenticator or Authy). - When building 2FA, make sure to give users multiple backup options (e.g., recovery codes) in case they lose access to their second factor. **OAuth 2.0 and OpenID Connect**: - If you're looking to offload the complexities of authentication, consider using **OAuth 2.0** or **OpenID Connect** to enable users to log in with external providers like Google, Apple, or GitHub. [Appwrite’s Auth API](https://appwrite.io/docs/products/auth/oauth2) makes setting up OAuth2 quick and seamless. - **JWT (JSON Web Tokens)** are commonly used in OAuth/OIDC to pass user claims and permissions between services. JWTs allow for stateless authentication, but remember: JWTs should be **signed** to prevent tampering and **encrypted** if sensitive data is involved. **Single Sign-On (SSO)**: - **SSO** allows users to authenticate once and gain access to multiple applications. For organizations, this simplifies user management and enhances security. - Implementing SSO can greatly reduce user password fatigue and the need for maintaining separate credentials for each app. Just ensure your SSO provider is configured with the correct security settings, such as enforcing 2FA. **Session management**: - **Session tokens** vs. **JWTs**: You’ll need to decide between traditional session tokens stored on the server (usually in a database) or JWTs, which are stateless and stored on the client side. Each has pros and cons. - For traditional session-based authentication, store session IDs securely in HTTP-only cookies. For JWTs, ensure they are signed and ideally, encrypted, and never store them in localStorage or expose them in JavaScript to prevent XSS attacks. - Consider implementing **automatic session expiration** or idle timeouts to improve security. Regularly rotate and invalidate tokens to prevent session hijacking. **Secure password reset mechanisms**: - Password reset functionality is often an easy target for attackers, so make sure the process is secure. - When sending password reset links, ensure that the link expires after a short time and can only be used once. Also, avoid revealing whether the email entered exists in your system to prevent enumeration attacks. ### Mistakes to avoid when setting up user authentication #### **Weak password policies** Weak passwords are a top vulnerability in authentication security. Enforce strong password policies, requiring a mix of uppercase, lowercase, numbers, and special characters, along with a minimum character length. Additionally, consider supporting multi-factor authentication (MFA) for an extra layer of security, which helps reduce the risk of unauthorized access even if passwords are compromised. #### **Storing plaintext passwords** Storing passwords in plaintext is highly insecure. Always hash passwords before storing them, using strong hashing algorithms like **bcrypt** or **argon2**, which are designed specifically for password security. Avoid older, weaker hashing algorithms like MD5 or SHA-1, which are vulnerable to attacks. Additionally, apply a unique salt to each password to further protect against rainbow table attacks. #### **Not validating tokens properly** Tokens, especially JWTs (JSON Web Tokens), are a popular choice in modern authentication. However, failing to validate token expiration or signature can expose applications to token-related vulnerabilities. Always check the token’s expiration date and validate the signature against your secret key to ensure authenticity. Rotate tokens periodically and, when possible, implement short expiration periods with refresh tokens to maintain security. #### **Using insecure third-party authentication providers** While third-party providers like OAuth, OpenID Connect, or SAML simplify authentication, they must be secure and reputable. Choose providers that follow industry-standard security protocols and regularly audit their security measures. Configure scopes appropriately, granting only necessary permissions. Test third-party integrations to ensure they follow security best practices and update configurations to remain aligned with current standards. ### Final thoughts User authentication is the first line of defense in safeguarding digital identities, and users truly value the peace of mind that comes from knowing their data is secure. As a developer, implementing proper authentication protects both your users and your app from a wide range of threats. However, keeping up with evolving threats takes time, and building a secure authentication system from scratch might not be feasible, especially if you're also responsible for the rest of your backend. That’s why it's often better to trust established security experts rather than reinvent the wheel. Appwrite’s Auth service can help you set up multiple secure authentication methods — including OAuth, email/password, magic links, and OTP — in just a few lines of code. This way, you ensure that your users are protected by industry standards without having to maintain the entire system yourself. ### Frequently asked questions (FAQs) **1. What’s the difference between authentication and authorization?** Authentication verifies who you are, for example, logging in with an email and password. Authorization determines what you can do after logging in, such as accessing admin pages or specific APIs. Think of authentication as the key to enter a building, and authorization as the permission to enter certain rooms. **2. What’s the most secure way to handle passwords?** Never store plain-text passwords. Always hash them with strong algorithms like bcrypt or Argon2, and add a unique salt to each hash. This ensures that even if your database leaks, passwords can’t easily be reversed. Also, enforce password length and complexity without making them too frustrating to remember. **3. Is passwordless authentication more secure than passwords?** In most cases, yes. Passwordless methods like magic links, WebAuthn, or biometric login remove the need for users to remember passwords, which eliminates risks from password reuse or brute-force attacks. But you still need to secure token expiration, link validity, and fallback recovery options. **4. How can developers add authentication without building it from scratch?** Use a backend service like Appwrite, Supabase, or Auth0. They handle user creation, token management, and secure password resets for you. With Appwrite, for example, you can implement email/password, OAuth, OTP, or magic link login in just a few API calls, while still keeping full control over your data. **5. Should I still use OTP over SMS in 2025?** SMS OTPs are convenient but increasingly risky due to SIM swapping and phishing attacks. They’re fine for low-risk apps, but for better security, use authenticator apps, email OTPs, or WebAuthn-based passkeys that are tied to a device instead of a phone number. ### More resources: - [Auth docs](https://appwrite.io/docs/products/auth) - [How to set up Sign in with Apple](https://appwrite.io/blog/post/how-to-set-up-sign-in-with-apple) - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) - [How to implement GitHub sign-in with Appwrite](https://appwrite.io/blog/post/implement-sign-in-with-github) - [Password protection for developers: best practices](https://appwrite.io/blog/post/password-protection) - [Say goodbye to plaintext passwords](https://appwrite.io/blog/post/goodbye-plaintext-passwords) - [Should you stop using OTP SMS now?](https://appwrite.io/blog/post/should-you-stop-using-otp-sms) --- ## Celebrate Open Source with Appwrite and Hacktoberfest 2023! https://appwrite.io/blog/post/hacktoberfest-2023 Hacktoberfest is back, celebrating a decade of open source! Appwrite is proud to support open-source developers for the fifth consecutive year of [Hacktoberfest 2023](https://hacktoberfest.com), partnered with DigitalOcean. Here is how you can get involved and grab some cool Appwrite swag! #### Quick Guide to Getting Involved ##### 1. Appwrite Repository: - Check out the Appwrite repository on [GitHub](https://github.com/appwrite). Ensure you “Star” and “Like” the repo so you can find it easily ##### 2. Search for Issues: - Look for [issues labeled `hacktoberfest`](https://github.com/appwrite/appwrite/labels/hacktoberfest). Not seeing enough to choose from? Do not worry, we will be adding more issues throughout the month - Can't find an issue you want to work on? You can create an issue if you have great ideas on how you can contribute to improving Appwrite. Keep in mind this years focus is on quality, not quantity ##### 3. Choose Your Contribution: - Find the Issue you want to contribute to - Reply with a comment, “I would like to work on this. Can you assign it to me?” - Be sure to follow up with the issue within 3 days, so we know you are still working on it - Once your PR is ready, link the PR in the issue and tag a maintainer to let them know it is ready for review - Your PR will be confirmed within 7 days, and if it's accepted, we will label it as `hacktoberfest-accepted` ##### 4. Get Support: - Our maintainers are here to assist, guide, and mentor you through your contribution. Need alternative ways to contribute or additional help? Contact us on [Discord](https://appwrite.io/discord)! ##### 5. Win Swags: - Earn an Appwrite hoodie and stickers for contributing only one accepted and merged PR! - Earn a Digital Swag Bag from DigitalOcean for contributing 4 PRs+ from any open-source project participating in Hacktoberfest. We have a surprise gift inside this swag bag waiting for you ![Hacktoberfest swag](/images/blog/hacktoberfest2023-swag.png) #### How Appwrite Supports Developers - We are hosting several events that you can find in the Appwrite Discord community to introduce you to open-source projects, guide your contributions, and prep you for October. We are hosting two PR Review parties each week where you can watch our engineers live reviewing your pull requests and giving tips on contributing - We are consistently sharing content on how to build with Appwrite and become a better developer #### Join the Appwrite Community Stay informed on new announcements, events, and features, and interact with other Appwriters from the community! [Join us on Discord](https://appwrite.io/discord), join the #hacktoberfest channel, and celebrate open-source together! --- ## Get inspired for Hacktoberfest 2024 with these ideas https://appwrite.io/blog/post/hacktoberfest-ideas-2024 Appwrite has just announced our very own **Hacktoberfest 2024 Hackathon**, and if we know the open source community, you're probably buzzing with ideas! Hacktoberfest is the perfect chance to collaborate, build, and showcase your creativity, whether you’re an experienced developer or just getting started. But with so many possibilities, where do you start? A few months ago, we hosted our own internal hackathon to foster collaboration, spark creativity, and put the new 1.6 features to the test before [their official release](https://appwrite.io/changelog/entry/2024-09-18). The Appwrite team rose to the challenge and delivered some amazing projects. In this blog, we’ll highlight a few of those projects to inspire your Hacktoberfest 2024 journey! ### How do you come up with a winning project for Hacktoberfest 2024? Seasoned hackathon contributor Aditya Oberai [shared his 4-step approach](https://appwrite.io/blog/post/the-subtle-art-of-hackathon%20ideation) to help you brainstorm and ideate for any hackathon: - **Pick a problem you relate to**: Choose a problem that personally impacts you, as emotional connections will keep you motivated and focused throughout the hackathon. - **Understand the problem's scale**: Research how many others are affected by the problem, increasing the value of your solution. - **Evaluate potential solutions**: Brainstorm multiple ways to solve the problem and choose the most practical one, avoiding wasted time later on. - **Choose a self-sustainable solution**: Pick a solution that can run independently without relying on external systems or resources for long-term success. ### Appwrite Hackathon projects We have 3 projects we'd like to share that demonstrate a variety of ideas and approaches. We hope these examples provide some inspiration as you start thinking about your own Hacktoberfest projects. ![Terrazone](/images/blog/get-inspired-for-hackathon/1.png) ### Terrazone: Revolutionizing global workforce management Terrazone is a platform designed to help remote teams work better together across different time zones through a simple, user-friendly interface. #### What problem does it solve? Working remotely can come with challenges, such as scheduling meetings, dealing with communication delays, and building a strong team culture. Terrazone helps everyone on the team stay connected by keeping track of where they are and when they're available. #### Key features - **Global workforce visualization**: A cool globe that shows where your team members are located and their time zones at a glance. - **Easy access**: Simple login and registration keep your data secure and accessible. - **User-friendly management**: Team members can easily manage their time zones, availability, and external access to their schedules. - **Flexible scheduling**: Users can set their working hours and choose who can view their schedules for more privacy. #### Appwrite features used - [Go runtime and SDK](https://appwrite.io/blog/post/announcing-go-support) for Functions - [New Appwrite CLI](https://appwrite.io/blog/post/introducing-new-appwrite-cli) for Functions - [Local development](https://appwrite.io/blog/post/announcing-local-development) to test functions - [Appwrite Authentication](https://appwrite.io/docs/products/auth/oauth2) for authentication ![MailMemo](/images/blog/get-inspired-for-hackathon/2.png) ### MailMemo: Simplifying calendar notifications #### What problem does it solve? Managing multiple personal and work calendars can be overwhelming, especially with last-minute event notifications. MailMemo simplifies this by consolidating all your calendar events and sending personalized email reminders, helping you stay organized and on top of your schedule. #### Key features - **Calendar consolidation**: Connects all your calendars in one place. - **Customizable reminders**: Choose when and how often to receive notifications. - **Email summaries**: Get organized updates directly in your inbox. #### Appwrite features used - [GitHub OAuth](https://appwrite.io/docs/products/auth/oauth2) in Appwrite Authentication - [Appwrite Database](https://appwrite.io/docs/products/databases) and Collections - [Mailgun provider](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) on Messaging - [New Go runtime and SDK](https://appwrite.io/blog/post/announcing-go-support) for Functions - [New Functions ecosystem](https://appwrite.io/blog/post/introducing-functions-ecosystem) to sync calendars and schedule emails ![Cord](/images/blog/get-inspired-for-hackathon/3.png) ### Cord: A new way to do async Cord is a Discord bot designed to help teams be more effective when communicating through async. It comes with a few handy features like timezone and location in bio, automatic starting messages, and standup summaries. #### What problem does it solve? Even though we've mastered the art of async communication, misunderstandings can still happen. With a global team, it’s not always clear when others are online. That’s where Cord comes in—it provides a simple way to see when your coworkers are available, making communication smoother and more efficient. #### Key features - **/location:** Display your current location and timezone in your bio - **/daily**: Write down your progress, and the command will organize it into a daily standup format automatically. - **/schedule:** Schedule your message at a later time. - **/start:** This command will notify your team you began your day, displaying local time. #### Appwrite features used - [Bun runtime](https://appwrite.io/blog/post/why-you-need-to-try-the-new-bun-runtime) for Functions - [Email OTP feature](https://appwrite.io/docs/products/auth/email-otp) in Appwrite Authentication - [Appwrite Databases](https://appwrite.io/docs/products/databases) for user and project settings - [Delayed executions](https://appwrite.io/docs/products/functions/execute) ### Final thoughts What do all these projects have in common? Beyond being built with Appwrite 1.6, they all tackle challenges that our team faces ourselves. Our top advice is simple: create a project that solves your own pain points (or dive into Reddit or Twitter to discover common developer issues). Remember, the sky’s the limit! If any of these projects have inspired you and sparked an idea for our Hacktoberfest 2024 Hackathon, don’t forget to [register your team and project here](https://apwr.dev/htf24-hackathon) for a chance to win a prize. Happy coding! --- ## Handle CORS errors in Appwrite Functions https://appwrite.io/blog/post/handle-cors-in-serverless-functions Cross-Origin Resource Sharing (CORS) is a security feature that allows web applications to interact securely with resources from different origins and denies unwanted communication. You might be reading this because your browser has blocked access to your Appwrite serverless function. This guide will help you "unblock" that access. ### Understanding CORS CORS controls how web applications interact with resources from different origins, preventing unauthorised requests from malicious websites. To allow cross-origin requests, servers must include specific headers in their responses that indicate which origins are permitted to access the resources. #### **How CORS works** When a browser makes a cross-origin request, it includes an Origin header with the request. The server can respond with various CORS headers to specify: - Which origins are allowed (`Access-Control-Allow-Origin`) - Which methods are allowed (`Access-Control-Allow-Methods`) - Which headers are allowed (`Access-Control-Allow-Headers`) For certain types of requests, the browser sends a preflight (OPTIONS) request to check if the actual request is safe to send. ### CORS in Appwrite serverless functions In traditional Node.js and Express setups, CORS headers are often set using `res.setHeader()`. However, in Appwrite serverless functions, this approach won't work due to the way Appwrite handles responses. Instead, you should directly include CORS headers in the response returned by the function. #### Steps to handle CORS ##### 1. Create and deploy your serverless function Start by creating and deploying your serverless function through the Appwrite console or CLI. This ensures your function is set up correctly within the Appwrite environment. Detailed steps can be found in the [Appwrite Functions documentation](https://appwrite.io/docs/products/functions). ##### 2. Handle preflight (OPTIONS) requests Preflight requests are sent by browsers to verify permissions before making actual requests. To handle these requests, check if the incoming request is an OPTIONS request and respond with the appropriate CORS headers: ```javascript if (req.method === 'OPTIONS') { return res.send('', 200, { 'Access-Control-Allow-Origin': 'YOUR_DOMAIN_HERE', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }) } ``` ##### 3. Include CORS headers in responses For other requests (POST, GET, etc.), include the `Access-Control-Allow-Origin` header in the response to specify which origins are allowed to access the resource: ```javascript if (req.method === 'POST') { try { return res.json({ ok: true }, 200, { 'Access-Control-Allow-Origin': 'YOUR_DOMAIN_HERE', }) } catch (error) { return res.json({ error: 'Internal Server Error' }, 500, { 'Access-Control-Allow-Origin': 'YOUR_DOMAIN_HERE', }) } } ``` ### Using the wildcard `*` to allow all origins It is possible to use the wildcard `*` in the `Access-Control-Allow-Origin` header to allow any origin to access the resource. While this simplifies development, it can pose security risks. For this reason, you might encounter errors when using the wildcard `*` in your Appwrite production environment. When developing locally, here's how you might configure CORS to allow access from any origin: ```javascript return res.json(completion, 200, { 'Access-Control-Allow-Origin': '*', }) ``` For better security, replace `*` with your specific domain name to restrict access to only trusted origins: ```javascript return res.json(completion, 200, { 'Access-Control-Allow-Origin': 'YOUR_DOMAIN_HERE', }) ``` This way, only requests originating from your domain will be allowed. ### Conclusion Handling CORS in Appwrite serverless functions ensures secure communication between your web applications and backend resources. By understanding how CORS works, you can prevent unauthorized requests and protect your serverless functions from potential security threats. - [Get started with Appwrite](https://cloud.appwrite.io/) - [Explore the Functions documentation](https://appwrite.io/docs/products/functions) - [Join our Discord community](https://discord.com/invite/appwrite) --- ## Appwrite's Hacktoberfest 2023 journey https://appwrite.io/blog/post/hf-2023-journey **October** is our favorite month of the year because it brings with it **Hacktoberfest**, the largest celebration of open source in the world! And this year, we returned to support DigitalOcean and **Hacktoberfest 2023** as a sponsor to invest in the upliftment of the open-source world and to rejoice the 10th anniversary of this wonderful initiative. Hacktoberfest brought moments of nostalgia seeing developers from different walks of life join in to take their first steps in the open-source world. We had an absolute blast interacting with all the new contributors and hosting all the events throughout the month! Our **[Discord server](https://appwrite.io/discord)** was full of **AMAs** and **PR Review parties**, which enabled us to demonstrate our PR review workflow and allowed the community to really appreciate the work that goes into maintaining open-source projects. We were also able to educate new contributors on various facets of open source, usage of tools like Git and GitHub, the process of submitting a PR, communication best practices, and so much more! Not only did we see multiple virtual events, but we also hosted an [in-person Hacktoberfest Kickoff meetup](https://photos.app.goo.gl/n5YQjK56CCgqyrj68) in collaboration with DigitalOcean in Bengaluru, India. Overall, this Hacktoberfest was one of the most wholesome and fulfilling experiences for our team, and we cannot wait to share our highlights from this edition. ![Hacktoberfest Kickoff event in Bengaluru, India](/images/blog/hf-2023-journey/kickoff-blr.png) #### Our history with Hacktoberfest Before we jump into some amazing open-source contributions from Hacktoberfest this year, we would love to share why our participation in Hacktoberfest matters far more to us than we can express. **Appwrite was first released back in September 2019** with one mission, to make software development accessible and enjoyable for all. **Hacktoberfest 2019** was truly a game-changer for us, as we saw **over 200 contributions** from some lovely members of the open-source fraternity that October. The stars on our project doubled that month and entered the 4-digit range for the very first time. That one month truly enabled us to lay the foundations of a budding organization with the sole purpose of helping developers build faster and better. And for that, no thank-yous will ever be enough to express our gratitude for this amazing open source community that chose to carry us on their shoulders at our inception. This year, as a sponsor of Hacktoberfest, we set out with the mission to **uplift the ever-growing open-source community** and **enable more newcomers** to step in and grow in this ecosystem. #### Some of our favorite contributions This Hacktoberfest, we focused on collaborating with our contributors to make impactful contributions. Some of them really stood out to us, and we thought to share them here for you to check out too! - [Security Scans in the CI pipeline using GitLeaks](https://github.com/appwrite/appwrite/pull/6492) - [Functions template to implement Sync with MeiliSearch in Kotlin](https://github.com/appwrite/templates/pull/234) - [Messaging adapter for Postmark](https://github.com/utopia-php/messaging/pull/26) - [Storage adapter for IBM Cloud Object Storage](https://github.com/utopia-php/storage/pull/90) - [Unit tests for the Android and Kotlin SDKs](https://github.com/appwrite/sdk-generator/pull/729) The Appwrite team also participated in a number of livestreams throughout the month. - [Open Source Friday with Appwrite](https://www.youtube.com/watch?v=VJvawxeUhN4) by GitHub - [Learn healthy contribution practices with Aditya Oberai!](https://www.twitch.tv/videos/1947516149) by GitHub Education - [Building Appwrite Functions](https://www.twitch.tv/videos/1953276802) by MLH #### Milestones achieved so far Together, we made a lot of noise during Hacktoberfest, leading to some amazing results for the Appwrite community! - **135+** Hacktoberfest issues - **65+** accepted Pull Requests - **30+** new GitHub contributors - **8** Hacktoberfest PR Review Parties All these achievements were made possible through the strong collaboration and active participation of the community through a variety of contributions, spanning **adapters**, **function templates**, and more! #### What’s next for Appwrite We have a lot of exciting features that we’re working towards as we gear up to make **Appwrite Cloud** generally available. Appwrite Cloud is the hosted Appwrite solution managed by our team, so developers like you can focus on building their applications. We’re currently running Appwrite Cloud in Public Beta, so [sign up and start building](https://cloud.appwrite.io/register) as soon as you can! Besides that, this Hacktoberfest, one new product that we saw a number of folks contribute to is **Appwrite Messaging**. Stay tuned for more information on the same. #### How can you contribute after Hacktoberfest? Hacktoberfest may be over, but you don’t have to stop contributing! We have **lots of open issues** that you can find on our [GitHub repos](https://github.com/appwrite/appwrite/issues). You can also **write articles**, **create tutorials**, or **build demo apps** and add them to our [Awesome Appwrite repo](https://github.com/appwrite/awesome-appwrite) and the [Built With Appwrite website](https://builtwith.appwrite.io). There are always new ways to support the [community](/community), and we truly love all the contributions you make. If you need help with Appwrite or would like to explore some interesting ways to contribute, join us on our [Discord server](https://appwrite.io/discord) and connect with the Appwrite community. Thank you so much once again for joining us during Hacktoberfest. We hope you enjoyed contributing to open source as much as we do, and we can’t wait to have you all with us next year! --- ## How Appwrite streamlines database operations using hooks https://appwrite.io/blog/post/hooks-appwrite-databases Software engineering is complex, especially when you aim to build robust applications. For example, you may want to handle type conversions and clean your data before storing it in your database, add external loggers and observability tools, or add additional user authentication factors for specific functionalities. At some point, the need for extensibility will inevitably arise in your software. This is where **hooks** come into the picture. But what are hooks, and how do we implement them? Let's find out. ### What are hooks? Hooks allow developers to inject custom code at specific points in the application's execution flow. They also provide a mechanism to intercept and modify data at various points in the application's lifecycle. Hooks let you manipulate data and trigger custom behavior without directly altering the core codebase. They are used for various tasks such as data validation, manipulation, logging, and even triggering external processes. #### How are hooks implemented? Different programming languages implement hooks in different ways. However, the process of preparing hooks is usually similar across various languages and frameworks: 1. **Identify hook points:** First, you need to identify the points in your application where you want to allow custom code to be executed. These points are often associated with specific events or actions in your application lifecycle, such as before or after a database query, before rendering a template, or after user authentication. 2. **Define hook functions**: Next, you'll create functions that contain the custom code you want to execute at these hook points. These functions are often referred to as hook functions or callbacks. These functions could be defined anywhere in your codebase, such as within a dedicated hooks file or the relevant class or file. 3. **Register hook functions:** At the appropriate points in your application's code, register the hook functions to be executed. If your application uses a framework or library that supports hooks, you can register them manually or via a hook management system. 4. **Trigger hook execution:** When the application reaches the registered hook points, the hook functions are executed in the order they were registered. This allows your custom code to be seamlessly integrated into the application logic. ### How we implemented hooks in Appwrite's codebase Appwrite's tech stack has numerous points where we need to extend the fundamental functionality of our tools. This section will discuss how we implemented hooks in our Databases service. #### Step 1: Identifying hook points Our Database library, the [Utopia PHP Databases library](https://github.com/utopia-php/database/), powers every database-relevant action in Appwrite, whether we're talking about user-facing functionalities present in [Appwrite's Database API](https://appwrite.io/docs/references/cloud/client-web/databases) or internal functionalities such as storing Appwrite organization and project data, usage statistics for different services, SSL certificates for custom domains, etc. There are various scenarios (or "hook points") in Appwrite where we need to transform data, such as hashing passwords, converting timezones for DateTime value, and serializing JSON objects before storing them in our underlying MariaDB database. Therefore, we decided to implement hooks in the form of custom filters to achieve these data transformations. #### Step 2: **Defining the hook functions** After the hook points were decided, we created hooks or, in this case, custom filters for our Database tables. These filters consist of three fundamental components: - **Filter name:** The filter name is a unique identifier for a specific hook. It allows us to target the desired hook when defining their custom logic. - **Encode function:** The encode function comes into play when data is being written to the database. It acts as a transformation mechanism, allowing us to modify the data before it gets stored. For example, if we want to encrypt sensitive data before saving it, we can create a filter and define the encryption logic within the encode function. - **Decode function:** The decode function operates when data is read from the database. This function enables data transformation during retrieval. For example, if our application stores dates in UNIX timestamp format but wants to display them in ISO format, we can implement the conversion within the decode function. Here is a [code example](https://github.com/appwrite/appwrite/blob/a49c3a33f0fd831423afa7a0b53df2c5d709fc2b/app/init.php#L554-L578) of how we prepared the `encrypt` filter in Appwrite using the Utopia PHP Databases library to store secrets: ```php . . . Database::addFilter( 'encrypt', // Filter name function (mixed $value) { // Encode function $key = System::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; return json_encode([ 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), 'method' => OpenSSL::CIPHER_AES_128_GCM, 'iv' => \bin2hex($iv), 'tag' => \bin2hex($tag ?? ''), 'version' => '1', ]); }, function (mixed $value) { // Decode function if (is_null($value)) { return; } $value = json_decode($value, true); $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); } ); . . . ``` #### Step 3: Registering the hook functions Next, we attached these filters to specific hook points within Appwrite. For example, the `encrypt` filter we saw in the previous step had to be declared for each column (attribute) that needs to be encrypted in a table (collection) in Appwrite's underlying MariaDB database, like the `secret` column in the `tokens` table in our [code example](https://github.com/appwrite/appwrite/blob/a49c3a33f0fd831423afa7a0b53df2c5d709fc2b/app/config/collections.php#L502): ```php . . . [ '$id' => ID::custom('secret'), 'type' => Database::VAR_STRING, 'format' => '', 'size' => 512, // https://www.tutorialspoint.com/how-long-is-the-sha256-hash-in-mysql (512 for encryption) 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => ['encrypt'], ], . . . ``` These encode and decode functions for these filters were already "hooked" into the necessary functions within the Utopia PHP Databases library, like the [`createDocument`](https://github.com/utopia-php/database/blob/f979e2fed855118da1455da205982645be8b2ae6/src/Database/Database.php#L3265) function. The [encode](https://github.com/utopia-php/database/blob/f979e2fed855118da1455da205982645be8b2ae6/src/Database/Database.php#L5329) and [decode](https://github.com/utopia-php/database/blob/f979e2fed855118da1455da205982645be8b2ae6/src/Database/Database.php#L5388) functions then verified which filters had to be applied for each field in the database entity using the table name. With a simple declaration in the Appwrite configuration, we could apply any filter to any data added or retrieved from our underlying database. #### Step 4: Triggering the hook functions Implementing and registering the filters allowed them to be triggered whenever data is written or read in the appropriate columns in our database tables, invoking the respective `encode` or `decode` functions. This has resulted in seamless, transparent data transformation, enhancing security and user experience. ### Conclusion Hooks are integral to the functioning of Appwrite Databases. As shown above, they enable data sanity without altering core database functionality. In addition to databases, [Appwrite](https://appwrite.io/) provides other core backend services like user authentication and authorization, databases, file storage, serverless functions, messaging, and more. You can check it out and get started with your first project in minutes here: - [Quickstarts](https://appwrite.io/docs/quick-starts) - [Appwrite Cloud](https://cloud.appwrite.io) - [Appwrite Docs](https://appwrite.io/docs) - [Discord community](https://appwrite.io/discord) --- ## How to host SSR web apps on Appwrite Sites https://appwrite.io/blog/post/host-ssr-web-apps-sites When you're building a modern web app, how you serve your content matters. Some pages need to be pre-rendered ahead of time for speed. Others need to be generated dynamically on the server for personalization or real-time data. This process is called Server-Side Rendering (SSR), and Appwrite Sites supports SSR, just like it supports Client-Side Rendering (CSR) and Static Site Generation (SSG). If you've used platforms like Vercel before, this will feel familiar. You can connect your GitHub repository, choose a branch, and Appwrite will build your app and deploy it to a server. In this post, we'll show you exactly how to host an SSR-capable web app using [Appwrite Sites](/products/sites), from repository connection to framework-specific configurations. Whether you're migrating from Vercel or starting from scratch, this guide will walk you through every step. ### What is SSR, and why does it matter? Server-Side Rendering (SSR) means your web app's pages are rendered on the server at request time. Unlike pre-rendered static pages, SSR allows your app to respond to each request with dynamic content that's fully rendered before it reaches the browser. It's a powerful approach for apps that need authentication, personalization, real-time data, or SEO-friendly dynamic content. Appwrite Sites supports SSR by allowing you to configure your framework's build settings and output behavior, and by running your app on a server runtime (Node.js) where needed. ### Hosting an SSR app on Appwrite Sites Let's walk through the process step by step. #### 1. Open the Appwrite Console Log in to your Appwrite Console. In the left sidebar, you'll see a section labeled **Sites**. Click on it. This will take you to the Sites dashboard, where you can manage existing sites or deploy a new one. Click the **Create Site** button to get started. ![Setup Sites on the Console](/images/blog/ssr-how-to-create-sites.png) At this point, you have two options: - **Clone a template** , If you're starting fresh, this is a quick way to scaffold a new site. - **Connect a repository** , If you already have a working app in GitHub, this is what you'll want. Choose **Connect a repository**. Appwrite will walk you through selecting a GitHub repository and giving the necessary permissions if you haven't already. #### 2. Configure your deployment settings Once your repository is connected, you'll land on the deployment configuration page. Here's what you'll see: - **Name**: A name for your site, usually taken from your repository. - **Branch**: The branch you want to use for production (typically `main` or `master`). - **Root directory**: Leave this as `./` unless your app lives in a subfolder. - **Silent mode**: This prevents Appwrite from adding GitHub comments when changes are pushed. - **Framework**: Optional, but recommended. Selecting your framework here helps Appwrite apply sensible defaults and display SSR instructions specific to your setup. Now, scroll down to the **Build settings** section. This is where you define how your app gets built: - **Install command**: The default `npm install` usually works unless your app uses a different package manager or custom setup. - **Build command**: For most frameworks, this is `npm run build`. - **Output directory**: For Next.js, this would typically be `./.next`. Different frameworks may use different output paths. If your app needs environment variables, like API keys, secrets, or runtime flags, you can define them in the **Environment variables** section. You can also import them from an existing `.env` file. When you're done, click **Deploy**. Appwrite will pull your code, run your build steps, and deploy your site. ### Framework-specific SSR tips While Appwrite Sites supports SSR, not every framework behaves the same out of the box. If your app was previously deployed on Vercel, there's a good chance it was configured with a Vercel-specific adapter, especially if you used Next.js or SvelteKit. These adapters are tailored for Vercel's environment and won't run properly on Appwrite, which expects Node.js as the runtime. To make SSR work on Appwrite, you'll need to switch to the appropriate Node.js adapter. Let's go through a few popular frameworks and what to look out for: #### Next.js If you're using Next.js, SSR is supported. In your `next.config.js` file, **make sure you do not set the `output` field**. That's specific to Vercel and can interfere with the way Appwrite handles the build output. Just let Next.js use its default behavior, and in your site settings, keep the output directory as `./.next`. #### SvelteKit SvelteKit sites hosted on Vercel typically use `@sveltejs/adapter-vercel`. That won't work on Appwrite. To fix this, open your `svelte.config.js` file and switch to the Node.js adapter: ```tsx import adapter from '@sveltejs/adapter-node' export default { kit: { adapter: adapter(), }, } ``` Once that's set, rebuild and redeploy your site. #### Nuxt Nuxt works quite seamlessly on Appwrite. In your site settings, make sure the build command is set to: ```bash npm run build ``` SSR is supported out of the box with no additional adapter configuration needed. #### Angular (with SSR) If you've enabled SSR in your Angular app, make sure your `src/server.ts` file is using the `@angular/ssr/node` package. This enables Angular Universal to run properly in a Node.js environment. #### Astro For Astro apps, you'll want to install and configure the Node adapter. In `astro.config.mjs`, update your adapter: ```tsx import node from '@astrojs/node' export default { output: 'server', adapter: node(), } ``` This tells Astro to render pages server-side using Node.js. #### Remix Remix apps should be configured to use `@remix-run/node` in the server entry file. Make sure your `entry.server.tsx` file imports from: ```tsx import { createRequestHandler } from '@remix-run/node' ``` This ensures the server runtime is correctly set for Node environments like Appwrite. #### Analog (Angular meta-framework) Analog supports SSR via Vite. To enable it, set the `ssr` property to `true` inside your `vite.config.ts` file: ```tsx export default defineConfig({ plugins: [ analog({ ssr: true, }), ], }) ``` This activates server-side rendering in your build pipeline. ### How to check or change framework instructions If you're not sure what to do for your specific framework, you can find help directly in the Appwrite Console. Just open your deployed site from the Sites dashboard, click on **Settings**, and scroll down to the **Build settings** section. When you select a framework from the dropdown, Appwrite will show instructions for SSR configuration, specific to your chosen stack. That way, you'll know exactly what needs to change and how. ### Final thoughts Hosting SSR apps with Appwrite Sites gives you a lot of flexibility. You get a Git-powered workflow, server runtime support, and integration with frameworks you already use. But more importantly, you're in control. You're not locked into a proprietary environment, and you can configure the exact build behavior you need. If you're switching from Vercel, just remember to revisit your framework's adapter and build setup. Most of the time, it's just a matter of switching to a Node.js adapter and updating a couple of config files. Once you've set that up, Appwrite Sites takes care of the rest, from cloning your repo to deploying server-rendered content. Try it out, and let us know how it works for your stack. ### More resources - [Appwrite Sites docs](/docs/products/sites) - [Building with Sites templates](/blog/post/building-with-sites-templates) - [SSR vs CSR with Next.js](/blog/post/csr-vs-ssr-with-nextjs) - [Appwrite SSR Authentication](https://www.youtube.com/watch?v=7LN05c-ov_0) - [Here's how you do auth with 100% SSR in Next.js](https://www.youtube.com/watch?v=ENnG7GusuO4) - [Appwrite Discord server](/discord) --- ## Announcing hosting for Flutter web: deploy your Flutter web apps with Appwrite https://appwrite.io/blog/post/hosting-flutter-web Appwrite has long been a powerful backend platform for Flutter developers building mobile applications. Today, we’re bringing that same seamless experience to the web. With full support for Flutter in [Appwrite Sites](/products/sites), you can now deploy Flutter web apps directly from your Appwrite project. No extra configuration, no added complexity. This means you can use your existing Dart and Flutter knowledge to create fast, responsive web apps without needing to learn HTML, CSS, or JavaScript. Build once with Flutter and deploy to mobile, desktop, and web. All from a single codebase, all hosted on Appwrite. ### Why native Flutter web support matters While most web environments are made for Node, Deno, or Bun runtimes, only a few are tailored for the needs of Flutter developers. Appwrite Sites is different. It’s designed with full Flutter web support in mind, not just as an afterthought. That means native framework detection, optimized build settings, and direct integration with your Appwrite backend products like databases, authentication, storage, and functions. More importantly, it eliminates the common friction points of mobile app deployment. You don’t need to pay for developer accounts ($25 for Android, $99/year for Apple), wait days for store approvals, or navigate restrictive platform policies. With web deployment, your app is live the moment you push it. And because web apps run anywhere with a browser, you’re no longer limited to iOS or Android. Your Flutter app can be accessed from a desktop, tablet, mobile phone, or even a smart fridge. The web is universal, and Appwrite makes it easy to reach your users. ### Not just for full web apps Even if you're not targeting the web as your primary platform, Appwrite Sites still solves real needs for Flutter developers. Many apps include flows that rely on external links, such as email verification, password reset, or payments. These typically direct users to a browser. With Sites, you can now create and host those supporting pages using the same Flutter tooling you're already familiar with. Instead of stitching together HTML pages or managing another stack, you can build these "satellite" pages as lightweight Flutter web apps, and deploy them effortlessly with Appwrite. It's a way you leverage your existing knowledge and a more consistent way to build, with fewer moving parts and one unified codebase. ### Key features - **Full Flutter framework support**: Sites now recognizes and supports Flutter as a framework during setup. - **Auto-detection**: Flutter web projects are automatically identified. No manual config required. - **Optimized static hosting**: Deployed like any other static site, but with Flutter-first optimizations. - **GitHub integration**: Automatically deploy on every push from your GitHub repository. - **Works on Cloud and Self-Hosted**: Whether using Appwrite Cloud or Self-hosting, Sites for Flutter works everywhere. ### How to get started #### Step 1: Create Flutter web app First, you must either create a Flutter Web app or set up the [Flutter Web starter template](https://github.com/appwrite/templates-for-sites). Open your terminal, and run the following command. ```bash flutter create my_app ``` In case you have an existing Flutter app and want to add web support to it, you need to run the following command in your project directory: ```bash flutter create . --platforms web ``` Push this project to a [GitHub repository](https://github.com/new). #### Step 2: Create Appwrite project Head to the [Appwrite Console](https://cloud.appwrite.io/). If this is your first time using Appwrite, create an account and create your first project. #### Step 3: Create site Head to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**. Connect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease). 1. Select the **production branch** and **root directory** from your repo. 2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Flutter Web** from the drop-down list. 3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Flutter Web are: - **Install command:** `N/A` (leave empty) - **Build command:** `flutter build web` - **Output directory:** `./build/web` 4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app. Click on the **Deploy** button. #### Step 4: Visit site After successful deployment, click on the **Visit site** button. ### Simplifying development Sites for Flutter is part of Appwrite’s larger mission to simplify the developer experience. With support for backend and frontend services, including static hosting, databases, functions, and auth, Appwrite is the all-in-one platform to build any application. Try it today on **Appwrite Cloud** or **self-hosted,** and you can take your Flutter app live in just a few clicks. ### More resources - [Appwrite Sites docs](/docs/products/sites) - [Appwrite compared to Vercel](/blog/post/open-source-vercel-alternative) - [Appwrite Sites product tour](https://youtu.be/VtDe6hDw91k) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## Secure, scalable e-commerce: How Appwrite makes authentication easy https://appwrite.io/blog/post/how-appwrite-makes-auth-easy-for-ecommerce Building an e-commerce platform today is more than just creating a catalog and checkout flow. Security, user experience, and scalability are critical, and authentication sits right at the heart of it all. Whether you're launching a small boutique or scaling a global marketplace, handling authentication correctly can make or break your app. At Appwrite, we believe authentication should be powerful but painless. Let's walk through how Appwrite helps you manage user [authentication](/products/auth) for e-commerce projects, without adding complexity. ### Why Authentication matters in E-Commerce Authentication is more than a login screen. It's about creating trust. - **Security**: Protect users' personal and payment information. - **Conversion**: A seamless sign-up and login flow can significantly reduce cart abandonment. - **Compliance**: Meet privacy regulations like GDPR and CCPA with minimal effort. - **Personalization**: Give customers a tailored experience once they're logged in. Bad authentication experiences can lead to lost sales, security breaches, and broken trust. That's why it needs to be first-class from day one. Here are some [auth best practices](/post/guide-to-user-authentication) to follow to secure your app and improve user experience. ### Authentication challenges in E-Commerce Building auth for an e-commerce platform isn't as simple as “email and password” anymore. Modern buyers expect: - **Multiple sign-in options** (email, social login, magic links) - **Passwordless experiences** - **Two-factor authentication (2FA)** - **Session management across devices** - **Account recovery** - **Secure APIs** for mobile apps, web apps, and third-party integrations And that's before you think about scaling to millions of users. ### How Appwrite simplifies E-Commerce authentication Appwrite provides a secure,[fully-featured authentication system](/docs/products/auth) out of the box. Here’s what you get: #### 1. **Multiple sign-in methods** Appwrite supports: - Email/Password - Anonymous login (perfect for guest checkout) - OAuth providers (Google, Apple, GitHub, Discord, and more) - Magic URL sign-in (passwordless authentication) You can mix and match depending on your store's needs. #### 2. **Built-in 2FA** Enhance security by enabling two-factor authentication using TOTP apps like Google Authenticator. No extra backend work required. Learn more about [MFA](/docs/products/auth/mfa) {% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Built-in security and compliance" point2="Multiple login methods" point3="Custom authentication flows" point4="Multi-factor authentication" cta="Request a demo" url="https://appwrite.io/contact-us/enterprise" /%} #### 3. **Self-Service account management** Appwrite provides ready-to-use APIs for users to: - Reset passwords - Update email addresses - Manage sessions across devices Build beautiful UI components on top without reinventing the wheel. #### 4. **Secure, scalable sessions** Every session is tightly managed and tokenized, making sure your users stay authenticated safely across devices, whether they shop on mobile or desktop. #### 5. **Customizable authentication workflows** Want custom onboarding, email verification, or a loyalty signup flow? Appwrite’s Cloud Functions and webhooks let you extend and customize without touching your core app code. #### 6. **Privacy & compliance friendly** With built-in features for GDPR compliance, encrypted storage, and fine-grained permissions, Appwrite makes it easier to protect customer data and respect user privacy. ### Real-world example: Fast checkout with magic URLs Imagine reducing checkout friction by letting users sign in with a single email link. No passwords, no back-and-forth. Appwrite’s Magic URL flow lets you send users a secure link that signs them in instantly, helping boost conversion rates and customer happiness. ### Getting started: Your first Auth flow in minutes With Appwrite’s SDKs (for Web, Flutter, iOS, Android, and more), integrating authentication is fast: ```jsx import { Client, Account } from 'appwrite'; const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('your-project-id'); const account = new Account(client); // Create a new account await account.create({ userId: ID.unique(), email: 'user@example.com', password: 'password123' }); // Login await account.createEmailPasswordSession({ email: 'user@example.com', password: 'password123' }); ``` That's it. Secure, scalable auth in just a few lines. ### Build E-Commerce your way, with Appwrite Authentication shouldn't slow down your e-commerce dreams. With Appwrite, you can focus on building amazing shopping experiences, not fighting backend complexity. Whether you are starting a new store or growing a marketplace, Appwrite provides tools for secure and easy authentication. You can use them your way. Ready to get started? [Deploy Appwrite](https://cloud.appwrite.io/) in minutes and launch your next big thing. ### Further reading - [Appwrite Authentication docs](/docs/products/auth) - [Appwrite vs Auth0: Which is better for a B2C app?](/appwrite-vs-auth0-b2c) - [OAuth with Google](/integrations/oauth-google) - [OAuth with Amazon](/integrations/oauth-amazon) --- ## How to attract contributors and users to your open source project https://appwrite.io/blog/post/how-to-attract-users-to-open-source-project The open-source community is a remarkable space. In this unique corner of the Internet, developers come together to collaborate, help out, and build solutions without expecting anything in return. This generosity makes it an excellent environment for developers trying to build the next big thing with limited resources. The community around Appwrite is one of a kind, having won awards for its openness and welcoming nature. While this community naturally formed around the Appwrite project, there are steps you can take to help foster and support its growth. ### How it happened Appwrite began as a side project, a labor of love by our Founder and CEO, Eldad, to solve his own challenges as a software engineer. The launch exceeded his expectations, going viral on Hacker News and reaching 1,500 GitHub stars in the first month alone. Fast forward to today, and Appwrite has over 42K stars, 800 contributors, and 120K Cloud users. Our team is made up of some of the top contributors who have been with us from the start. Our Discord server has grown to over 17K members who support each other, promote Appwrite, and inspire developers to create amazing things. ![120K Appwrite Cloud users](/images/blog/how-to-attract-users-to-open-source-project/120k-users.png) We're incredibly grateful and proud of the Appwrite community. The success of Appwrite is fueled by the dedication and enthusiasm of developers in the open source space. But how did we go from a viral project to the vibrant community we are today? ### How to foster your open source community #### Focus on quality - **Build for your audience** Your solution should solve a real problem that developers face. If you're struggling with something, chances are other developers are too. Developers love to streamline their processes and will do whatever it takes to improve their workflows and help others do the same. For example, Appwrite’s easy-to-use backend addressed the issue of wasting time on repetitive and complex backend tasks, which quickly attracted a community of eager-to-help engineers. ![Coby Fayock quote](/images/blog/how-to-attract-users-to-open-source-project/Colby-1.png) - **Create clear documentation** Focus on lowering the barrier to entry and making it easier for others to contribute. Clear documentation is the first thing potential contributors and users will look at. If your documentation is well-organized and easy to understand, they’ll be more likely to dive in and get involved. ![Eddie Jaode quote](/images/blog/how-to-attract-users-to-open-source-project/Eddie-1.png) - **Offer tutorials and getting-started guides** A developer will spend an average of 3.14 seconds testing your solution before looking for help. That’s why providing user-friendly getting-started guides is a fantastic way to engage new users quickly. These resources help users get up to speed without wasting time. They offer various formats to cater to different learning styles — like video tutorials and building use cases with code snippets or quick-starts. For example, here at Appwrite we have an extensive [getting-started section on docs](https://appwrite.io/docs), or comprehensive [video tutorials on YouTube](https://youtube.com/playlist?list=PL-nc7zI7zjsZ6aS2jk4YFgXeNNYaiT_f8&feature=shared) — there’s something for everyone! ![Offer tutorials and getting started guides](/images/blog/how-to-attract-users-to-open-source-project/tutorials.png) #### Engage with your community - **Provide support** The best way to attract and keep an open source audience is to make sure your users are happy. Provide support, fix bugs, and educate the community. Our Discord server has a dedicated support channel where our support team, together with experienced community members called [Appwrite Heroes](https://appwrite.io/heroes), helps address questions and resolve issues quickly. ![Discord support at Appwrite](/images/blog/how-to-attract-users-to-open-source-project/support-discord.png) - **Ask for help and feedback** Want your users to contribute? Just ask! Most developers are more than happy to help improve an open-source solution they use. Pro tip: create a code of conduct and contribution guidelines to help users provide quality contributions. You can also invite feature requests and gather feedback from your community. This goes back to our first point: build for your community. By asking for their input, you make them feel valued and ensure your project evolves in a way that benefits everyone. - **Encourage discussion** Community engagement happens through conversation. Meet your community where they are, whether it's on X, Reddit, or Discord. Join the discussions and share your insights on industry trends. Or better yet, create public events and invite your users. For example, we host regular Office Hours on Discord to assist users with any issues, discuss current events, and generally geek out about tech. It’s great fun and helps the user base put faces behind the product. ![Appwrite Office Hours on Discord](/images/blog/how-to-attract-users-to-open-source-project/open-hours-screenshot.png) - **Show appreciation** Give some love back to the community. At Appwrite, we do this in various ways. We love swag, and guess what? Developers love swag too. Do you see where this is going? ![Appwrite swag](/images/blog/how-to-attract-users-to-open-source-project/swag-twitter.png) As a token of our appreciation, we send swag to the most prominent contributors and supporters and host regular community giveaways. Every shirt, hoodie, bag, or pair of socks sold from the [Appwrite store](https://appwrite.store/) contributes to the open source community. Have no resources for swag? No worries! Another way we show appreciation is through community recognition. Give your contributors public shouts on social media and thank them for their support. A little recognition goes a long way. ![Colby Fayock quote 2](/images/blog/how-to-attract-users-to-open-source-project/Colby-2.png) #### Build in public - **Use social media to your advantage** Developers are allergic to marketing, but if you want people to use your project, you have to tell them about it. The creator of Snowpack Fred K. Schott suggests [finding your storytelling style](https://dev.to/fredkschott/5-things-i-learned-while-building-snowpack-to-20-000-stars-b9d). Get creative on social media, tell your story in your own words, and speak to the audience on your blog. Creative and unique content will help promote your work and build user relationships. ![Appwrite Twitter](/images/blog/how-to-attract-users-to-open-source-project/twitter-poll.png) - **Network with other devs** Collaboration and cross-promotion is the name of the game. Participate in conferences, meetups, and webinars to showcase your project and its capabilities. Connect with other open-source project maintainers and communities. It can also help you build your team in the future. ![Appwrite team at RenderATL 2024](/images/blog/how-to-attract-users-to-open-source-project/render-atl-team-photo.png) - **Work with creators** Collaborating with content creators is a fantastic way to amplify your project's reach. By partnering with bloggers, YouTubers, and mentors in the tech community, you can tap into their established audiences and gain credibility. Creators can produce tutorials, reviews, and case studies that showcase your project in action, providing authentic and relatable content that resonates with their followers. #### Keep the momentum going - **Release updates** Even small updates can signal that your project is actively maintained. Release updates regularly to reassure your community and keep them engaged. - **Share your roadmap** Being transparent with your users pays off. Let the community know what features and improvements are planned while also giving them space to request features and fixes. We’ve recently [released our new public roadmap](https://appwrite.io/blog/post/public-roadmap-announcement) as part of our commitment to the community. It’s designed to improve collaboration between the Appwrite team and contributors. When users know what to expect, they feel heard and valued. - **Celebrate milestones** An open-source project is nothing without its community, so be sure to include them in your milestone celebrations. Share when you reach significant milestones, such as major releases or achieving a certain number of contributors. Openly celebrating your community on social media or your Discord server is a great way to show appreciation. ![42K Github startgazers](/images/blog/how-to-attract-users-to-open-source-project/42k-stars.png) ### Conclusion We’ve seen firsthand how an engaged open-source community can help drive innovation and growth. Appwrite’s journey from a side project to a platform with thousands of users wouldn’t have been possible without the passion of open source developers. That’s why we want to give back and support OSS projects. If you’re building or maintaining an open-source project, join our [OSS program](https://appwrite.io/blog/post/announcing-the-appwrite-oss-program), and don’t forget to say hi on our [Discord server](https://appwrite.io/discord). Let’s continue to build amazing things together. --- ## How to back up and restore your Appwrite data https://appwrite.io/blog/post/how-to-back-up-your-appwrite-data Updated on October 6, 2025 Backing up and restoring data is an extremely important part of running servers. It's a virtual safety net against most bad things that can happen. Made a bad config change? Restore a backup. Messed up an update? Restore a backup. Corrupted Drives? Restore a backup. Not only that, backups can also come in handy when migrating data to other systems, like migrating a development server into a production environment or vice versa. To make this process as easy as possible, we've written this guide to explain everything you need to know about backing up and restoring your Appwrite instance. Appwrite consists of multiple sections, and most of it is stateless. This means there are only two main things you need to back up: Appwrite's database (MariaDB) and the Docker volumes that store functions, data, and uploads. The rest can be automatically handled and regenerated by Appwrite. Please note that all these commands need to be run within the same directory as Appwrite's `docker-compose.yml` With all that said, lets begin! ### Backing up the MariaDB Database Due to the fact that Appwrite uses a Docker image of MariaDB it is extremely easy to dump the entire database with just one command and likewise to restore the dump. Creating a Database backup is just one command: ```bash docker-compose exec mariadb sh -c 'exec mysqldump --all-databases --add-drop-database -u"$MYSQL_USER" -p"$MYSQL_PASSWORD"' > ./dump.sql ``` This command does a couple things: 1. Docker-compose launches a temporary shell onto the MariaDB container to start work 2. It runs `mysqldump` on the server with two specific options `-all-databases` and `-add-drop-database` these are important since they ensure that when the backup is restored old data doesn't get overlapped with new data. 3. The output of `mysqldump` is piped into a `dump.sql` file. This is your backup. ### Restoring the MariaDB database Restoring the database is similarly easy and also requires just one command to do: ```bash docker-compose exec -T mariadb sh -c 'exec mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD"' < dump.sql ``` This command is very simple once you break it down: 1. Docker-compose launches a temporary shell onto the MariaDB container to start work 2. Using the `mysql` command we restore the dump through a pipe ### Backing up your Docker volumes Appwrite stores various types of data in Docker volumes, including file uploads and Cloud Function data. These volumes help coordinate data between the central Appwrite container and the various Appwrite workers. It's crucial to back up these uploads since they contain all your app's file uploads. Keep in mind that these backup commands might take some time to run, depending on the amount of data you need to back up. > Before running these commands is it highly recommended to shut down your Appwrite instance to ensure you get a complete backup. > To backup the functions volume the command is: ```bash mkdir -p backup && docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/backup ubuntu bash -c "cd /storage/functions && tar cvf /backup/functions.tar ." ``` and to backup the uploads volume the command is: ```bash mkdir -p backup && docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/backup ubuntu bash -c "cd /storage/uploads && tar cvf /backup/uploads.tar ." ``` Finally, also backup your builds volume: ```bash mkdir -p backup && docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/backup ubuntu bash -c "cd /storage/builds && tar cvf /backup/builds.tar ." ``` Both these commands do similar things and when you break them down they are pretty simple. 1. Start a new Docker container. This Docker container has a few special options - `-rm` will delete the container once it's done running. The reason we want this is because this container is only being used to package up our backup and give it to the host machine. - `-volume-from` This flag special as it will mount all of the volumes of the container we give it. To get the container ID we want we use a `$(docker-compose ps -q appwrite)` to get the ID within the command - `v` This flag is being used to mount a volume onto our new container which will give us access to a backup folder we created using the `mkdir` command at the start `ubuntu` is the image we are basing our new container on 1. Finally with this command created we change directories into the normal Appwrite mount point for uploads and create a tarball which will be created in the backup directory where we will be able to access it. Once these commands are run you should find a new `backup` folder which contains`uploads.tar` and `functions.tar` these are your backups. Keep them safe. ### Restoring your Docker volumes Restoring your Appwrite volumes is fairly simple as well. Move the backup folder you just created to your destination machine next to the `docker-compose.yml` file and simply run the following commands to restore the backup. > Please note that the Appwrite instance should be shut down while running these commands. Restoring functions volume: ```bash docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/restore ubuntu bash -c "cd /storage/functions && tar xvf /restore/functions.tar --strip 1" ``` Restoring uploads volume: ```bash docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/restore ubuntu bash -c "cd /storage/uploads && tar xvf /restore/uploads.tar --strip 1" ``` Restoring the builds volume: ```bash docker run --rm --volumes-from "$(docker-compose ps -q appwrite)" -v $PWD/backup:/restore ubuntu bash -c "cd /storage/builds && tar xvf /restore/builds.tar --strip 1" ``` This command creates a new temporary Docker container similar to the backup command, but instead of creating a backup, it extracts the tar file back into the functions and uploads endpoints to restore the backup. ### Copy your `_APP_OPENSSL_KEY_V1` environment variable Appwrite keeps all your data encrypted, to ensure that all your files, hashes and all other encrypted data is accessible to your new Appwrite instance make sure to copy the `_APP_OPENSSL_KEY_V1` from your original instance to your new instance. ### Conclusion To create a complete Appwrite backup, you'll need to back up MariaDB and the two specified volumes. Once you’ve done this, ensure the backup is stored safely. The best practice is to store it in multiple locations, both locally and across different cloud services. Like with any cloud-native application, regular backups of your Appwrite instance are essential to prevent data loss in case of a server failure. This process makes migrating an Appwrite installation simple. Just copy the backup files to another server and run the restore steps. We hope you enjoyed this article! We love contributions and encourage you to take a look at our [open issues](https://github.com/appwrite/appwrite/issues) and [ongoing RFCs](https://github.com/appwrite/rfc/pulls). If you get stuck anywhere, feel free to reach out to us on our [friendly support channels](https://appwrite.io/discord) run by humans. ### Frequently asked questions (FAQs) **1. Why should I back up my Appwrite instance regularly?** Backups protect your data from accidental deletions, failed updates, or hardware issues. They also make it easy to migrate between servers or environments. Regular backups ensure you can restore your setup quickly without losing user data or configurations. **2. What parts of Appwrite do I need to back up?** You only need to back up two main things: - The MariaDB database (stores all project data) - The Docker volumes for functions, uploads, and builds. Everything else is stateless and can be regenerated automatically by Appwrite. **3. Can I back up Appwrite while it’s running?** It’s possible, but not recommended. To avoid incomplete or inconsistent backups, it’s best to shut down your Appwrite instance before running backup commands. This ensures all data is captured correctly. **4. How do I restore an Appwrite backup to a new server?** Move your backup files to the new server, place them next to the `docker-compose.yml`, and run the restore commands for the database and volumes. Don’t forget to copy your `_APP_OPENSSL_KEY_V1` environment variable, it’s required to decrypt your data. **5. Where should I store my Appwrite backups?** Keep backups in multiple safe locations, ideally a mix of local storage and cloud storage. This protects you from local disk failures and makes recovery easier if your primary environment goes down. **6. How often should I back up my Appwrite database?** It depends on how frequently your data changes. For active projects, schedule daily or hourly backups of the MariaDB database to prevent data loss. You can automate this using cron jobs or CI pipelines. For smaller or less active projects, weekly backups are usually enough. Just make sure to store them securely and test restores occasionally. Here are some handy links for more information: - [Appwrite contribution guide](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md) - [Appwrite Github](https://github.com/appwrite) - [Appwrite docs](https://appwrite.io/docs) --- ## How to build a tech stack for a remote startup https://appwrite.io/blog/post/how-to-build-a-remote-tech-stack Building a remote company? Awesome! You've probably read many great stories about the benefits of work-life balance, working from anywhere, and hiring global talent. Or you might come from a 9 to 5 office job and think: never again! Either way, you're here because you now might be wondering, how do I actually communicate, organize, hire, and work fully remotely? Since day one, Appwrite has been working entirely remotely and across the globe. We've learned a thing or two about what tools work best for us, and we would like to help you get started with your own remote-empowering tech stack. ### How to choose the right tech stack Before you start looking into your stack, ask yourself what you value most in using tools. At Appwrite, we appreciate tools that help us work remotely more effectively. We also prefer open-source and privacy-friendly software, though not exclusively. This list highlights the tech we use to organize our work as an open-source, fully remote, and privacy-focused company. ### 10 tools you need in your remote startup tech stack #### 1. Discord for team communication ![Discord](/images/blog/how-to-build-remote-tech-stack/1.png) Initially created for gamers, [Discord](https://discord.com/) is designed for real-time communication, which is really important to help remote teams stay connected. We love grouping conversations per team and topic, with additional channels for cross-team collaboration. The “lobby” chat acts as an office space where team members say hi when they log on, have informal conversations, or share gifs — the water cooler talk of remote companies! This helps us feel connected even if our coworkers are online in a different timezone. Discord is also a favorite of the open source community, with many OSS projects being developed in Discord servers. Appwrite has an active [public server on Discord](https://appwrite.io/discord), and you're welcome to join! Some Discord pros: - Ease of use - Robust features for free - Serious security features, including 2FA, IP location lock and more - Versatile communication channels, including audio and video - Customizable server structure #### 2. Linear for task tracking ![Linear](/images/blog/how-to-build-remote-tech-stack/2.png) As your remote team and their workloads grow, it becomes crucial to ensure tasks are completed on time, nothing gets overlooked, and no team member feels overwhelmed. At Appwrite, we use [Linear](https://linear.app/) for its straightforward, developer-friendly approach. Linear began as an issue-tracking software and has evolved into a comprehensive project management platform. It maintains its original concept: each task is an "issue" assigned to a team in 2-week cycles. Issues have an owner, due date, completion stages, and comments. This system makes it easy to track who is responsible for what and distribute workloads evenly among team members. If you've ever managed a project, you know that work expands to fill the time available. By framing issues within 2-week cycles, Linear helps focus efforts and accelerate task completion. Linear enables us to collaborate effectively across teams. Its intuitive approach is accessible to both developers and non-developers. As a fully remote company, Linear understands the challenges of remote work and offers solutions that truly work. Some Linear pros: - Developer-friendly approach - User-friendly - 2-week cycles - Easy task tracking #### 3. Figma for design & collaboration ![Figma](/images/blog/how-to-build-remote-tech-stack/3.png) You've probably heard of [Figma](http://figma.com) as a design tool. But did you know you can also use it for brainstorming, retrospectives, and collaboration? We love this versatility. At Appwrite, our design teams use Figma for everything—from product designs and web pages to blog covers and social assets. Meanwhile, the rest of the team can leave comments and collaborate seamlessly. Figma's whiteboard feature, FigJam, is perfect for brainstorming and real-time collaboration. You can set the topic, start a timer, and let ideas flow freely on a shared whiteboard. This makes brainstorming sessions more cohesive and focused. Some Figma pros: - Versatility - Real-time collaboration - Easy-to-use #### 4. Notion for databases & collaboration ![Notion](/images/blog/how-to-build-remote-tech-stack/4.png) Your remote company's documents need a reliable home, and Notion databases are the perfect solution. Appwrite uses [Notion databases](http://notion.so) as its single source of truth for all crucial information, including the company handbook, team documents, campaign briefs, content databases, and social media calendars. Notion's collaborative features allow team members to work together in sync and async, making it an excellent choice for a remote setup. This centralization fosters a transparent and cohesive work environment, keeping information easily accessible and up-to-date. Due to its versatility, Notion might be a bit daunting to get started with — but you can download plenty of free templates to customize your databases to your preference. Some Notion pros: - Comprehensive databases - Async collaboration - Customizable - Easy-to-use #### 5. Loom for sharing information remotely ![Loom](/images/blog/how-to-build-remote-tech-stack/5.png) Organizing a meeting every time you need to demo a tool, explain a new process, or train a new team member can be a real hassle, especially if you're coordinating schedules across different time zones for remote teams. These meetings often turn into a waste of time, especially for repeatable and straightforward tasks. That's why we're big fans of [Loom](https://www.loom.com/). With Loom, you can record your screen and provide a detailed walkthrough of any process. No more frantic calendar shuffling or missed meetings. Just hit record, explain your thing, and share the video with your team. They can watch it whenever suits them best, whether they're in Tokyo, New York, or anywhere in between. It's handy for maintaining productivity and ensuring everyone stays in the loop, no matter their work hours. Some Loom pros: - Async communication - Easy recording and sharing - AI tools for a video summary #### 6. Attio for customer relationship management ![Attio](/images/blog/how-to-build-remote-tech-stack/6.png) As your client base skyrockets through the roof (hopefully!), you’ll need a way to build relationships with your customers. At Appwrite, we use Attio for CRM. With [Attio](http://attio.com), we manage our [OSS](https://appwrite.io/blog/post/announcing-the-appwrite-oss-program) and [Startups programs](https://appwrite.io/startups), and keep all partnerships in one place, like creators, events and integrations. Its user-friendly design helps our team track and organize contacts effectively, as well as collaborate on campaigns with ease. Some Attio pros: - User-friendly - Customizable workflows - Real-time updates #### 7. GitHub team for open source contributions ![GitHub teams](/images/blog/how-to-build-remote-tech-stack/7.png) GitHub simplifies managing remote teams and permissions. With [GitHub Teams](https://github.com/team), you can organize team members, assign roles, and set specific access levels for repositories. This approach allows Appwrite to receive open-source contributions from developers around the world in a secure and efficient manner. Developers can suggest changes following our [contributing guidelines](https://github.com/appwrite/appwrite/blob/main/CONTRIBUTING.md), and our teams can quickly review and approve them. This process ensures that every issue undergoes a review before merging, helping to eliminate human error and maintain high-quality standards. Some Github Team pros: - Simple role and permission management - Scalability - Centralized control - Activity tracking #### 8. Appwrite for backend ![Appwrite](/images/blog/how-to-build-remote-tech-stack/8.png) We couldn't end this list without mentioning [Appwrite](https://appwrite.io/). When asked, "Do you use Appwrite to build Appwrite?" the answer is a yes. The team is building Appwrite with the exact same API we let developers use to develop their apps. Our entire backend, including user authentication, databases, and collections, is powered by Appwrite's easy-to-implement APIs. Appwrite enables you to build a robust backend in half the time and with fewer human resources. By handling complex and time-consuming tasks like authentication and database management, Appwrite lets you focus on the unique features that set your product apart. This also applies to the Appwrite team as we work to improve our products. Some Appwrite pros: - Versatile APIs - Robust and quality features - Self-hosted or cloud — your choice - Easy-to-use and understand ### Bonus tools Now, these tools are not strictly related to remote work. However, they have great privacy features and are integral to our workflows. #### Plausible for analytics ![Plausible](/images/blog/how-to-build-remote-tech-stack/9.png) [Plausible](http://plausible.io) is a privacy-friendly alternative to Google Analytics, which we opted for as part of [our commitment to data privacy](https://appwrite.io/blog/post/How-to-put-privacy-first). It is an open-source analytics platform with some of the highest [privacy standards](https://plausible.io/privacy). It helps us get demographic and user behavior data from the website and Appwrite Cloud in a privacy-friendly manner. Some Plausible pros: - privacy-friendly - user data is hosted on European servers - open source - user-friendly #### Dub.co for link-building ![Dub.co](/images/blog/how-to-build-remote-tech-stack/10.png) As a privacy-conscious company, we have stopped using marketing classified pixels such as webpage tags and cookies. As soon as you leave this website, we will lose all information about you. However, we still like to track the effectiveness of our campaigns. Dub.co, an open-source alternative to Bitly, allows us to create UTM links that track attributions from different campaigns, mediums, sources, and specific content. We can tell whether a visitor is coming from a specific link or not, which helps the growth team see if sponsoring a YouTube video or an event was effective. Some [dub.co](http://dub.co) pros: - Open-source - Straightforward pricing - Advanced analytics - Easy-to-use ### Moving forward Building a remote startup offers many perks, like tapping into the global talent pool, the ability to move fast, and achieve more. But it also comes with its own set of challenges. Starting with remote-friendly tools in your tech stack will help mitigate some of those challenges, resulting in an agile and smart startup and enabling faster scaling with fewer growing pains. If you’re a startup founder, you might be interested in the [Appwrite Startups Program](https://appwrite.io/startups), which provides free cloud credits and dedicated support to help you build your backend faster. Apply now and create the next big thing with Appwrite. More resources: - [How to attract users and contributors to your open-source project](https://appwrite.io/blog/post/how-to-attract-users-to-open-source-project) - [How to create a privacy-first growth strategy](https://appwrite.io/blog/post/How-to-put-privacy-first) --- ## How to build your digital event tickets https://appwrite.io/blog/post/how-to-build-your-digital-event-tickets Do you remember the Appwrite [Cloud cards](https://dev.to/appwrite/how-we-implemented-the-card-animation-in-appwrite-cloud-public-beta-4npb)? They were an absolute hit and filled our entire timeline for days. For Init, we wanted to create a new card, or better yet, a ticket to celebrate in style. So, we created three types of tickets that are unique to you with the help of your GitHub contributions and the tribe customization. As always, we want to share our learning so you can create your own tickets with GitHub OAuth and grid integration. ### Ticket design inspiration Let’s start with the inspiration for the tickets. A few team members went to GitHub Universe in November 2023 and received a unique, customizable event badge with a grid on the side. These physical [event tickets](https://twitter.com/didier_lopes/status/1724925458762936817) inspired our very own Init tickets that would convey the feeling of a real ticket to an event. We took this idea, made it our own, and created three different types of tickets that would celebrate Init. The different types are categorized into: - Init celebration - Developers building with Appwrite - Appwrite contributors Depending on your specific situation, you get a certain type of card that you can then customize and personalize. Here is an overview of the three design types. ##### Init celebration The Init celebration ticket is what everyone gets at sign-up. This is your starting point. ![Init celebration ticket](/images/blog/card1.png) ##### Building with Appwrite The Appwrite ticket is for all developers with an Appwrite account. They get a pink ticket with an extra pink glow. ![Init Appwrite dev ticket](/images/blog/card2.png) ##### Appwrite contributors Then, we have one very special ticket dedicated to contributors of Appwrite. If you have contributed to Appwrite in any way, you will get this special design. It’s a platinum card with a rainbow glow. ![Init contributors ticket](/images/blog/card3.png) ### Show off your GitHub contributions grid An essential part of the ticket is that we wanted to emphasize the importance of contributing to open source. What better way than showing off your actual contributions within your ticket? ![Init celebration ticket](/images/blog/tickets-blog.png) As you can see on each side of the ticket, a grid resembles your unique GitHub contributions over the past year, just like on your GitHub profile. You can choose to add the grid with a toggle, but to do so, you need to connect your GitHub account. Now, in case you would like to know how you can do this, we asked the engineer in charge, [Thomas G Lopes](https://github.com/TGlide), to explain the process. #### Associating your GitHub account with your ticket First, you need to associate your GitHub account with your Init ticket. Therefore, we need to create an authentication flow on our website. Fortunately, with Appwrite Cloud, it was quite easy to do so. By following our [documentation on OAuth](https://appwrite.io/docs/products/auth/oauth2), and creating an OAuth GitHub App, we're quickly able to create a login flow for Appwrite Init. When logging in for the first time, a new ticket is instantiated and saved to Appwrite Cloud. ![Init celebration ticket](/images/blog/tickets-blog2.png) #### Integrating the GitHub contributions grid Now that the association part is done, it’s time to integrate the more interesting part, the GitHub contributions grid. There are two ways to achieve this. The first one, is using the GraphQL API. [This article](https://medium.com/@yuichkun/how-to-retrieve-contribution-graph-data-from-the-github-api-dc3a151b4af) was initially used as a source of inspiration when implementing this feature. However, with this data, you only get the number of contributions but not the actual chart. In GitHub's chart, each square can have a level from 0 to 4, where each level is brighter than the other, indicating more contributions. We need a way to convert those numbers of contributions to levels, but unfortunately, that algorithm is not exposed by GitHub. We wanted the contribution chart to be as close to reality as possible. So, we arrived at our second solution: getting the chart from the source! Our website is developed with SvelteKit, which is a full-stack framework. TL;DR, we have control over both the server and the client. So, when someone requests their ticket, we can make the server directly access your GitHub profile page and read the chart! We used a library called `node-html-parser` to deal with the HTML data. We then can generate a matrix that representes the contribution chart. Each array of the matrix will represent a week, and each item of said array will contain the contribution level for that particular day. ```ts const res = await fetch(`https://github.com/${gh_user}`); const html = await res.text(); const root = parse(html); const table = root.querySelector('table'); if (!table) return null; const matrix: ContributionsMatrix = []; const rows = table.querySelectorAll('tbody tr'); const maxCols = rows[0].querySelectorAll('[role="gridcell"]').length; for (let c = 0; c < maxCols; c++) { matrix.push([]); for (let r = 0; r < rows.length; r++) { const row = rows[r]; const cells = row.querySelectorAll('[role="gridcell"]'); if (c >= cells.length) continue; const cell = cells[c]; matrix[c].push(Number(cell.getAttribute('data-level'))); } matrix[c] = matrix[c].reverse(); } ``` However, this operation is expensive! It's best if we don't have to always fetch it on each and every access to it. So we save it to Appwrite Cloud. By using a number array attribute and converting the matrix to a flat array, this is what our final code will look like. ```ts import { APPWRITE_DB_INIT_ID, APPWRITE_COL_INIT_ID } from '$env/static/private'; import { appwriteInit } from '$lib/appwrite/init'; import parse from 'node-html-parser'; import type { TicketData, ContributionsMatrix } from '../../constants'; export async function getContributions(id: string): Promise { const { gh_user, contributions } = (await appwriteInit.database.getDocument( APPWRITE_DB_INIT_ID, APPWRITE_COL_INIT_ID, id )) as unknown as TicketData; if (!gh_user) return null; if (contributions?.length) { // Transform flat array into matrix with 7 columns const matrix: ContributionsMatrix = []; for (let i = 0; i < contributions.length; i += 7) { matrix.push(contributions.slice(i, i + 7)); } return matrix; } // Code for fetching the matrix here // Update the document with the new contributions await appwriteInit.database.updateDocument(APPWRITE_DB_INIT_ID, APPWRITE_COL_INIT_ID, id, { contributions: matrix.flat() }); return matrix; } ``` With this, requests will be much faster. But we still care about the initial load, right? First impressions are everything. So, we adopted a smart strategy. When requesting the ticket, instead of waiting for all the data to load (the ticket data + the contributions) before rendering the page, we stream the contribution data. This is what our load function looks like: ```ts import { getTicketByUser, getTicketContributions, getUser, isLoggedIn } from '$routes/init/helpers'; import { redirect } from '@sveltejs/kit'; export const load = async () => { const loggedIn = await isLoggedIn(); if (!loggedIn) { redirect(307, '/init/ticket'); } const user = await getUser(); const ticket = await getTicketByUser(user); return { ticket, user, streamed: { contributions: getTicketContributions(ticket.$id, fetch) } }; }; ``` Then, with SvelteKit, we can show the contributions as soon as they're ready. ```svelte {#await contributions then c} {#if c && show_contributions}
    {#each c as row}
    {#each row as level, j}
    {/each}
    {/each}
    {/if} {/await} ``` This means that users can see the ticket without waiting for all data to load but still see the chart seamlessly added to the ticket. ### Your tribe Thomas explained the cool part of how we managed to make your ticket truly unique. But we also wanted to allow for customization so that you could also have a say in what your ticket looked like. For this, we chose to add your favorite technology's logo as a watermark. ![Init celebration ticket](/images/blog/tickets-blog3.gif) ### Create your own You can find the entire source code for the tickets on [GitHub](https://github.com/appwrite/console/blob/cloud-1.1.x/src/routes/card/Card.svelte) to create your digital event tickets. In May, we created a [showcase site](https://appwrite-card-snippets.vercel.app/popup) for the Cloud cards on how to add a pop, rotation, and glare to the cards. We advise you to check it out to make your tickets all the more magical. Although Init is over, you can still get yourself a ticket at [appwrite.io/init/tickets](/init/tickets) [Join us on Discord](https://appwrite.io/discord) to be the first to get updates and to be part of a vibrant community! --- ## How to plan and execute database migration successfully with the new Appwrite CLI https://appwrite.io/blog/post/how-to-execute-database-migration-with-appwrite-cli Database migration is a critical task in the lifecycle of any application. It involves making schema changes while ensuring that data remains intact, often to accommodate new features, improve performance, or ensure scalability. With the release of the new Appwrite CLI, the process of planning and executing database migrations has become significantly easier. The new CLI features allow you to manage your database schemas more efficiently, ensuring smooth transitions and minimal downtime. ### The new Appwrite CLI The new Appwrite CLI introduces several enhancements to improve the developer experience in managing database schemas. One of the standout features is the ability to use your `appwrite.config.json` file as the source of truth for your database collections' schema. This approach brings numerous benefits, including better change tracking, seamless migrations using GitOps, and simplified project replication. Let's explore these advantages in more detail: **1. Tracking changes** By using `appwrite.config.json` as the source for your collections schema, you can easily track changes over time. This file can be version-controlled with Git, allowing you to see a detailed history of modifications and collaborate effectively with your team. **2. Migrating database changes with GitOps** GitOps is a methodology that uses Git repositories to manage and deploy infrastructure changes. The new Appwrite CLI allows you to leverage GitOps for database schema changes, ensuring a smooth and consistent migration process across different environments. **3. Easy project replication** When you need to replicate a project, having the database schema defined in `appwrite.config.json` allows you to recreate the database setup effortlessly in different instances or environments, as well as in different projects within the same environment and instance. ### Step-by-step guide to database migration with the new Appwrite CLI Let's walk through the process of planning and executing a database migration using the new Appwrite CLI. #### Step 1: Pull existing database configuration If you already have a database setup, start by pulling the current configuration into your `appwrite.config.json` file. This step ensures that you have the latest schema as a baseline. ```bash appwrite pull collections --all ``` #### Step 2: Creating and pushing collections If you are starting from scratch, you can initialize a new collection and push it to Appwrite. 1. Create a new collection: ```bash appwrite init collection ``` 2. Push the collection to Appwrite: ```bash appwrite push collections ``` #### Step 3: Modifying collections To make changes to your collections, edit the `appwrite.config.json` file. For instance, you might want to change an attribute's type or add a new attribute. Here’s an example: ##### Original attributes ```json "attributes": [ { "key": "a", "type": "integer", "status": "available", "error": "", "required": false, "array": false, "min": 1, "max": 100, "default": null} ] ``` ##### Modified attributes ```json "attributes": [ { "key": "a", "type": "string", "status": "available", "error": "", "required": false, "array": false, "min": 1, "max": 100, "default": null}, { "key": "phone", "type": "integer", "status": "available", "error": "", "required": false, "array": false, "min": 1, "max": 100, "default": null} ] ``` #### Step 4: Pushing changes After making the necessary changes, push the updated schema to Appwrite: ```bash appwrite push collections ``` The Appwrite CLI will display a table summarizing all pending changes, allowing you to review them before applying. This ensures transparency and gives you a chance to verify changes before they take effect. ![Executing database migrations](/images/blog/how-to-migrate-database-with-cli/1.png) #### Step 5: Confirming changes To apply the changes, type `YES` to confirm. This step ensures that no changes are made accidentally and gives you full control over the migration process. In the case of CI/CD pipelines, you can use the --force flag to push the changes automatically; however, exercise caution when using this option. ### Conclusion The new Appwrite CLI makes database migration a more manageable and streamlined process. By using `appwrite.config.json` as your schema’s source of truth, you can take advantage of better change tracking, seamless GitOps-based migrations, and simplified project replication. Whether you are starting from scratch or managing an existing database, these features enhance your ability to plan and execute migrations successfully. - [Appwrite CLI docs](https://appwrite.io/docs/tooling/command-line/installation) - [Join our Discord server](https://appwrite.io/discord) - [More about the new Appwrite CLI](https://appwrite.io/blog/post/introducing-new-appwrite-cli) --- ## How to leverage Appwrite Dynamic Keys for enhanced security https://appwrite.io/blog/post/how-to-leverage-dynamic-api-keys-for-better-security Appwrite now features dynamic keys, significantly improving how you manage API keys within your projects. These keys (also known as ephemeral keys) are designed to enhance security and facilitate easier local development and environment setup. As part of the Appwrite Functions ecosystem, they reduce the need for manual key management and rotation, making your applications (and functions!) easier to maintain. This guide will walk you through understanding, setting up, and effectively using dynamic API keys in your projects. ### New additions and syntax in Appwrite With Appwrite 1.6 comes two important variables: #### APPWRITE_FUNCTION_API_ENDPOINT: Appwrite now automatically provides the API endpoint as an environment variable during function execution, ensuring your function always uses the correct API endpoint. This eliminates the need to manually set the endpoint in your function code or environment variables. Along with the existing `APPWRITE_FUNCTION_PROJECT_ID` variable, this setup eliminates the need to manually set the endpoint and project ID in your function code or environment variables, making the integration seamless. #### req.headers['x-appwrite-key'] This variable contains the dynamic API key generated for each function execution. With this key, you no longer need to manually generate or manage API keys for your functions. Additionally, you can set the scopes for this key in the Appwrite Console under function settings. These additions simplify the process of setting up and maintaining secure API access for your functions. In the following sections, we'll discuss dynamic API keys in more detail, with examples of how to use them in your projects. ### What are dynamic keys? Dynamic API keys are short-lived API keys that Appwrite automatically generates for each function execution. These keys are unique to each function run and have specific scopes which enhances security by reducing their lifespan and exposure. In contrast, long-lived keys pose a higher risk if compromised. ### How dynamic keys differ from standard keys Ephemeral keys and standard keys differ in several fundamental ways. Standard API keys are manually created and long-lived, meaning they require regular rotation and diligent management to ensure security. In contrast, dynamic API keys are automatically generated per execution and are short-lived, expiring after the function completes. This automatic generation means there is no need to embed these keys in environment variables manually, reducing setup complexity and maintenance. While we generally recommend using ephemeral keys, some third-party services may require long-lived keys, or may not support Appwrite's dynamic key generation. In such cases, you can still use standard API keys, but use caution and ensure proper key management practices. ### Benefits of ephemeral keys Dynamic API keys offer several advantages: 1. **Enhanced security:** The short-lived nature of dynamic API keys minimizes the risk of key exposure and misuse. Each key is unique to a specific function execution, reducing the attack surface. 2. **Simplified management:** Since these keys are automatically generated and do not require manual rotation or embedding in environment variables, the administrative overhead is significantly reduced. 3. **Granular control:** Each ephemeral key can be assigned specific scopes tailored to the function's overall needs. This ensures that functions only have the permissions they require, adhering to the principle of least privilege. 4. **Consistency in development and production:** In Appwrite, dynamic keys respect the scopes set in `appwrite.config.json`, ensuring that the behavior is consistent across both local development and production environments. ### Setting up and using dynamic API keys #### Step-by-step guide **1. Initial setup:** To begin, create and configure your function in the [Appwrite console](https://appwrite.io/docs/tooling/command-line/functions) or locally using the [Appwrite CLI](https://appwrite.io/docs/products/functions/develop-locally). **2. Configuring scopes:** Scopes control the permissions of the dynamic API key. You can define the necessary scopes for your function in the Appwrite console. This ensures that the function only has access to the required resources. To define scopes in the Appwrite console, navigate to your function's settings and scroll down to the "Scopes" section. Here, you can select the required scopes for your function. ![Dynamic-keys-Appwrite](/images/blog/how-to-leverage-dynamic-api-keys-for-better-security/1.png) **3. Writing the function code:**  In the old approach, the API endpoint and API key were typically hardcoded within the function. You would manually specify the endpoint URL and use a static API key stored in environment variables like this: ```jsx const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Hardcoded API endpoint .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY) // Static API key ``` While this approach still works, you can now leverage dynamic keys for better security and ease of use. To do this, update your function code to use the new environment variable for the API endpoint and obtain the dynamic API key from the request headers. Here's how you can modify your function code to use dynamic API keys: ```jsx const client = new Client() .setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(req.headers['x-appwrite-key']) ``` In the above example, the environment variable `APPWRITE_FUNCTION_API_ENDPOINT` is used for the endpoint, and the API key is obtained from `req.headers['x-appwrite-key']`. **4. Deploying and testing the function:** [Deploy your function](https://appwrite.io/docs/products/functions/functions) through the Appwrite console or the Appwrite CLI. Test the function by calling its endpoint and verifying that it performs the intended operations, such as creating a new document in the database. #### Using dynamic API keys in local functions Dynamic API keys also work when running functions locally and respect the scopes set in `appwrite.config.json`. Here's our guide on [running functions locally](https://www.appwrite.io/docs/products/functions/develop-locally). If you’ve made changes to your project in the Appwrite console, you can easily synchronize your local functions by running `appwrite pull functions` on your command line. This ensures that the same scopes are applied when running the function locally. If you want to deploy your function afterwards, you can run the command `appwrite push functions`. ### Conclusion Dynamic API keys in Appwrite provide a more secure, manageable, and efficient way to handle API access in your functions. By leveraging automatic key generation and specific scopes, you can ensure your applications are both secure and easy to manage. If you haven't already, upgrade your Appwrite CLI and SDKs to the latest version to take advantage of our new features. Further reading and resources: - [Serverless functions 101: best practices](https://appwrite.io/blog/post/serverless-functions-best-practices) - [Appwrite Functions Documentation](https://appwrite.io/docs/functions) - [Join the Appwrite Community on Discord](https://appwrite.io/discord) - [More about Init](https://appwrite.io/init) --- ## How to optimize your Appwrite project for cost and performance https://appwrite.io/blog/post/how-to-optimize-your-appwrite-project Updated on October 6, 2025 As your Appwrite project scales, performance and resource management become critical, not just to keep your app running smoothly, but also to avoid unexpected usage spikes. While Appwrite is designed to scale effortlessly, it's important to proactively optimize your usage so you don't reach resource limits prematurely. Effective optimization helps reduce costs, improves performance, and enhances user experience without compromising on functionality. This guide provides a detailed overview of techniques to optimize your Appwrite project, covering API request management, caching strategies, file compression, image optimization, and other methods to ensure your app performs well while staying within your desired resource limits. ### 1. Reducing API requests: Smart querying and consolidation Every API request consumes resources, and when requests are made inefficiently, they can consume substantial resources and increase usage costs. Here are strategies to help reduce unnecessary API calls: #### Efficient use of listDocuments with queries In Appwrite, fetching multiple documents from a database is made easy with the [listDocuments](https://appwrite.io/docs/references/cloud/client-web/databases#listDocuments) method. This method allows you to retrieve all documents in a collection or apply filters using the Query API. Instead of making multiple requests for each document, you can fetch only the relevant data with a single request. For example, in a content management system where you need to load blog posts written by a particular author, rather than making individual requests for each document, use Appwrite's `listDocuments` method with a filter: ```javascript const blogPosts = await databases.listDocuments({ databaseId: '', collectionId: '', queries: [Query.equal('author', 'Michael Scott')] }) ``` By fetching all the documents you need in one go and applying filters, you reduce the number of requests and improve performance. However, as we'll see in the next section, you should use the `listDocuments` method carefully to avoid fetching unnecessary data, else you might end up with a large dataset that could slow down your application. #### Optimize pagination and filtering For applications with large datasets (e.g., e-commerce platforms with thousands of products), retrieving all data at once can overwhelm your system. Use Appwrite's pagination and filtering feature to only fetch what's immediately needed. For example, if you're loading products from a database, instead of fetching all products, fetch them in batches using pagination. ```javascript ```javascript const firstPage = await databases.listDocuments({ databaseId: '', collectionId: '', queries: [ Query.equal('author', 'Meredith Palmer'), Query.limit(20), Query.offset(0),// Fetch the first 20 records ] }) const secondPage = await databases.listDocuments({ databaseId: '', collectionId: '', queries: [ Query.equal('author', 'Meredith Palmer'), Query.limit(20), Query.offset(20), // Fetch the next 20 records ] }) ``` ``` This strategy will not only conserve bandwidth but will ensure that your queries return faster results, improving your user experience. For more information on querying documents, the [Appwrite Queries documentation](https://appwrite.io/docs/products/databases/queries) provides detailed examples and options. #### WebSockets for real-time data When building real-time applications (e.g., chat apps or collaborative tools), polling APIs frequently can result in unnecessary usage and increased server load. Appwrite's [Realtime API](https://appwrite.io/docs/apis/realtime) solves this by allowing you to subscribe to events and receive updates in real time via WebSockets. This ensures that your app remains responsive without the need for constant polling. For example, you can subscribe to file upload events in real-time: ```javascript const unsubscribe = client.subscribe('files', (response) => { if (response.events.includes('buckets.*.files.*.create')) { console.log('New file uploaded:', response.payload) } }) ``` In addition to handling real-time updates, Appwrite's Realtime API can help decide when further requests are needed. Instead of continuously fetching data to check for updates, you can create a versioning system. 1. **Versioning Document**: Create a document with a single column, `version`, to track updates. 2. **Subscribe to Changes**: Use the Realtime API to monitor changes in this document. If the `version` field updates, it signals that the data has changed and needs re-fetching. Appwrite's Realtime API supports multiple channels, such as `files`, `account`, and specific `collections`, ensuring updates from any part of your app. To learn more about the Realtime API, refer to the [Appwrite Realtime documentation](https://appwrite.io/docs/realtime). By using Appwrite's real-time feature, you drastically reduce API request overhead, especially for high-traffic apps like live dashboards or messaging platforms, while maintaining instant, event-driven updates. ### 2. Effective caching: Reducing redundant data fetching Caching is one of the most important strategies for improving both performance and resource management in your Appwrite project. By caching frequently accessed data, you reduce the number of redundant requests sent to your Appwrite instance. #### Client-side caching with localStorage or IndexedDB For non-sensitive or rarely changing data, client-side caching can significantly reduce server load and improve application responsiveness. Use the browser's `localStorage` or `IndexedDB` to store data that users frequently access, such as user settings, app configurations, or product lists. For instance, in a to-do list app, user preferences (e.g., preferred task view) can be cached locally so that the app doesn't need to retrieve this data from Appwrite on every page load. ```javascript // Cache user preferences in localStorage localStorage.setItem('userPreferences', JSON.stringify(preferences)) // Retrieve user preferences from cache const cachedPreferences = JSON.parse(localStorage.getItem('userPreferences')) ``` This reduces unnecessary API requests and allows for instant access to cached data, improving performance. #### Optimized cache expiry and invalidations Caching data on the client side is effective, but it must be combined with intelligent cache expiry strategies to avoid serving stale data. Ensure that the cache is invalidated and refreshed when necessary, such as when the data changes or after a certain time period. ```javascript // Cache with expiration (using localStorage) const saveToCache = (key, data) => { const item = { data, expiry: Date.now() + 1000 * 60 * 60, // Cache for 1 hour } localStorage.setItem(key, JSON.stringify(item)) } const getFromCache = (key) => { const cachedItem = localStorage.getItem(key) if (!cachedItem) return null const item = JSON.parse(cachedItem) if (Date.now() > item.expiry) { localStorage.removeItem(key) // Invalidate stale cachereturn null } return item.data } ``` This ensures your users get up-to-date data while reducing the number of requests made to the server. #### Server-side caching with Redis Redis is a widely-used, high-performance in-memory data store that can be an excellent choice for server-side caching in your Appwrite projects. Redis can be deployed alongside your Appwrite backend to cache frequently accessed data, reducing the load on your Appwrite instance and speeding up response times for users. For example, in an e-commerce application, you could cache product categories, prices, or frequently queried data in Redis. This way, instead of hitting the database for every request, your application can retrieve data directly from Redis, which significantly improves performance for repeated requests. You can learn more about Redis in the [Redis documentation](https://redis.io/documentation). ### 3. Optimizing file storage: Compression and efficient file delivery Managing large files can be a significant source of resource consumption. Optimizing file storage and delivery can help keep storage costs down and ensure faster data transfers. #### Bucket compression One of the most important optimizations for file storage is **bucket compression**. Appwrite allows you to compress files in your buckets using either the `gzip` or `zstd` compression algorithms. Enabling compression reduces the file size and conserves storage space, which is particularly useful for large files and media-heavy apps. - **Gzip** is a widely-used compression algorithm that offers good compression ratios for many types of files. - **Zstd** (Zstandard) is a modern, high-performance compression algorithm that provides better compression efficiency and decompression speed compared to gzip, making it a great choice for high-performance applications. To enable compression for a bucket, you can set this up via the Appwrite Console or programmatically using the SDK: ```javascript const promise = storage.createBucket({ bucketId: 'bucketId', name: 'bucketName', compression: 'zstd' }); ``` Keep in mind that files larger than **20MB** are not compressed, even if compression is enabled. This is done to prevent performance bottlenecks when handling large files. Enabling compression at the bucket level not only helps reduce storage usage but also speeds up file transfers by reducing the amount of data sent over the network. #### Optimizing image delivery with the Preview API Appwrite allows you to serve compressed and optimized versions of your images to reduce bandwidth usage and improve performance. You can use the preview endpoint to adjust image quality, resolution, format, and more, without needing to modify the original file. This is useful for serving responsive images on the web, where you might want to reduce file sizes for faster load times. ```javascript // Serve an optimized image preview const optimizedImage = storage.getFilePreview({ bucketId: 'bucketId', fileId: 'fileId', width: 800, gravity: 'center', quality: 80, output: 'webp' }) ``` While this example shows how to serve an optimized image preview, and will greatly improve load times, it will not affect the original file size in your storage. So if you want to save storage space, consider compressing and optimizing files at the time of upload. A good practice for image optimization is to use formats like `WebP`, which offer better compression than JPEG or PNG. #### Optimizing file delivery with CDNs and regional project deployment For improved file delivery performance, especially in media-heavy applications, you can leverage a domain-level CDN like **Cloudflare**. A CDN caches files and serves them from locations closer to your users, reducing load times and improving the overall experience without overburdening your Appwrite server. In addition, deploying your Appwrite project in a region near your user base can further reduce latency. Currently, Appwrite supports the **FRA (Frankfurt) region**, which is great for users in Europe. We're also expanding to more regions soon, giving you even more options to optimize performance based on your audience's location. By combining CDNs and regional project deployments, you can enhance file delivery speeds, reduce latency, and ensure a more responsive user experience. ### 4. Using serverless functions for optimized workflows Serverless functions in Appwrite are a powerful tool for optimizing resource usage. Instead of handling complex, resource-heavy operations on the client or in your core backend, offload these tasks to serverless functions that can execute asynchronously and on-demand. Tasks like generating reports, processing data, or transforming files can be offloaded to serverless functions. This approach frees up the client and backend from performing expensive computations and ensures that heavy workloads don't slow down your core application. ### 5. Setting up usage limits, alerts, and budget controls Proactively managing your resource usage in Appwrite is key to avoiding unexpected overages and optimizing costs. Appwrite offers built-in tools for setting up usage alerts and budget controls to help developers manage their consumption. #### Budget controls You can also enable budget alerts to monitor your spending and prevent unexpected charges. For teams managing larger projects or multiple clients, setting budget limits ensures you stay within financial expectations. To configure budget alerts, start by navigating to the **Appwrite Cloud Console**, then go to the **Organisation** section and select **Billing**. Here, you'll find options to manage your budget. You can set a **budget cap** to define a hard limit on your spending, and in the **Budget Alerts** section, you can enable notifications that trigger when your spending reaches a certain threshold. This ensures you are alerted before exceeding your desired budget. ![Budget controls in Appwrite Cloud Console](/images/blog/how-to-optimize-your-appwrite-project/budget-controls.png) ### 6. Monitoring and scaling your Appwrite project Appwrite handles scalability efficiently, particularly in its cloud environment. Appwrite Cloud automatically scales resources as needed, ensuring that your application can handle increased traffic or workloads without requiring manual intervention. This automatic scaling allows you to focus on building your app, while Appwrite dynamically adjusts the resources behind the scenes to maintain optimal performance during traffic spikes. While this is a significant advantage of using Appwrite Cloud, self-hosted instances also provide flexibility for custom scaling configurations. By monitoring your Appwrite project's resource usage, you can identify bottlenecks, optimize your app, and scale horizontally as needed. #### Automatic scaling in Appwrite Appwrite's infrastructure is built on top of Docker, which allows for containerized scaling. Each service in Appwrite is containerized, meaning services like databases, storage, or functions can be scaled horizontally as demand increases. This happens automatically in the cloud version of Appwrite, making it easier for developers to handle surges in user traffic without experiencing downtime or performance degradation. For self-hosted Appwrite, scaling is also possible but requires setting up and managing Docker containers manually. In Appwrite's cloud environment, you don't have to worry about manually configuring load balancers or managing server instances to scale your application. Appwrite automatically distributes workloads, ensuring smooth operation regardless of traffic volumes. #### Monitoring resource usage Appwrite provides built-in monitoring to help track API usage, database operations, storage, and other key metrics. This monitoring allows you to identify potential inefficiencies, such as high API request volumes or unnecessary data fetches, before they escalate into larger issues. While Appwrite handles automatic scaling, regular monitoring can help you optimize your application to avoid overuse of resources and prevent unnecessary costs. You can monitor your Appwrite project's performance via the dashboard, which gives insights into your API request count, database operations, and storage usage. This data helps you manage your plan limits more effectively and optimize your app to avoid unexpected scaling needs. ![Monitoring resource usage in Appwrite dashboard](/images/blog/how-to-optimize-your-appwrite-project/resource-monitoring.png) #### Horizontal scaling For self-hosted environments, Appwrite can be scaled horizontally by replicating containers across multiple servers. Stateless containers like those running functions or workers can be easily replicated to distribute workloads. However, for stateful services like databases, manual scaling configurations may be required. Horizontal scaling ensures that the load is distributed evenly across multiple instances, making your application more resilient during high-traffic periods. ### Conclusion Optimizing your Appwrite project ensures you not only provide a better experience to your users but also prevent unnecessary resource overuse. By applying the techniques discussed in this guide, you can keep your project running smoothly while staying within your plan limits. These optimizations aren't just about cost savings—they are about making your app more scalable, efficient, and reliable as it grows. Rather than waiting until you hit plan limits, taking a proactive approach builds trust and ensures your users always experience the best possible performance, even as your app scales. ### Frequently asked questions (FAQs) **1. How can I reduce API request usage in Appwrite?** Use smart querying with `listDocuments()` and filters instead of multiple requests. Combine queries, enable pagination, and use the Realtime API for live updates instead of polling. This reduces server load and speeds up responses. **2. What’s the best way to cache data in an Appwrite app?** Cache frequently accessed or static data using localStorage or IndexedDB on the client, and use Redis for server-side caching. Set expiry rules to prevent stale data and limit unnecessary API calls. **3. How do I optimize file storage and image performance?** Enable bucket compression (gzip or zstd) and serve images using the Preview API for resizing or converting to formats like WebP. For global apps, add a CDN to deliver files faster and reduce server strain. **4. Can Appwrite automatically scale my project?** Yes. Appwrite Cloud handles automatic scaling for traffic spikes and workload increases. For self-hosted setups, you can scale manually using Docker containers to distribute services across multiple servers. **5. How can I prevent hitting resource or budget limits?** Set usage alerts and budget controls in the Appwrite Cloud Console. You can define spending caps and get notified before reaching limits, helping you manage resources proactively and avoid unexpected overages. ### Further reading and resources: - [Serverless functions 101: Best practices](https://appwrite.io/blog/post/serverless-functions-best-practices) - [Appwrite Queries documentation](https://appwrite.io/docs/products/databases/queries) - [Appwrite Buckets documentation](https://appwrite.io/docs/products/storage/buckets) --- ## How to create a privacy-first growth strategy https://appwrite.io/blog/post/How-to-put-privacy-first With our recent announcement of GDPR compliance, we took a big step in becoming a privacy-first organization. But we didn’t want to stop here. We wanted to take it further than just ticking the privacy boxes. We wanted to act as a party that values your personal data, so we needed to take action and move to privacy-friendly analytics. This led to removing most of the tags, pixels, and cookies on our website and the Appwrite Console. In this blog, we dive deeper into what we removed, how this affected our growth strategy, and, most of all, what we, as a community, gained from this decision. #### But first, what are pixels, tags, and cookies? **Pixels**, **tags**, and **cookies** are all pieces of technology used to [track user activity online.](https://www.osano.com/articles/privacy-cookies-pixels-and-tags) They are often used for marketing and advertising purposes but can also be used to improve website functionality and user experience. - **Tags** are snippets of code added to a website to track your activity and collect data. For example, a tag can be used to track how many people visit a particular page on a website or to collect information about what products you are viewing. The most well-known tool for this is [Google Tag Manager](https://www.semrush.com/blog/beginners-guide-to-google-tag-manager/). It allows you to manage all of your website tags in one place. - **Pixels** are small, transparent images that are embedded in a website. When you visit a website with a pixel, your browser sends information about your visit to the company that owns the pixel. This information can include the user's IP address, browser type, and the URL of the page you’re visiting. It is a beneficial tool to retarget website visitors with banner or social ads. - **Cookies** have two different types: first-party, which are set by the website you’ve visited mainly to improve your experience on that website, and third-party, which are set by different websites, such as Google Analytics. Third-party cookies are often used to track you across platforms, mostly meant for advertising purposes. ![Appwrite is GDPR compliant](/images/blog/gdpr.png) #### So, what did we remove? If you visit the Appwrite Cloud Console or website, you will see a cookie consent banner. However, you will see only two options for you to pick from, `Strictly necessary cookies` and `Product analytics.`We have removed all of our `Marketing` classified pixels, tags, and cookies such as: - HubSpot tag - Google Tag Manager - Twitter Pixel - Facebook Pixel - Google Ads Pixel - Google Analytics Pixel Most were used to get website analytics, analyze website clicks, measure ad performance, and collect form submissions. #### The consequences of removing marketing pixels, tags, and cookies The above mentioned allowed us to analyze user behavior, measure the effectiveness of ad campaigns, retarget ads, and, to some extent, personalize the user experience. There are hardly any growth or marketing teams that don’t rely on them, and as stated in an [article from Osano](https://www.osano.com/articles/marketer-friendly-privacy-software), privacy regulations make it a lot harder for growth and marketing teams to do their work. Go to any privacy footer on any website, and you will read an essay on all the data that is being tracked. But this is precisely what we are challenging: does removing pixels, cookies, and tags make growth’s life harder? Let’s take a look. - **Attribution**: It can be challenging to attribute conversions to specific marketing campaigns without the ability to track users across different devices and websites. This can make it challenging to know which campaigns drive the most results and make informed decisions about future campaigns. - **Segmentation**: Without the ability to track user behavior, preferences, and interests, it is challenging to segment users into different groups based on their interests and preferences. This can make delivering targeted messaging and experiences difficult for different user segments. - **Reporting**: It can be difficult to generate comprehensive reports on marketing performance without the ability to track users across different devices and websites. This can make it challenging to track progress over time and identify areas for improvement. - **Ad targeting and Retargeting:** Pixels and cookies enable precise ad targeting, allowing you to show ads to users who have shown interest in your products or services. Retargeting, which involves showing ads to users who have previously interacted with your website, becomes impossible without these tracking mechanisms. - **Competing:** For some, competition might also drive them towards using the tracking to stay or get ahead. You could fall behind if your competition has more information and data to leverage. Without pixels, cookies, and tags, you'll be at a significant disadvantage compared to competitors who can leverage data-driven insights. So, fear could be a legitimate driver. If you want to use data for decision-making or personalizing the user experience, removing tags, pixels, and cookies might not be your first choice. But as mentioned, we are here to challenge this idea. Do you really need to track all this personal data to do your work in growth? We think not. Since there are many privacy-friendly options for getting the data and insights you need, and you can adjust your growth strategy to match a privacy-friendly approach. So yes, you can run a successful growth team despite the above challenges. So, let’s look at what this looks like for Appwrite. #### Privacy-friendly alternatives for your growth stack Our growth philosophy and rules enable us to grow without relying on tracking you across the web. To give you some insight, here are three (of the six) growth statements we follow for success: - To center gaining access, knowledge, and information about Appwrite as the most crucial goal to lead to growth. - To let information flow freely for developers to find. (accessibility is key) - To use paid advertising and sponsorships only to amplify the reach of knowledge and information. (aka creating awareness) The above can simply be conveyed as having a good content and social media strategy, and the success of this can be analyzed in a privacy-friendly way. We can measure success in metrics like traffic, impressions, social reach, new accounts, etc. Here are some ways we keep the team informed: - **UTM data** helps us track attributions from different campaigns, mediums, sources, and specific content. We can tell whether a visitor is coming from a specific link or not, for which we currently use [dub.co](https://dub.co/), an open-source alternative to Bitly. This helps us determine whether sponsoring a newsletter was effective or if a piece of content brought us traffic. But that is about as far as we will go. As soon as you leave our website, we lose all your information and would like to keep it that way. - **Open source analytics alternatives.** We use privacy-friendly alternatives like [Plausible](https://plausible.io/about) to help us analyze our website and Cloud Console data. It is an open-source analytics platform with one of the highest [privacy standards](https://plausible.io/privacy) I have seen. It helps us get demographic and user behavior data in a privacy-friendly manner. - **First-party data.** We get a lot from users, like emails and names from authentication. We use it for login purposes, so you don’t have to enter your login details over and over again and add them to our CRM, HubSpot, for email communications. But with this data also comes great responsibility, and with our recent GDPR compliance announcement, we are not only upholding strict data privacy but also are certified with the European General Data Protection Regulation (GDPR) standards. - **Content performance.** As mentioned, you should be able to find information on Appwrite without any limitations. Many companies have gated content, where they set up forms to collect your data for you to get information or reports. We believe this restricts you from learning freely. Therefore, we focus on the performance of our content and see metrics such as `page view` and `time on page` as a measurement of success. - **Search.** Many people search the internet daily, looking for solutions, and even after the introduction of ChatGPT, this is still the case. So, keeping track of our search results is another indicator of our tactics' success, as it’s a great indicator of interest shown in Appwrite and the industry. For now, we use Semrush and Google Search Console to give us this data, but we are interested in learning about open-source tools in this area. To return to our growth philosophy, we believe the dev community will find the information they need on their terms. We know that the developer journey is not one straight line and that people will find information on their preferred platforms when needed. We just need to ensure that we have content ready for you to find wherever you are on your journey. This instinctively eliminates the idea of running ads across platforms to convince you to use Appwrite. We merely use paid media or sponsored newsletters to let you know we exist, making pixels, cookies, and tags redundant in most cases. But there is more to gain than just privacy-friendly data. #### Building experience and trust with the developer community As we often repeat, we are heavily focused on the developer experience. It is at the core of everything we do. Considering this, there are several reasons why we decided to remove most of the tags, pixels, and cookies from our website: - **Privacy:** As many studies show, people want to [control the information they share](https://venturebeat.com/data-infrastructure/report-94-of-consumers-want-control-over-the-information-they-share-with-companies/) with other parties online. By removing most of the tags, pixels, and cookies, we reduce the amount of personal data we collect from the Appwrite website and Cloud Console. And as we focus on collecting first-party data, we grant you that control. - **Performance:** Tags, pixels, and cookies can slow down a website's loading time. By removing them, we improve the performance of our website and the Cloud Console for you. And a [study from Akamai](https://www.akamai.com/newsroom/press-release/akamai-releases-spring-2017-state-of-online-retail-performance-report), shows that a two-second delay in web page load time increases bounce rates by 103 percent. So, improving performance is worth it. - **Security:** This tracking technology can also track you across the web and collect intrusive data about you. By removing them, we are making our website more secure for you and preventing potential [data breaches](https://venturebeat.com/security/report-multiple-data-breaches-common-in-past-year/). Overall, it made much sense to go down this route and focus on delivering a better experience and enhancing trust with the community. And I think it’s fair to say that with the removal of marketing pixels, cookies, and tags, we didn’t lose anything; we merely gained. #### Building a privacy-friendly and open-source growth stack We still have the necessary tools to improve our product, the developer experience, and analyze our website traffic. That said, we will continue to explore the possibilities of moving towards a fully open-source and privacy-friendly growth tech stack. Not only do we feel it is the right thing to do, but it is also rooted in our culture. We want to treat you as we want to be treated by any vendors we use. For now, this has been the most sensible decision we could have made for Appwrite and the community. We are excited to take privacy further and further, so who knows where we will stand a year from now. --- ## How to reduce cloud latency https://appwrite.io/blog/post/how-to-reduce-cloud-latency Whether users are streaming a video, loading a webpage, or interacting with an app, they expect things to work fast. One key factor that affects the speed of cloud services is *cloud latency —* the delay between a user's request and the cloud's response. But why does this matter, and how can reducing latency make your apps and services faster and more efficient? In this blog, we'll break down the importance of reducing latency, explore practical ways to achieve it, and explain why faster web experiences are vital for both users and businesses. ### What is cloud latency? Cloud latency refers to the delay or time it takes for data to travel between a user's device and the cloud server. This includes the time it takes for a user request to reach the server and the time it takes for the server to respond. High latency can cause slow load times, buffering, and a poor user experience, especially for applications like online gaming, video streaming, or real-time data processing. ### Why you need to make your app faster Speed isn't just a nice-to-have feature — it's a necessity. If your app is slow, it's likely losing users, conversions, and productivity. Here's why making your app faster should be a top priority: 1. **Users won't wait**: People expect instant responses from apps and websites. If your app is slow, users will quickly get frustrated and may leave for faster alternatives. Reducing latency ensures a smooth, satisfying experience that keeps users engaged. 2. **Boost your revenue**: In e-commerce, speed directly affects sales. Faster apps mean higher conversion rates, allowing users to browse, make decisions, and complete transactions faster. Every second of delay could be costing you customers and revenue. 3. **Wider accessibility**: Not everyone has access to fast internet or high-end devices. By reducing latency, you make your app more accessible to users in varying conditions, creating a better experience for everyone. 4. **Improved productivity**: In a world dominated by cloud services, faster apps mean quicker collaboration, faster access to resources, and higher overall productivity. Whether your users are sharing files or managing data, speed is essential for efficiency. ### The real impact of reduced latency Lowering latency has real, tangible benefits for people using your services every day. Let's explore how reduced latency affects your users in practical ways: 1. **Smoother UX**: When latency is low, apps load quickly, videos stream smoothly, and interactions feel immediate. For customers, this translates into a better overall experience. Whether you're checking your bank balance or browsing products in an online store, responsiveness makes all the difference. 2. **Better engagement**: Research shows that even slight delays can cause users to abandon websites. For example, if a page takes more than 3 seconds to load, nearly half of the users will leave. Reduced latency helps keep users engaged, minimizing drop-off and improving retention rates. 3. **More reliable real-time applications**: For everyday tools like video calls, online gaming, and real-time collaboration software, low latency is essential. High latency can cause lag, resulting in dropped frames, awkward delays, or even miscommunication during video chats. By lowering latency, these real-time applications become much more dependable and enjoyable to use. 4. **Better search engine rankings**: Search engines like Google consider page load times when ranking websites. Faster websites with lower latency tend to rank higher, increasing visibility and attracting more visitors. ### How to reduce cloud latency Reducing cloud latency requires a combination of infrastructure improvements and strategic decisions about how and where your data is stored. Here are two effective methods for minimizing latency: #### 1. Use a Content Delivery Network A **Content Delivery Network (CDN)** is a system of distributed servers that deliver content to users based on their geographic location. CDNs reduce latency by caching data closer to the user, which shortens the distance data must travel. This is especially effective for media-heavy websites or applications that serve users across different regions. - **How it works**: When a user requests data (like loading a webpage or a video), instead of pulling it from a distant server, the CDN retrieves it from the closest available location, speeding up the process. - **Why it matters for customers**: By shortening the distance data has to travel, CDNs drastically reduce loading times. For users, this means faster access to websites, streaming without interruptions, and quicker interactions with apps. #### 2. Choose the right cloud region Choosing the right **cloud region** is another important step in reducing latency. Most cloud providers, like AWS, Google Cloud, and Microsoft Azure, offer multiple regions across the globe. By selecting a cloud region that is physically closer to your users, you can significantly reduce the time it takes for data to travel between your server and end-users. - **How to do it**: Analyze where your users are located and select cloud regions close to them. For example, if most of your users are in the US, hosting your data in a European data center will lower latency compared to hosting it in a European-based server. The Appwrite Network has recently expanded to include regions in New York and Sydney so you can cover majority of the globe and choose the server closest to your userbase. - **Why it matters for customers:** Hosting in the right cloud region reduces wait times, ensuring quicker response. This localized approach improves the user experience, making it seamless regardless of where customers are located. ### Benefits of increased distribution with global cloud regions The more globally distributed your cloud infrastructure is, the better performance you can offer your users. Global cloud regions allow you to place your services closer to where your users are, improving speed, reliability, and user satisfaction. - **Faster access for global users**: The internet is global, and your users could be anywhere in the world. Increased distribution across cloud regions allows each user to connect to the server nearest to them. This means that whether a customer is in New York, London, or Tokyo, they'll experience the same fast, reliable service. - **Better uptime and reliability**: Distributing your services across different regions also adds a layer of resilience. If one server or region experiences downtime, another region can take over without users even noticing. For day-to-day users, this ensures fewer interruptions and greater reliability, even during peak times. - **Scalability and resilience**: Global cloud regions enable better scaling of services. If your platform experiences increased demand in a certain region, you can easily add more servers in that location without overloading your infrastructure. ### Conclusion Cloud latency plays a huge role in the performance of modern web applications and services. By reducing latency through CDNs, choosing the right cloud regions, and increasing distribution across global cloud regions, you can create faster, more reliable services that enhance user experience and engagement. Ultimately, prioritizing speed ensures that your app not only meets but exceeds user expectations. ### Further reading - [Why multi-cloud is taking over](/blog/why-multi-cloud-is-taking-over?doFollow=true) - [How to optimize your Appwrite project for cost and performance](/blog/post/how-to-optimize-your-appwrite-project?doFollow=true) - [How to stop unexpected cloud bills before they happen](/blog/post/budget-caps-stop-unexpected-cloud-bills?doFollow=true) - [The Appwrite Network docs](/docs/products/network?doFollow=true) --- ## How to set up Sign in with Apple https://appwrite.io/blog/post/how-to-set-up-sign-in-with-apple Authentication is a critical aspect of building secure applications. It protects user data and ensures that only authorized users can access your services. One of the most privacy-conscious authentication methods available today is **Sign in with Apple**. With its built-in security features and minimal data-sharing approach, it has become a popular choice for developers looking to integrate seamless authentication. In this guide, we'll walk through the full process of implementing **Sign in with Apple** using **Appwrite**, a backend server for building secure, scalable applications. We'll cover everything from configuring Appwrite for OAuth2 authentication to setting up your Apple Developer account, followed by examples for various platforms like web, Android, iOS, and Flutter. By the end of this guide, you'll have a complete understanding of how to integrate Apple Sign-In into your application. ### Understanding OAuth2 and how Appwrite handles authentication Before jumping into the implementation, it's important to understand the basics of [OAuth2](/docs/products/auth/oauth2). **OAuth2** is an open standard for authorization that allows third-party services to grant limited access to user accounts without exposing passwords. This makes it ideal for integrating with platforms like Apple, Google, and Facebook. In an OAuth2 flow, users are redirected to the authentication provider (in this case, Apple), where they authorize your application. The provider then returns a token that your backend can use to authenticate the user. Appwrite simplifies this process by managing the entire OAuth2 flow, including token handling and session management, so you can focus on the user experience. Appwrite's built-in support for OAuth2 allows you to enable various third-party login providers quickly, including **Sign in with Apple**. With a few configuration steps, you can leverage OAuth2 for secure, privacy-first [authentication](/docs/products/auth). ### Step 1: Set up your Appwrite project #### 1.1 Creating a new Appwrite project To start, you'll need to create a new project in your Appwrite instance. This project will manage your app's users, authentication, databases, and more. 1. **Log in to the Appwrite console**: Access your Appwrite console at [cloud.appwrite.io](https://cloud.appwrite.io/console). 2. **Create a new project**: In the dashboard, click **Create Project**, enter a name for your project, and click **Create**. ![Create a new Appwrite project](/images/blog/how-to-set-up-sign-in-with-apple/create-project.png) Once the project is created, you'll be taken to the project dashboard. Here, you can monitor usage and configure settings for your project. #### 1.2 Adding a platform to your project Each platform (web, Android, iOS) that interacts with your Appwrite project needs to be registered. This ensures that only authorized platforms can access Appwrite's APIs. In the project dashboard, navigate to the **Overview** tab, and in the **Add a platform** section, select your platform type (Web, iOS, Android). **For Web**: - Choose the **Web** option. - Enter a **platform name** (e.g., "WebApp") and the **hostname** of your app (e.g., `example.com`). ![Add a platform for Web](/images/blog/how-to-set-up-sign-in-with-apple/add-web-platform.png) After registering the platform, Appwrite will provide a code snippet for initializing the SDK. You'll use this later to integrate the Appwrite SDK into your project. ![Add a web platform - step 2](/images/blog/how-to-set-up-sign-in-with-apple/add-web-platform-2.png) **For Mobile**: - Select **iOS** or **Android** depending on your platform. - Enter the required information such as **Bundle ID** for iOS or **Package Name** for Android. With this setup, your Appwrite project is ready to handle authentication requests from your app. ### **Step 2: Configuring Appwrite for Apple Sign-In** #### **2.1 Enabling Apple OAuth2 in Appwrite** Now that your Appwrite project is ready, you can configure Appwrite to use Apple Sign-In. In the Appwrite console, navigate to **Auth** > **Settings**. ![Enable Apple OAuth2 in Appwrite](/images/blog/how-to-set-up-sign-in-with-apple/enable-apple-oauth2.png) In the **OAuth2 Providers** section, click the **Apple** provider. This will return a modal with a toggle button to enable Apple authentication. ![Enable Apple Oauth2 in Appwrite - step 2](/images/blog/how-to-set-up-sign-in-with-apple/enable-apple-oauth2-2.png) #### 2.2 Providing Apple credentials in Appwrite Once enabled, you'll need to fill in the following fields: - **Services ID**: The Service ID you will create later in your Apple Developer account. - **Key ID**: The ID of the authentication key you will create later in your Apple Developer account. - **Team ID**: The Team ID from your Apple Developer account (you'll obtain this in the next steps). - **P8 File**: In the next step, you'll generate an authentication key from Apple, which will be downloaded as a `.p8` file. Open the `.p8` file in a text editor, copy its contents, and paste them into the **P8 File** field. These settings allow Appwrite to communicate securely with Apple's servers for authentication. Before we proceed, let's configure your Apple Developer account. ### Step 3: Configuring your Apple Developer account To enable **Sign in with Apple**, you'll need to configure several settings in the Apple Developer Console. #### 3.1 Setting up your Apple Developer account Log in to the [Apple Developer website](https://developer.apple.com/) using your Apple ID. Once logged in: Navigate to **Account** > **Membership** and note down your **Team ID**. This will be required for Appwrite's OAuth2 configuration. #### 3.2 Registering an App ID An **App ID** is required for every app that uses Apple's services, including authentication. 1. Go to **Certificates, Identifiers & Profiles** > **Identifiers**. 2. Click the plus icon (+) beside **Identifiers** to create a new identifier, then select **App IDs**. 3. Provide a **Description** and a unique **Bundle ID** (e.g., `com.example.myapp`). 4. Under **Capabilities**, enable **Sign in with Apple**. 5. Review the settings and click **Register**. #### 3.3 Creating a Service ID for web authentication If you're building a web app, you'll need to create a **Service ID** to handle Apple Sign-In. 1. In the **Identifiers** section, click the plus icon (+) and select **Service IDs**. 2. Provide a **Description** and a unique **Identifier** (e.g., `com.example.myapp.web`). 3. Click **Register** to create the Service ID. With your service ID created, you can configure it for **Sign in with Apple**. To do this: 1. Click on the Service ID you created. 2. Beside **Sign in with Apple**, tick the checkbox to enable it. 3. Click **Configure** to set up the service. 4. Select the **Primary App ID** you created earlier. 5. Under **Register Website URLs**, enter the domain of your web app and the **Return URI** you got from Appwrite earlier. 6. Click **Save** to apply the changes. This Service ID allows users to authenticate with Apple via your web application. #### 3.4 Generating an Apple authentication key To securely communicate with Apple's servers, you need to generate an authentication key. 1. Go to **Keys** and click **Create a Key**. 2. Set a **Key Name** (e.g., "Apple Sign-In Key"). 3. Enable **Sign in with Apple** and click **Configure**. 4. Select the **App ID** you created earlier. 5. After saving the key, click **Register** and a new page will display the key details. 6. Click **Download** to save the key as a `.p8` file. **Important**: Store the `.p8` file securely, as it can only be downloaded once. With these configurations, you can now go back to Appwrite and fill in the Apple credentials as described in Step 2. ### Step 4: Implementing sign in with Apple in your project With your Apple Developer credentials and Appwrite OAuth2 settings configured, it's time to implement **Sign in with Apple** in your app. We'll walk through this step-by-step for each platform. #### 4.1 Understanding the OAuth2 flow with Appwrite Before diving into platform-specific code, let's break down the OAuth2 flow in Appwrite. Here's how the process works: 1. **Initialization**: Your app uses Appwrite's SDK to initiate an OAuth2 session by redirecting the user to Apple's login page. 2. **Authentication**: The user authenticates with their Apple ID credentials on Apple's login page. 3. **Token exchange**: Once authenticated, Apple redirects the user back to your app with an authorization code. 4. **Session creation**: Appwrite exchanges the authorization code for an access token from Apple, creates a session for the user, and returns the user to your app with the session. 5. **Session management**: After login, your app can use Appwrite's [Account API](https://appwrite.io/docs/references/cloud/client-web/account) to manage the user's session, check login status, and handle logouts. Now, let's move on to platform-specific implementations. #### 4.2 Implementing in a web application For web applications, you will use the Appwrite SDK to initiate the OAuth2 flow and handle authentication. Here's how you can integrate **Sign in with Apple** into your web app: **1. Install the Appwrite SDK**: First, make sure the Appwrite JavaScript SDK is installed in your project: ```bash npm install appwrite ``` **2. Initialize the SDK**: Place the following code in your project where the Appwrite SDK should be initialized, such as in the main application file (`index.js` or similar): ```jsx import { Client, Account, OAuthProvider } from 'appwrite' const client = new Client() .setEndpoint('https://') // Your API Endpoint .setProject('') // Your project ID const account = new Account(client) ``` **3. Create the OAuth2 session**: When a user clicks the "Sign in with Apple" button, trigger the following function to create an OAuth2 session: ```jsx async function loginWithApple() { try { await account.createOAuth2Session({ provider: OAuthProvider.Apple, success: 'https:///auth/oauth2/success', failure: 'https:///auth/oauth2/failure', }) } catch (error) { console.error('Error logging in with Apple:', error) } } ``` The redirect URLs should correspond to the success and failure URLs you configured in your Apple Developer account and Appwrite settings. On a successful login, the user is redirected to the success URL, and on failure (e.g., user cancels login), the user is sent to the failure URL. **4. Session handling**: After a successful login, Appwrite creates a session for the user. You can use Appwrite's session management API to check the user's login status: ```jsx const session = await appwrite.account.getSession({ sessionId: 'current' }) console.log('User session:', session) ``` This helps you manage authenticated states in your application. **5. Error handling**: If login fails (due to network issues, user cancellation, etc.), handle the error gracefully. For example: ```jsx if (error.message === 'OAuth2Error') { alert('Login failed. Please try again.') } ``` **6. Logout**: Provide an option to log out the user by terminating the session: ```jsx await account.deleteSession({ sessionId: 'current' }) ``` #### 4.3 Implementing in a Flutter application For Flutter applications, Appwrite provides an SDK that allows you to manage authentication seamlessly. Here's how to integrate **Sign in with Apple** into your Flutter app: **1. Add the Appwrite SDK to your project**: Learn how to do this [here](https://appwrite.io/docs/quick-starts/flutter). **2. Initialize the Appwrite client**: Initialize the SDK in your app's `main.dart` file or wherever you handle authentication logic: ```dart import 'package:appwrite/appwrite.dart'; void main() { Client client = Client(); client .setEndpoint('https:///v1')// Appwrite API Endpoint .setProject('');// Your project ID } ``` **3. Initiate the OAuth2 flow**: When the user taps the "Sign in with Apple" button, start the authentication process: ```dart final account = Account(client); Future loginWithApple() async { try { await account.createOAuth2Session( provider: 'apple', success: 'https:///auth/oauth2/success', failure: 'https:///auth/oauth2/failure', ); } catch (error) { print('Error logging in with Apple: $error'); } } ``` **4. Check session status**: After login, you can check the current session to see if the user is authenticated: ```dart final session = await account.getSession(sessionId: 'current'); print('Current session: $session'); ``` **5. Handle errors**: Just like on the web, handle possible errors during the OAuth2 flow. You could display an error message or provide retry logic: ```dart if (error.toString().contains('OAuth2Error')) { showError('Login failed. Please try again.'); } ``` **6. Logout**: To log the user out, delete the current session: ```dart await account.deleteSession(sessionId: 'current'); ``` #### 4.4 Implementing in Android Android requires special handling to manage the lifecycle of activities during OAuth2 authentication. Here's how to implement **Sign in with Apple** on Android: **1. Add the Appwrite SDK**: Learn how to add the Appwrite SDK to your Android project [here](https://appwrite.io/docs/quick-starts/android). **2. Initialize the SDK in your `MainActivity`**: ```kotlin import io.appwrite.Client import io.appwrite.services.Account class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val client = Client(applicationContext) .setEndpoint("https://YOUR_ENDPOINT") .setProject("YOUR_PROJECT_ID") } } ``` **3. Start the OAuth2 flow**: Trigger the OAuth2 flow when the user interacts with the "Sign in with Apple" button: ```kotlin val account = Account(client) GlobalScope.launch { try { account.createOAuth2Session( activity = this@MainActivity, provider = "apple" ) } catch (e: Exception) { Log.e("SignInError", "Error logging in with Apple", e) } } ``` **4. Handle redirects**: Ensure that your app's redirect URL matches the one configured in the Apple Developer console. **5. Error handling**: Manage errors such as failed authentication attempts or network issues in the callback: ```kotlin Log.e("OAuthError", "Login failed: ${e.message}") ``` #### 4.5 Implementing in iOS (Swift) For iOS apps, integrating **Sign in with Apple** using Appwrite involves configuring the SDK and managing OAuth2 redirects. **1. Install the Appwrite SDK**: Learn how to add the Appwrite SDK to your iOS project [here](https://appwrite.io/docs/quick-starts/swift) **2. Initialize the SDK in your AppDelegate**: ```swift import Appwrite let client = Client() .setEndpoint("https:///v1") .setProject("") ``` **3. Trigger the OAuth2 flow**: In your view controller, start the OAuth2 flow when the user taps the "Sign in with Apple" button: ```swift let account = Account(client) account.createOAuth2Session( provider: "apple", success: "https:///auth/oauth2/success", failure: "https:///auth/oauth2/failure" ) { result in switch result { case .failure(let error): print("Error logging in: \(error.localizedDescription)") case .success: print("Login successful") } } ``` **5. Handle error states**: Manage error states when the login fails: ```swift case .failure(let error): showAlert("Login failed: \(error.localizedDescription)") ``` ### 4.6 Token management and session handling After a successful OAuth2 session, the user's session is stored by Appwrite, and you can access it via the SDK to maintain login states across your app. It's important to handle session management, especially in long-running applications: #### Check active sessions Use the following function to check if a user is still authenticated: ```jsx const session = await account.getSession({ sessionId: 'current' }) if (!session) { console.log('No active session. Redirecting to login...') } ``` #### Handle token expiration Be prepared for tokens to expire, and have logic to refresh tokens or redirect users to re-authenticate when necessary. #### Logout users Offer users the ability to logout and clear their session. For example, in web apps: ```jsx await appwrite.account.deleteSession({ sessionId: 'current' }) ``` By following these steps, you can implement **Sign in with Apple** using Appwrite across various platforms. Each platform requires slightly different handling for OAuth2 flows, session management, and error handling, but the core process remains the same: redirect the user to Apple for authentication, manage the session through Appwrite, and handle errors and edge cases effectively. With proper integration, your app will offer secure and seamless authentication using Apple IDs, backed by Appwrite's OAuth2 platform. ### Step 5: Testing your integration Once you've integrated **Sign in with Apple**, it's time to test and troubleshoot. Here's how to approach it: During testing, simulate various scenarios to ensure everything works as expected: - Test logging in with valid and invalid Apple IDs. - Simulate session expirations and token refresh flows. - Check the behavior across different platforms and devices to ensure consistency. ### Conclusion Integrating **Sign in with Apple** using Appwrite allows you to provide a secure and user-friendly authentication experience. With minimal configuration, you can utilize Appwrite's OAuth2 support to integrate third-party login systems across platforms. In this guide, we walked through the full setup process, including configuring Apple Developer credentials, setting up OAuth2 in Appwrite, and testing your integration across different platforms. For more information on this, check out the [Appwrite OAuth2 documentation](/docs/products/auth) and Apple's [Sign in with Apple documentation](https://developer.apple.com/sign-in-with-apple/). ### Frequently asked questions (FAQs) **1. What is Sign in with Apple, and why should I use it?** Sign in with Apple lets users log in using their Apple ID instead of creating new accounts. It improves privacy by sharing minimal personal data and enhances security through features like built-in two-factor authentication. It’s a great option if your users are mostly on iOS or macOS. **2. How does Sign in with Apple work with OAuth2?** It uses the standard OAuth2 authorization flow. The user signs in with their Apple ID, Apple verifies the identity, and then returns a secure token to your app. Appwrite manages this token exchange automatically, so you don’t have to handle credentials or session logic manually. **3. Do I need an Apple Developer account to set this up?** Yes. You’ll need an active Apple Developer account to generate your Team ID, Service ID, and authentication key (.p8 file). These credentials allow Appwrite to securely connect with Apple’s authentication servers. **4. Does Appwrite support Sign in with Apple?** Yes. Appwrite natively supports Sign in with Apple through its built-in OAuth2 authentication system. You can enable it directly from the Appwrite Console under Auth → Settings → OAuth2 Providers, then add your Apple Developer credentials. Once configured, it works seamlessly across web, Android, iOS, and Flutter apps, just like other OAuth providers such as Google or GitHub. ### More resources: - [Set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) - [Appwrite authentication docs](https://appwrite.io/docs/products/auth) - [Integrate any external authentication solution into your Appwrite project](https://appwrite.io/blog/post/integrate-custom-auth-sveltekit) --- ## A technical deep dive into image classification https://appwrite.io/blog/post/image-classification Image classification is an exciting field of computer vision that tries to understand and label images in their entirety. It has many applications: you can integrate it into your app to automatically categorize photos, filter inappropriate content from social media feeds, or even organize your online store's product catalog. In this article, we’ll go over how the image classification field developed and its most popular architectures and datasets. Then, we’ll run you through implementing it into your Appwrite project using TensorFlow. ### A brief history of image classification Early image classification began in the 1960s and relied on simple algorithms such as nearest neighbours and linear classifiers. During this time, some of the foundations of image classification were invented, such as the Vapnik-Chervonenkis Theory (VC Theory). In the late 1980s, researchers explored how neural networks could be used. The introduction of back-propagation made it easier to train multi-layer perceptrons (MLPs) and showed that neural networks had the potential for classification tasks. However, a lack of large datasets to train on and insufficient processing power made real-world applications impractical. All these efforts culminated in 2012 AlexNet won the ImageNet Large Scale Visual Recognition (ILSVRC) competition. This victory highlighted the potential of Convolutional Neural Networks (CNNs) for image classification, leading to a major shift in the field. Following AlexNet, other notable architectures emerged, including VGGNet and GoogLeNet in 2014, ResNet in 2015, and EfficientNet in 2019. Each built on AlexNet's foundation, refining and simplifying CNN architecture, which is widely used today. Retention mechanisms and transformers, inspired by their success in natural language processing, are new trends in image classification. These models treat image patches like text tokens. Few-shot learning and transfer learning have potential to reduce the need for large datasets, making models training more efficient. ### Architectures Numerous architectures have been utilized for the task of image classification; here are a few notable ones: ##### Convolutional Neural Networks (CNNs) Convolutional neural networks (CNNs) use multiple layers to process data. Convolutional layers extract important features, pooling layers simplify the data and enhance feature detection, and a fully connected layer makes the final decisions. ##### Transformers The transformer architecture has completely changed the language processing field of AI. Researchers have investigated using the Transformer architecture since 2020, by converting sections of images into tokens with [Google's ViT](https://arxiv.org/pdf/2010.11929) and [Microsoft's Swin Transformer](https://arxiv.org/pdf/2103.14030). In these cases, Transformers showed yields matching or even greater results than CNNs. Currently, Transformers are leading benchmarks on most image classification datasets, including ImageNet ([OmniVec](https://arxiv.org/pdf/2311.05709)), CIFAR-10 (ViT-H/14), and [STL-10](https://cs.stanford.edu/~acoates/stl10/) (ViT-L/16). Still, they need help with datasets containing many classes, such as CIFAR-100, which is currently topped by an [EfficientNet](https://arxiv.org/pdf/1905.11946) (CNN). Another downside to transformers is that they require significantly more data than a CNN to train. ##### Capsule Networks (CapNet) [CapNets](https://arxiv.org/pdf/1710.09829) were invented by Geoffrey Hinton to remedy CNN's issue of recognizing different orientations. Unlike CNNs, CapNets don't use max-pooling techniques, which may cause important details to be lost in an image. Instead, they utilize smaller groups of neurons called capsules, designed to detect specific objects and output a vector instead of a single float value. Because of this output, CapNets can understand relationships between objects better (Eyes appear above the nose, etc.…) to build up a more accurate classifier even when viewing images with different orientations. ### Datasets Finding data to train these architectures used to be the most challenging part of developing state-of-the-art image classification models. Now, however, there is standardized high-quality labeled data that is offered entirely free for any researchers who require it. Next, we'll cover some of the most popular datasets. ##### ImageNet The most well-known dataset, and perhaps the oldest one still in use, [ImageNet](https://www.image-net.org/) is a collection of over 14 million labeled images built up over ten years, with over a million of those images also containing bounding box data on top of that. ImageNet is most famous for its competition, the ImageNet Large Scale Visual Recognition Challenge (ILSVRC for short), held annually from 2010 until 2017. It made waves across the industry when in 2012, AlexNet beat all other algorithms by over 10.8% using a convolutional neural network, sparking a boom in computer vision research. ##### CIFAR-10/100 Released in 2009 by the Canadian Institute for Advanced Research, [CIFAR-10/100](https://www.cs.toronto.edu/~kriz/cifar.html) is now another widely used benchmark for image classification models. It is a collection of 60,000 32x32 color images, each labeled with one of 10 classes. It is a subset of the 80 Million Tiny Images subset, which was also released in 2009. CIFAR-100 is another CIFAR dataset with 100 classes, each containing 600 images. It is also heavily used in benchmarking and training models. ##### MNIST Released by Yann LeCun and Corinna Cortes, [MNIST](https://yann.lecun.com/exdb/mnist/) is an extensive database of over 60,000 images of handwritten digits commonly used to train image classification models that power OCR technology. Each image is 28x28 pixels. MNIST's training data was taken from American Census Bureau employees, while the testing data was from American high school students. MNIST used to be the hello-world dataset for Tensorflow's image classification tutorial; however, it has since been replaced with 3,700 pictures of flowers to demonstrate better how to use Tensorflow with color images. ### How to implement image classification into your app To show you how you might use image classification in your application, we can create two scripts: a training script you can run on your machine and an inference script you can run as an Appwrite function. ##### Step 1: Train the TensorFlow model Install TensorFlow and NumPy installed on your machine, then paste the following code into a file called training.py; this is a modified version of the TensorFlow image classification tutorial found [here](https://www.tensorflow.org/tutorials/images/classification). ```python import matplotlib.pyplot as plt import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential import pathlib dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz" data_dir = tf.keras.utils.get_file('flower_photos.tar', origin=dataset_url, extract=True) data_dir = pathlib.Path(data_dir).with_suffix('') batch_size = 32 img_height = 180 img_width = 180 train_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="training", seed=123, image_size=(img_height, img_width), batch_size=batch_size) val_ds = tf.keras.utils.image_dataset_from_directory( data_dir, validation_split=0.2, subset="validation", seed=123, image_size=(img_height, img_width), batch_size=batch_size) class_names = train_ds.class_names AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE) val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE) ### Compile the model num_classes = len(class_names) model = Sequential([ layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)), layers.Conv2D(16, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(32, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Conv2D(64, 3, padding='same', activation='relu'), layers.MaxPooling2D(), layers.Flatten(), layers.Dense(128, activation='relu'), layers.Dense(num_classes) ]) model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy']) model.summary() ### Train the model epochs=10 history = model.fit( train_ds, validation_data=val_ds, epochs=epochs ) converter = tf.lite.TFLiteConverter.from_keras_model(model) tflite_model = converter.convert() with open('model.tflite', 'wb') as f: f.write(tflite_model) ``` This code will download a bunch of sample images of flowers, create two groups of images for training and validation with an 80/20 split, and then build a standard convolutional neural network and train it. Finally, it will export the trained model into a TensorFlow Lite model. ##### Step 2: Create a new function Next, initialize a new function using the Appwrite CLI by using: ```bash appwrite init project # Use if you don't already have an appwrite.config.json appwrite init function ``` Give it any name and make sure to select the `Python (ML)` runtime; this should create a new function with the boilerplate included. Change directories into the freshly created function and open a code editor within it. ##### Step 3: Add our inference code to the function With the function boilerplate created, replace the `main.py` file with a script to load the model we just trained and run inference: ```python import os import json import numpy as np import tensorflow as tf from PIL import Image from io import BytesIO from appwrite.client import Client from appwrite.services.storage import Storage script_dir = os.path.dirname(os.path.abspath(__file__)) TF_MODEL_FILE_PATH = os.path.join(script_dir, 'model.tflite') img_height = 180 img_width = 180 interpreter = tf.lite.Interpreter(model_path=TF_MODEL_FILE_PATH) interpreter.allocate_tensors() def preprocess_image(image_bytes): img = Image.open(BytesIO(image_bytes)) img = img.resize((img_width, img_height)) img = np.array(img) img = img.astype('float32') / 255.0 img = np.expand_dims(img, axis=0) return img def main(context): if context.req.method != "POST": return context.res.send("Invalid Request", 400) client = ( Client() .set_endpoint("https://.cloud.appwrite.io/v1") .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) .set_key(os.environ["APPWRITE_API_KEY"]) ) bucketId = os.environ["APPWRITE_BUCKET_ID"] storage = Storage(client) try: body = json.loads(context.req.body) except ValueError as err: return context.res.json({"ok": False, "error": err.message}, 400) file_id = body["$id"] file_bytes = storage.get_file_download(bucketId, file_id) # Preprocess the image img = preprocess_image(file_bytes) # Get the signature runner for the model classify_lite = interpreter.get_signature_runner('serving_default') # Run the image through the model predictions_lite = classify_lite(rescaling_1_input=img)['dense_1'] score_lite = tf.nn.softmax(predictions_lite) class_names = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips'] response_message = "This image most likely belongs to {} with a {:.2f} percent confidence.".format( class_names[np.argmax(score_lite)], 100 * np.max(score_lite) ) context.log(response_message) return context.res.send(response_message, 200) ``` Add a requirements.txt file with TensorFlow and NumPy, to fetch the required dependencies: ```text tensorflow appwrite numpy Pillow ``` ##### Step 4: Deploy your function Move the `model.tflite` file that was created into the same directory as the `main.py` file and then change directories to that containing the `appwrite.config.json`. This `model.tflite` is quite a small file, so store it in the function. If using larger models, upload them to Appwrite Storage and then use `getFileDownload` to download them to the function. With the file now copied, run appwrite deploy function and select the function to deploy it to Appwrite. ##### Step 5: Update your environment variables Now that the function has been deployed, navigate to it on the Appwrite console and set the `APPWRITE_API_KEY` and `APPWRITE_BUCKET_ID` environment variables to be ready for execution. Make sure to redeploy the function so these new environment variables are set. ##### Step 6: Test your function Finally, send a `POST` request to the function with a JSON body that contains a file ID from the bucket like this: ```json {"$id": "66503a9e00246481a92c"} ``` The classification result will be returned shortly as a response. This function can also accept a file event. To build on this function, you may want to upload the result to the database to identify images uploaded to your instance automatically. ### Conclusion This demo should give you a good starting point for experimenting with AI and Appwrite. Try making a more advanced version or using safe tensors instead, and if you make anything cool, do not hesitate to let us know on our [Discord server](https://appwrite.io/discord). Check out the resources below to learn how to integrate AI into your Appwrite-powered application. We can’t wait to see what you build! - [Appwrite AI integrations documentation](https://appwrite.io/docs/products/ai) - [Build an intelligent chatbot with GPT-4o and Appwrite Functions](https://appwrite.io/blog/post/personal-chatbot-gpt-4o) - [Learn how to build with Appwrite AI Function templates](https://appwrite.io/blog/post/building-with-ai-function-templates) --- ## Image transformation with Appwrite Storage https://appwrite.io/blog/post/image-transformation-with-appwrite-storage Images are a core part of any modern web or mobile application. Whether you're displaying user avatars, product thumbnails, or full-screen backgrounds, images need to be optimized for performance, aesthetics, and consistency. Loading large, uncompressed images can slow down your app, and mismatched styles can break your UI. This is why dynamic image transformation should be a part of your app. Instead of manually editing images before uploading them or storing multiple variations of the same file, Appwrite lets you manipulate images on the fly using the Storage preview endpoint. With a simple API call, you can resize, crop, compress, change formats, add borders, round corners, adjust opacity, and even apply background colors. The best part is that Appwrite automatically caches the transformed images, speeding up repeat requests. This guide will walk you through everything you need to know about image transformation with Appwrite, and how to use the Storage preview endpoint to transform images. By the end, you'll be able to integrate image transformations into your app without touching an image editor. ### Why dynamic image transformations? Here are some of the benefits of transforming images dynamically: 1. **Optimize performance** - Uncompressed images can slow down your app and increase bandwidth usage. Appwrite allows you to dynamically resize and compress images, improving load times and reducing network costs. 2. **Maintain a consistent UI** - By adjusting borders, border-radius, and background color, you can ensure images match your app's theme and display correctly across different screen sizes. 3. **Improve user experience** - You can crop, rotate, and adjust opacity to fine-tune how images appear. This is useful for dynamic UI elements like profile pictures, cards, and galleries. 4. **Built-in caching** - Appwrite caches transformed images, which reduces processing time and ensures faster repeat requests. ### How image transformation works in Appwrite Appwrite's **Storage preview endpoint** applies transformations dynamically when retrieving an image. The original file remains unchanged, while Appwrite generates a modified version on-the-fly and returns the transformed image. Here's an example of how to transform an image. With each example, we'll use the Appwrite Storage SDK to show the actual result: ```jsx import { Client, Storage } from 'appwrite' const client = new Client() const storage = new Storage(client) client .setEndpoint('') // API Endpoint .setProject('') // Project ID const result = storage.getFilePreview({ bucketId: 'photos', fileId: 'sunset.png', width: 1600, gravity: 'center', quality: 90, borderWidth: 5, borderColor: 'FD346E', borderRadius: 15, background: 'FFFFFF', output: 'webp' }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=0 gravity="center" quality=90 border_width=5 border_color="FD346E" border_radius=15 opacity=1 rotation=0 background_color="FFFFFF" output="webp" /%} This returns a new URL for the transformed image, which can be used directly in your app. Let's break down the parameters: ### 1. Resizing the image Resizing is one of the most common transformations. Whether you're displaying thumbnails, profile pictures, or high-resolution banners, controlling the width and height ensures that images fit well within your design. Appwrite allows you to set: - **width**: 0-4000 pixels (Resizes while maintaining aspect ratio if height is not provided) - **height**: 0-4000 pixels (Resizes while maintaining aspect ratio if width is not provided) If only one dimension is set, Appwrite adjusts the other proportionally. **Example:** ```jsx const previewUrl = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 600 }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=600 /%} ### 2. Cropping with gravity Cropping allows you to remove unnecessary parts of an image and focus on the important area. The **gravity** parameter controls which part of the image remains visible when cropping. #### Common use cases - Ensuring a profile picture always centers on a face - Keeping product images aligned in an e-commerce store - Removing excess background in UI components #### Gravity options - `center` (default) - `top-left`, `top`, `top-right` - `left`, `right` - `bottom-left`, `bottom`, `bottom-right` **Example:** ```jsx const previewUrl = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 1600, gravity: ImageGravity.TopLeft }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=1600 gravity="top-left" /%} ### 3. Adjusting image quality The **quality** parameter controls image compression, helping to balance clarity and file size. - **Higher values (80-100)**: Retain more detail but result in larger file sizes. - **Lower values (10-50)**: Reduce file size significantly but may introduce visible compression artifacts. **Example:** ```jsx const compressedImage = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 800, quality: 10 }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=800 gravity="center" quality=10 /%} ### 4. Adding borders and border radius Borders help separate images from the background, while border radius adds rounded corners for a softer appearance. - **borderWidth**: 0-100px - **borderColor**: Hex color code (without `#`) - **borderRadius**: 0-4000px (Higher values create more rounded corners) **Example:** ```jsx const previewUrl = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 1000, borderWidth: 8, borderColor: 'FF3366', borderRadius: 80 }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=1000 gravity="center" quality=100 border_width=8 border_color="FF3366" border_radius=80 /%} ### 5. Controlling opacity Setting **opacity** allows images to blend into backgrounds or appear as overlays. - `0 = Fully transparent` - `1 = Fully opaque` **Example:** ```jsx const overlayImage = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 1000, opacity: 0.3 // Opacity for overlay effect }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=1000 gravity="center" quality=100 opacity=0.3 /%} ### 6. Rotating an Image The **rotation** parameter allows you to rotate an image by a specified number of degrees. - **0-360** degrees **Example:** ```jsx // 45-degree rotation with padding const rotatedImage = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 400, height: 400, rotation: 45 // 45-degree rotation }) ``` {% storage_image just_img=true file_id="cat-image" width=400 height=400 gravity="center" quality=100 rotation=45 background_color="FFFFFF" /%} ### 7. Changing background color For transparent images (like PNGs), you can set a background color. - **Hex color code** (without `#`) **Example:** ```jsx const previewUrl = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 800, opacity: 0.7, background: 'FF9900' }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=800 gravity="center" quality=100 opacity=0.7 background_color="FF9900" /%} ### 8. Configure output format The **output** parameter lets you convert images between different formats on the fly, regardless of the original image format. **Example:** ```jsx const webpImage = storage.getFilePreview({ bucketId: 'bucketID', fileId: 'fileID', width: 1600, height: 800, quality: 90, output: 'webp' }) ``` {% storage_image just_img=true file_id="cat-image" width=1600 height=800 gravity="center" quality=90 output="webp" /%} The above image is originally a PNG file. However, by setting the output format to WebP, Appwrite automatically converts the image to WebP format and returns the transformed image. You can confirm this by downloading the image or inspecting its source. The `output` parameter supports `png`, `jpeg`, `webp`, `gif`, and `heic`. Choosing the right format can significantly impact your application's performance. For example, WebP is a modern format that offers better compression and quality than JPEG and PNG. However, while it's supported in most modern browsers, it's good practice to implement a fallback for older browsers. ### Final thoughts With just a few lines of code, Appwrite's [image transformations](/docs/products/storage/images) eliminate the need for: - Multiple image versions cluttering your storage - Complex client-side image processing - Manual image editing for each use case - Third-party image processing services Try replacing your next image processing task with a single API call, and you might be surprised how much time and performance you can gain. Check out the [docs](/docs/storage) for more information on how to use Appwrite Storage. ### Further reading - [Building a full-stack app with Svelte and Appwrite](/blog/post/build-fullstack-svelte-appwrite?doFollow=true) - [Setting up route protection in React Native](/blog/post/setting-up-route-protection-in-react-native?doFollow=true) - [A technical deep dive into image classification](/blog/post/image-classification?doFollow=true) --- ## How to implement Sign in with GitHub https://appwrite.io/blog/post/implement-sign-in-with-github If you're building an app that developers will use, adding GitHub login makes things easier for your users. In this tutorial, you'll learn how to create a GitHub login system using Vanilla JavaScript and Appwrite. By the end, your users will be able to log in with their GitHub accounts, see personalized details, and log out. We'll go step by step, starting with setting up OAuth on GitHub, configuring Appwrite, and then connecting everything in your app. Even if you've never worked with OAuth or Appwrite before, at the end of this tutorial, you'll have a fully functional login system that you can easily integrate into any project. ![Appwrite GitHub auth demo](/images/blog/implement-sign-in-with-github/appwrite-github-auth.gif) Let's get started! ### **Prerequisites** Before we begin, ensure you have the following: - [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed - A [GitHub account](https://github.com/) - An [Appwrite account](https://console.appwrite.io/) ### **Step 1: Setting up the project** We'll begin by creating a new [Vite](https://vitejs.dev/) project and installing the necessary dependencies for Appwrite. Vite is the build tool we'll use to manage the project and its dependencies. #### **1.1. Create a Vite project** In your terminal, navigate to the directory where you want to create your project and run the following commands: ```bash npm create vite@latest github-auth-app --template vanilla cd github-auth-app npm install ``` These commands will create a new project folder called `github-auth-app` with the Vite vanilla JavaScript template. You'll also install the necessary dependencies for Vite. Next, you can test the project by running `npm run dev` and opening the specified URL that Vite will provide. You should see the default Vite app for Vanilla JS. #### **1.2. Install the Appwrite SDK** Now, we need to install the [Appwrite SDK](https://appwrite.io/docs/sdks), which will allow us to interact with Appwrite's authentication and database services. Run this command inside your project folder: ```bash npm install appwrite ``` This will add the Appwrite JavaScript SDK to your project, which we'll use later to handle GitHub OAuth authentication. ### **Step 2: Setting up GitHub OAuth** We need to set up an OAuth application on GitHub to enable authentication for users using their GitHub accounts. #### **2.1. Create a new OAuth app on GitHub** 1. Go to your GitHub account. 2. Navigate to **Settings** > **Developer settings**. 3. Click on **OAuth Apps**. 4. Click **New OAuth App**. You'll see a form titled **Register a new OAuth app**. This is where you'll fill in the details of your GitHub OAuth app: ![GitHub OAuth app setup](/images/blog/implement-sign-in-with-github/image-1.png) - **Application Name**: You can name it "GitHub Auth with Appwrite". - **Homepage URL**: Input your homepage URL. You can use your localhost URL for now. - **Authorization callback URL**: You can use the same URL as your homepage URL for now. We'll update this later with a value generated by Appwrite. Once you've filled out the form, click **Register Application**. GitHub will provide you with a **Client ID** and **Client Secret**. **Save these details** because we'll need them when configuring Appwrite. ![GitHub OAuth app details](/images/blog/implement-sign-in-with-github/image-2.png) ### **Step 3: Setting up Appwrite** Now that we have our GitHub OAuth app set up, let's move on to configuring Appwrite. Appwrite will handle the OAuth process and act as the backend for our authentication flow. #### **3.1. Create a new Appwrite project** If you already have an Appwrite project, you can skip this step. 1. Log in to your Appwrite console. 2. Click on **Projects** and then **+ Create Project**. 3. Give the project a name and click **Create**. Take note of the **Project ID** and **API Endpoint** for this project, as we will need these later when configuring our frontend. You can find these in the **Settings** page of your Appwrite project. ![Appwrite project settings](/images/blog/implement-sign-in-with-github/image-3.png) #### **3.2. Add GitHub as an authentication provider** 1. Inside your Appwrite project, go to the **Auth** section from the left-hand menu. 2. Click **Settings** and scroll down to the **OAuth2 Providers** section 3. Select **GitHub** from the list of providers. 4. Enter the **Client ID** and **Client Secret** that you obtained from GitHub earlier. 5. At the bottom of the form, you'll see a URI field. This is the **Authorization Callback URL** that you'll need for GitHub. ![Appwrite GitHub OAuth settings](/images/blog/implement-sign-in-with-github/image-4.png) You need to copy this URL and **update the callback URL** in your GitHub OAuth app (from **Step 2.1**) to this new value. With this, Appwrite will be configured to handle GitHub OAuth. ### **Step 4: Configuring the frontend project** Next, let's set up our project to communicate with Appwrite using the Client ID and API details from Appwrite. #### **4.1. Create an `.env` file** In the root of your project, create a file called `.env` and add the following environment variables: ```bash VITE_APPWRITE_ENDPOINT=[your_appwrite_endpoint] VITE_APPWRITE_PROJECT_ID=[your_appwrite_project_id] ``` Replace `[your_appwrite_endpoint]` and `[your_appwrite_project_id]` with the actual values from your Appwrite project. These environment variables will be used by Vite and Appwrite to authenticate users and make API calls. ### **Step 5: Building the app** Now let's start writing the code to handle the GitHub OAuth functionality. #### **5.1. Setting up the HTML** We'll start by setting up the HTML structure of our app. Open the `index.html` file inside the `public` folder and replace its contents with this: ```html GitHub Auth with Appwrite
    ``` Here, we have a simple HTML file with an empty `
    ` element (with the ID as `app`) where our JavaScript code will dynamically insert the login buttons or user information. #### **5.2. Initializing the Appwrite client** Next, let's set up the Appwrite client and handle authentication. Open `main.js` and replace what's currently in it. We'll start by initializing the Appwrite client. Add this code to your `main.js` file: ```jsx /* Import the required Appwrite modules */ import { Client, Account, OAuthProvider } from 'appwrite' /* Initialize the Appwrite client */ const client = new Client() client .setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT) .setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID) /* Create an instance of the Account service */ const account = new Account(client) ``` This block of code initializes the Appwrite client with the endpoint and project ID from our `.env` file. The `Account` service will allow us to manage the user's authentication. #### **5.3. Handling user authentication** Now that we have the Appwrite client set up, let's handle user authentication using GitHub OAuth. We'll first add code to check if the user is already authenticated. Add the following block to your `main.js` file: ```jsx /* Declare a variable to store the user */ let user = null /* Function to check if a user is already logged in */ const checkSession = async () => { try { user = await account.get() updateUI() } catch (error) { if (error.code === 401) { /* User is not logged in */ user = null } else { console.error('Session error:', error) } updateUI() } } ``` Let's break down what's happening in the code: 1. **`account.get()`**: This function retrieves the current user session. If the user is logged in, it returns their details. If not, an error is thrown. 2. **Session Error Handling**: If the error code is `401`, it means the user isn't logged in. We handle this by setting `user` to `null`. Next, we'll update the UI based on whether the user is logged in or not. #### **5.4. Updating the UI based on authentication state** Now, let's create the `updateUI` function that will render different content based on whether the user is logged in or logged out. Add this code next: ```jsx /* Get the app element from the DOM */ const app = document.querySelector('#app') /* Function to update the UI based on the user's authentication state */ const updateUI = () => { if (user) { /* If the user is logged in, show a welcome message and logout button */ app.innerHTML = `

    Welcome, ${user.name}!

    ` document.querySelector('#logout').addEventListener('click', logout) } else { /* If the user is logged out, show a login button */ app.innerHTML = `

    GitHub x Appwrite

    ` document.querySelector('#login').addEventListener('click', login) } } ``` In this code: - **Logged-in State**: If the user is logged in, the app displays a welcome message and a **Logout** button. - **Logged-out State**: If the user is not logged in, the app displays a **Login with GitHub** button. #### **5.5. Implementing the login and logout functionality** Finally, let's implement the `login` and `logout` functions to handle the OAuth process. Add the following code to your `main.js` file: ```jsx /* Function to log in using GitHub OAuth */ const login = async () => { try { await account.createOAuth2Session({ provider: OAuthProvider.Github, success: 'http://localhost:5174/',// Redirect here on success failure: 'http://localhost:5174/',// Redirect here on failure }) } catch (error) { console.error('Login error:', error) } } /* Function to log out */ const logout = async () => { try { await account.deleteSession({ sessionId: 'current' }) // Delete the current session user = null updateUI() } catch (error) { console.error('Logout error:', error) } } /* Check if user is logged in when the app loads */ checkSession() ``` Let's look at the functions we've added: 1. **`login()`**: This function creates an OAuth session with GitHub. If the login is successful, the user will be redirected back to the app. 2. **`logout()`**: This function logs the user out by deleting the current session in Appwrite. We also call `checkSession()` when the app loads to check if the user is already authenticated. Don't forget to change the redirect URLs in the `login()` function to your app's URL. ### **Step 6: Running and testing the app** Now that everything is set up, let's run the app and test it. #### **Start the development server** If your app isn't already up, start it by running the following command on your terminal: ```bash npm run dev ``` This will start the development server. Open your browser and go to the URL displayed in your terminal. You should see the login screen with a **Login with GitHub** button. #### **Test the authentication flow** - Click the **Login with GitHub** button. This will redirect you to GitHub for authentication. - After logging in, you'll be redirected back to the app, and you should see a welcome message with your GitHub username. - Click the **Logout** button to end the session and return to the login screen. ![Appwrite GitHub auth demo](/images/blog/implement-sign-in-with-github/appwrite-github-auth.gif) ### **Conclusion** You've just built a simple GitHub authentication app using **Vite**, **Vanilla JavaScript**, and **Appwrite**. Throughout the tutorial, we incrementally built the app, and you learned how to: - Set up OAuth2 authentication with GitHub. - Use Appwrite to handle authentication. - Manage a simple UI for login and logout. From here, you can expand the app by integrating additional GitHub APIs or adding features such as fetching repositories or displaying more user information. You can even add a database using [Appwrite](https://appwrite.io/docs/products/databases/quick-start) to store user information. ### Further reading - [How to set up Google authentication in React with Appwrite](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) - [Integrate any external authentication solution into your Appwrite project](https://appwrite.io/blog/post/integrate-custom-auth-sveltekit) - [Appwrite auth documentation](https://appwrite.io/docs/products/auth) --- ## Improve your Appwrite developer experience with dev keys https://appwrite.io/blog/post/improve-devex-dev-keys When building applications with Appwrite, developers often need a way to test and debug services repeatedly over short periods. Sometimes, this can become a hassle, as Appwrite enforces strict rate limits on client apps to prevent abuse. Developers needed a way to bypass these rate limits in their test environments and CI/CD workflows to ensure the robustness of their app functionalities. And this is the exact problem **dev keys** are here to solve! ### What are dev keys? Dev keys are a type of secret used by Appwrite's client SDKs. They allow the developer to bypass any rate limits enforced by Appwrite and suppress any CORS errors caused by not using a configured hostname. A developer can configure the expiration date of a dev key to any of the three preset options (1 day, 7 days, or 30 days). Dev keys should never be used in production environments, as they can make your app more susceptible to abuse and security breaches. ### Try out dev keys in an app To test dev keys, I created a simple demo web app that sends 200 requests to create a new document in an Appwrite database. To set this app up, you must complete the following steps: #### Step 1: Setup Appwrite project First, create an [Appwrite Cloud](https://cloud.appwrite.io) account or [self-host Appwrite 1.7](/docs/advanced/self-hosting). Create a project (which will lead you to the project overview page) and head to the **Databases** page from the left sidebar. Create a new database with the ID `testDb` and a new collection with the ID `testCollection`. Store both of these IDs for future usage. In this collection, add the following attribute: | Name | Type | Required | | --- | --- | --- | | `number` | Integer | True | Then, head to the **Settings** tab of the collection, scroll down to the **Permissions** section, and add the following permissions: | | Create | Read | Update | Delete | | --- | --- | --- | --- | --- | | Any | False | False | False | False | Lastly, return to the project overview page, head to the **Settings** page from the left sidebar, and copy your **API endpoint** and **project ID** for future usage. #### Step 2: Create web app Next up, we'll create our test app. This will require us to create two files in our working directory: - `index.html` ```html Dev keys demo

    Dev keys demo

    Click on this button to add 200 documents to the database in less than 1 minute.

    ``` - `app.js` ```js const client = new Appwrite.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const database = new Appwrite.Databases(client); document.querySelector('button').addEventListener('click', async () => { const promises = []; for (let i = 0; i < 200; i++) { const promise = database.createDocument({ databaseId: 'testDb', // Your database ID collectionId: 'testCollection', // Your collection ID documentId: Appwrite.ID.unique(), data: { number: i + i } }); promises.push(promise); console.log('Request initiated:', i+1); } await Promise.all(promises); }); ``` If you open the HTML page in your browser and click on the `Add documents` button, you will notice numerous errors in the console with the HTTP code `429`, as Appwrite's rate limits allow one client to create 120 requests per minute for this API endpoint. #### Step 3: Create dev key Head back to your Appwrite project. On the overview page, select the **Dev keys** tab under the Integrations section and create a new dev key. You can add whatever name and expiry date you like. ![Dev key](/images/blog/improve-devex-dev-keys/dev-key.png) After creating this dev key, head to the `app.js` file and update the Appwrite client to the following: ```js const client = new Appwrite.Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setDevKey(''); // Your dev key ``` #### Step 4: Test the app Reopen the HTML page in your browser. Clicking the `Add documents` button will allow all 200 requests to execute successfully. ### Next steps And with that, you have successfully tested dev keys! Learn more about Appwrite by visiting the docs and joining the Discord community. - [Appwrite dev keys docs](/docs/advanced/platform/dev-keys) - [Appwrite Discord server](/discord) --- ## Improving UX with passwordless authentication https://appwrite.io/blog/post/improve-ux-passwordless-auth Today, as concerns about security and user convenience only grow with digital activity, traditional password-based authentication systems are becoming a relic of the past. In recent times, we have seen a rise in passwordless systems, which are increasingly seeing adoption in both small and big companies alike, such as Expensify (whose transition was also covered in a [Forbes article](https://www.forbes.com/sites/quickerbettertech/2023/05/29/on-technology-expensify-forces-passwordless-on-its-users-and-good-for-them/?sh=397a7b017cac) in 2023. A [survey from Enterprise Strategy Group](https://research.esg-global.com/reportaction/SecuringTheIdentityPerimeterReport/Toc), a division of TechTarget, in 2022 also revealed that: - 31% of respondents picked passwordless authentication as their top identity-related activity - 34% of respondents chose passwordless authentication among their top three identity-related activities - 54% of respondents have started to transition to passwordless authentication - Of organizations transitioning to passwordless strategies, more than 50% experienced a significant positive impact on risk reduction and improved UX - Almost two-thirds reported increased efficiency for IT and security teams Therefore, in this blog, we'll explore what passwordless authentication is, the drawbacks of conventional methods, and how embracing passwordless solutions can be a game-changer. ### The drawbacks of traditional password-based systems [As per an article by Lastpass](https://blog.lastpass.com/2023/07/pervasive-password-less-protection-the-solution-to-the-compromised-credentials-crisis), passwords need to be treated like a user experience today because repeated login attempts, forgotten passwords, mandatory password changes, and looking up passwords in a document or notebook only slow employees down and create friction in their workflow. Traditional password systems, despite their ubiquity, come with significant drawbacks, including: - **Password fatigue**: Users struggle with remembering numerous passwords for different accounts. - **Security risks**: Weak or reused passwords increase vulnerability to hacking and data breaches. - **User inconvenience**: Managing complex passwords is often cumbersome and frustrating and only causes more fatigue. - **Time-consuming**: Resetting forgotten passwords and managing accounts adds unnecessary steps. - **Phishing vulnerabilities**: Traditional passwords are susceptible to phishing attacks and can open up new attack vectors for a product. - **Limited physical security**: Passwords can be easily observed or stolen, especially in public or shared spaces. ### What is passwordless authentication? Passwordless authentication is exactly what it sounds like – a way to authenticate users without the need for passwords. This paradigm shift in user verification employs methods like *passkeys* (fingerprint, facial scans, or screen lock), *magic links* (one-time clickable links sent via email or SMS), and *one-time passcodes* (OTPs) sent to a user's device. These methods are not only innovative but also align with the natural human tendency to seek convenience and simplicity. #### Benefits of passwordless authentication for user experience The transition to passwordless authentication brings a myriad of benefits: - **Enhanced security**: By eliminating passwords, we inherently reduce the risk of password-related breaches. - **Improved ease of use**: Users no longer need to remember or manage a plethora of passwords. This simplifies the login process, leading to a more frictionless user experience. - **Accelerated process**: Authentication becomes quicker, with just a click or a touch, streamlining the user's journey. - **Higher authentication speed**: Users can access services faster, without the delay of typing a password. - **Enhanced user retention**: Easier login processes can lead to lower user drop-off rates. - **Accessibility for all users**: Such methods can be more accessible for users who have difficulty remembering passwords or have physical disabilities that make typing challenging. - **Lower support costs**: A lack of passwords reduces the volume of password reset requests and related support issues, saving an immense amount of IT and support effort and cost. - **Scalability**: Easily adapts to growing user bases without the need for extensive password management systems. - **Reduced risk of internal threats**: Minimizes the risk of password theft or misuse by internal actors within an organization. #### Tradeoffs of passwordless authentication While the benefits are substantial, passwordless authentication isn't without its tradeoffs, such as: - **Implementation complexity**: It can be more challenging and resource-intensive to implement than traditional methods. - **Hardware dependencies**: Some methods require specific hardware, like biometric scanners, which add further costs and may not be as readily accessible everywhere. - **Privacy and security concerns**: The collection of biometric data raises privacy issues for individuals as well as opens up newer security challenges in regard to how the data must be stored. - **User adaptation**: Users are often accustomed to well-set systems and may need time to adapt to new authentication methods. - **Potential technical issues**: Some methods rely on the proper functioning of user devices and external systems (like email or SMS services), which the company may not have control of. ### Implementing passwordless authentication using Appwrite Appwrite Authentication also features three commonly used passwordless authentication methods: [**magic links**](/docs/products/auth/magic-url), [**email OTPs**](/docs/products/auth/email-otp), and [**phone authentication**](/docs/products/auth/phone-sms), which can be integrated into applications seamlessly with Appwrite’s client-side SDKs. This approach not only enhances security but also significantly improves the user experience by simplifying and streamlining the login process. {% tabs %} {% tabsitem #magicurl title="Magic URLs" %} Magic URL authentication is a two-step process in Appwrite. First, we initialize the login process by sending an email with the magic URL. If the email has never been used, a new account is also generated. ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const user = await account.createMagicURLToken({ userId: ID.unique(), email: 'email@example.com', url: '' }); ``` After receiving the secret from an email, you can create a session for the user. ```js const urlParams = new URLSearchParams(window.location.search); const secret = urlParams.get('secret'); const userId = urlParams.get('userId'); const user = await account.createSession({ userId, secret }); ``` {% /tabsitem %} {% tabsitem #emailotp title="Email OTPs" %} Email OTP authentication is a two-step process in Appwrite. First, we initialize the login process by sending an email. If the email has never been used, a new account is also generated. ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const sessionToken = await account.createEmailToken({ userId: ID.unique(), email: 'email@example.com' }); const userId = sessionToken.userId; ``` After receiving the secret (6-digit number) in the email, you can use it along with the returned user ID to confirm the user. ```js const session = await account.createSession({ userId, secret: '' }); ``` {% /tabsitem %} {% tabsitem #phoneotp title="Phone auth" %} Phone authentication is a two-step process in Appwrite. First, we initialize the login process by sending an SMS. If the phone number has never been used, a new account is also generated. ```client-flutter import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final token = await account.createPhoneToken( userId: ID.unique(), phone: '+14255550123' ); final userId = token.userId; ``` After receiving the secret (6-digit number) in the SMS, you can use it along with the returned user ID to confirm the user. ```dart final session = await account.createSession( userId: userId, secret: '' ); ``` {% /tabsitem %} {% /tabs %} Passwordless authentication stands at the forefront of a new era in digital security and user experience. My recommendation to all is to embrace the future – say goodbye to passwords and hello to a more secure, convenient digital experience. Learn more about Appwrite Authentication from our [docs](/docs/products/auth) and join our [Discord community](https://appwrite.io/discord) to interact with fellow developers using the same. --- ## February 2024 - Incident Report https://appwrite.io/blog/post/incident-report-feb-24 As Appwrite Cloud continues to grow and scale, it is inevitable that we face challenges and obstacles that test the resilience of our systems and our team's ability to respond swiftly. February 2024 was no exception, as we encountered a series of incidents that put our protocols to the test. While these moments are opportunities for growth, they also remind us of the importance of transparency and communication with our community. This blog post aims to provide a detailed overview of the incidents we experienced throughout the month, including the causes, our responses, and the lessons learned. Our commitment to continuous improvement drives us to share these insights openly, reinforcing our dedication to reliability and trust. #### TLDR; for February 2024 | Date | Title | Duration | |------------|----------------------------------------------------|------------------| | 2024-02-01 | High Memory Usage During Scheduled Backups | 4 hours, 6 mins | | 2024-02-12 | Database Restart Incident | 1 hour, 34 mins | | 2024-02-13 | Gateway Timeouts in Traefik Containers | 15 mins | | 2024-02-14 | Gateway Timeouts in Traefik Containers | 8 mins | | 2024-02-15 | Gateway Timeouts in Traefik Containers | 8 mins | | 2024-02-19 | Executor Crash and Bad Webhook | 27 mins | | 2024-02-21 | Gateway Timeouts in Traefik Containers | 25 mins | | 2024-02-24 | Gateway Timeouts in Traefik Containers | 30 mins | | **Total** | | **7 hours, 33 mins** | ###### High Memory Usage During Scheduled Backups - **Date:** February 1, 2024 - **Duration:** 4 hours and 6 minutes - **Summary:** High memory usage in a database cluster caused widespread impact on cloud components. - **Description:** A high memory usage alert was triggered for one of the database clusters, leading to unresponsive behavior that impacted critical cloud components, including Databases, API, Functions, etc. The root cause was identified as a high memory load during scheduled backups, affecting API performance for all users on the cloud. - **User Impact:** Projects on the affected database were impacted, and partial API performance degradation during the initial phases. - **Resolution and Steps Taken:** The problematic database was temporarily disconnected from the connection string, allowing for a necessary restart. After addressing the high memory usage, the database was reconnected, mitigating the issue. ###### Database Restart Incident - **Date:** February 12, 2024 - **Duration:** 1 hour and 34 minutes - **Summary:** Manual restart of a database affected project APIs. - **Description:** An unexpected manual restart of the `database_db_fra1_self_hosted_2_0` database was triggered by an engineer, impacting all projects associated with this database. The root cause was identified as high memory usage, compounded by a scheduling error due to time zone confusion. - **User Impact:** Projects on the affected database experienced downtime and service disruption. - **Resolution and Steps Taken:** Transparent communication was maintained with affected users throughout the incident. Post-incident, measures were implemented to ensure such time zone errors are avoided in the future. ###### Repeated Gateway Timeouts in Traefik Containers - **Dates:** February 13, 14, 15, 21, & 24 - **Duration:** Varied; a total of 86 minutes across all incidents - **Summary:** Multiple incidents of gateway timeouts following cloud deployments. - **Description:** Routine cloud deployments triggered gateway timeout errors due to a specific configuration introduced to Traefik (`--serversTransport.maxIdleConnsPerHost=-1`), affecting routing post-deployment. This issue was consistent across several incidents, impacting users' ability to access Appwrite Cloud through the API and console. - **User Impact:** Sporadic gateway timeout errors on the Appwrite API and console, affecting user access to Appwrite Cloud. - **Resolution and Steps Taken:** Immediate action involved restarting the Appwrite service to force correct container routing. The root cause was addressed by reverting the Traefik configuration change on February 25, 2024, which resolved the gateway timeout errors. ###### Executor Crash and Bad Webhook - **Date:** February 19, 2024 - **Duration:** 27 minutes - **Summary:** A crash in the functions executor combined with a faulty webhook led to increased API latency and failures. - **Description:** The system experienced a crash in one of its executors, triggered by a faulty webhook. This incident was compounded by a high load on the function executions, triggering multiple webhooks simultaneously, which blocked API workers ultimately leading to increased latency and monitors being triggered. - **User Impact:** Users experienced API timeouts and failed function executions, leading to service disruption. - **Resolution and Steps Taken:** Immediate actions included restarting the API and executor to recover from the crash. The problematic webhook was removed, and steps were taken to restart the webhook worker, ensuring pending jobs could continue to execute. --- ## Announcing Init: a new era begins https://appwrite.io/blog/post/init-may-2025 We are about to blow your socks off with what comes next for Appwrite. Init brings you a new week of major announcements from 19-23 May 2025. For those new to [Init](/blog/post/announcing-init): it's the start of something new here at Appwrite as we announce new products and features every day, for a week. We can't disclose anything just yet, but we can say that this launch will forever change the way you build with Appwrite. A major stepping stone into a new Appwrite era, all with one mission of making software development accessible and fun for all. ### Product Hunt We're going all out for this release and launching on Product Hunt on the 19th of May. Support the launch on Product Hunt, go to the Appwrite Product Hunt page, and click “Notify me". [Go to Product Hunt](https://www.producthunt.com/products/appwrite) Mark your calendars and be sure to check your inbox for when we go live! ### Grab your Init ticket The Appwrite design team has outdone itself this time, bringing you a fresh design. ![ticket visual](/images/blog/init-may-2025/ticket3.png) [Grab a ticket to register for Init and get updates.](https://appwrite.io/init) ### New Init. New swag ![init swag](/images/blog/init-may-2025/init3-swag.png) What would Init be without limited edition swag? Not a lot! We love bringing you new ideas and designs. We will share more details soon. All you need to do is grab a ticket and join the giveaway! [Claim your ticket](https://appwrite.io/init) ### Init events Each day of Init, we will host an online event on the Appwrite YouTube channel. From the kick-off to live demos, we will bring you fun, learning, and community! You can [RSVP on the Appwrite Discord server](https://apwr.dev/InitKickOff). Be sure to join us and grab your ticket! [Claim your ticket](https://appwrite.io/init) --- ## Integrate any external authentication solution into your Appwrite project https://appwrite.io/blog/post/integrate-custom-auth-sveltekit Whether we contribute to any existing software or build new one, user authentication is a fundamental feature our users need. Between email-password authentication, magic URLs, phone and email OTPs, and 30+ OAuth providers, Appwrite offers a variety of 1st-party offerings for your apps. However, every now and then, you will need an authentication solution beyond this list. Fortunately, Appwrite now offers a solution that allows developers to integrate any external authentication method with their Appwrite project. Therefore, in this blog, we will learn about Appwrite’s custom token authentication solution, how it works, and how you can implement it in a SvelteKit app. ### What is custom token authentication? Custom token authentication allows you to use one of Appwrite’s [Server SDKs](https://appwrite.io/docs/sdks#server) to generate tokens, short-lived secrets that can be exchanged for a session by a [Client SDK](https://appwrite.io/docs/sdks#client) to log in users. This allows you to code your own authentication methods using Appwrite Functions or your own server-side APIs. This can be beneficial in a number of scenarios, such as: 1. **Legacy system integration**: Integrate with old systems using unique authentication methods without major changes. 2. **Custom security needs**: Implement special security features like hardware tokens or voice recognition. 3. **External authentication providers**: Use providers like Clerk, SuperTokens, or Amazon Cognito, which Appwrite doesn’t support directly. 4. **Advanced user authentication**: Create more sophisticated auth workflows, for example, triggering different authentication methods based on the user's location, device, or behavior. 5. **Single Sign-On (SSO)**: Integrate with enterprise SSO solutions that use protocols like SAML or LDAP. 6. **Migration to Appwrite**: Transition smoothly from an existing authentication system to Appwrite. ### Implementing custom token authentication In order to implement custom token authentication in an application, you need to develop two distinct parts: 1. Server-side function to run the authentication flow and create a user token 2. Client-side app to trigger the custom auth flow and create a session via the token secret For the purposes of this demo application, I will be implementing these in a SvelteKit application. #### Pre-requisites Before we implement our auth flow, we must first: - Create an Appwrite project and create an API key - Set up a SvelteKit app on our local system ##### Appwrite First, we must create an account on [Appwrite Cloud](https://cloud.appwrite.io/), followed by creating a new project and an API key with the scopes `users.read` and `users.write`. ![Appwrite Project Overview](/images/blog/integrate-custom-auth-sveltekit/overview.png) > Note: If you plan to deploy this application publicly, please also add the hostname of your web app as a Web platform to the project. ##### SvelteKit To build this app, we will use SvelteKit, a framework that lets you build web applications using JavaScript. For the purpose of this blog, we will use a server function in the SvelteKit app to create our custom auth flow. However, you can also use an Appwrite function or develop your own backend API if you prefer that. We will first set up a skeleton SvelteKit project (without TypeScript): ```bash npm create svelte@latest my-project cd my-project npm i ``` Next, we shall create a `.env` file at the root of the directory and add the following: ```bash PUBLIC_APPWRITE_ENDPOINT= PUBLIC_APPWRITE_PROJECT_ID= APPWRITE_API_KEY= ``` #### Creating the server function To create our server function, which contains the custom auth flow and creates a token, we will first install the Appwrite Node.js SDK by running the following command in our terminal: ```bash npm i node-appwrite ``` We will then develop our API route `/auth` by creating a file `./src/routes/auth/+server.js` and add the following code: ```js import { Client, Users, ID, Query } from 'node-appwrite'; import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public'; // Gets the public environment variables shared with the client import { env } from '$env/dynamic/private'; // Gets the private server-only environment variable const endpoint = PUBLIC_APPWRITE_ENDPOINT; const projectId = PUBLIC_APPWRITE_PROJECT_ID; const apiKey = env.APPWRITE_API_KEY; const client = new Client() .setEndpoint(endpoint) .setProject(projectId) .setKey(apiKey); const users = new Users(client); /** * Returns user if user exists in Appwrite, if not creates a new user * * @param {string} email * @returns {Promise} user */ async function getUser(email) { try { let usersList = await users.list({ queries: [ Query.equal('email', email) ] }); if (usersList.total != 0) { return usersList.users[0]; } else { return await users.create({ userId: ID.unique(), email: email }); } } catch (err) { console.error(err); } } /** * Logic for authentication * * @param {string} email * @param {string} password * @returns {Promise} user */ async function authLogic(email, password) { try { // You can have any auth logic here. For this example, we're only matching the password with '123456' if (password === '123456') { return await getUser(email); } else { returns null; } } catch (err) { console.error(err); } } export async function POST({ request }) { try { const requestBody = await request.json(); const email = requestBody.email; const password = requestBody.password; // Call the auth logic let user = await authLogic(email, password); // If user exists, create a token if(user) { let token = await users.createToken({ userId: user.$id }); // Ideally, you should not send the token object in response. Send the token secret to the user through an alternative secure channel return new Response(JSON.stringify({ user, token }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } else { return new Response(JSON.stringify({ message: 'Invalid credentials' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } } catch(err){ console.error(err); return new Response(JSON.stringify({ message: err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } } ``` When a `POST` request is sent to this endpoint, the `POST` action uses the `authLogic` function (which can contain any custom authentication logic). On successful credentials verification, the function returns a user from Appwrite (or creates a new one) using the `getUser` function. One highly important note for production applications is that unless the client is already trusted, you should not return the token object directly to the client application. Instead, the token secret should be sent to the user over a secure channel such as email or SMS. #### Developing the client app Before we create our client app functionality, we will set up the Appwrite Web SDK. We will first install the SDK by running the following command in our terminal: ```bash npm i appwrite ``` We will then create a file `./src/lib/appwrite.js` and add the following code: ```js import { Client, Account } from 'appwrite'; import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public'; const endpoint = PUBLIC_APPWRITE_ENDPOINT; const projectId = PUBLIC_APPWRITE_PROJECT_ID; const client = new Client() .setEndpoint(endpoint) // Your API Endpoint .setProject(projectId); // Your project ID; export const account = new Account(client); ``` Next, we will develop a page at the index route of our demo app. As this is a demo app, the code will focus only on the application logic. All CSS or styling-related information will be accessible in the final project repository at the end of the blog. We will create a file `./src/routes/+page.svelte` and add the following code: ```html

    Custom Token Auth Demo

    {#if currentState == state[0]}

    Login

    {:else if currentState == state[1]}

    Token secret: {token.secret}

    {:else if currentState == state[2]}

    Session details

    {JSON.stringify(session, undefined, 4)}
    {/if} ``` The page has a `currentState` property, which helps track whether a user has yet to start the auth flow (`state[0]`), has received the token secret (`state[1]`), and has an active session (`state[2]`). In `state[0]`, the user submits their email and password (hardcoded to `123456` for the demo), which are then sent to our server function. In `state[1]`, the user can generate a session using the token secret and user ID received from our server function. In `state[2]`, the user can see the current session object, thus showcasing a successful login. #### Testing the project Once we have complete the steps above, the project is ready to test. Run the following command in the terminal: ```bash npm run dev ``` ### Next steps And with that, our demo project to try custom token authentication in Appwrite is ready! You can find the application’s complete source code at this [GitHub Repo](https://github.com/appwrite-community/appwrite-custom-token-auth-demo). Additionally, if you would like to learn more about Appwrite Auth, here are some resources: - [Appwrite Auth docs](https://appwrite.io/docs/products/auth): These documents provide more information on how to use Appwrite Auth. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## Email your users using Resend and Appwrite Messaging https://appwrite.io/blog/post/integrate-resend-smtp Pretty much every app we develop or consume requires a medium of communication with its users, whether that is to send notifications and reminders, execute marketing campaigns and newsletters, or share security-related information like one-time passwords. Such messages are often sent by apps via email. Lately, we have noticed that **Resend** has become one of the most popular email API providers among the developer community. Therefore, in this blog, we will explore what Resend is and how you can integrate it with Appwrite Messaging to send emails to your app’s users. ### What is Resend? **Resend** is a modern API service for sending emails. Unlike traditional email-sending services, Resend emphasizes clean design, ease of use, and powerful integrations, enabling developers to send, track, and manage emails with minimal friction. Some of its **key features** include: 1. **API-first design**: Resend provides a straightforward REST API, making it easy to integrate with modern applications. 2. **Custom domains**: Easily configure and send emails from your custom domain to enhance brand credibility and improve deliverability. 3. **Developer-focused**: Features like detailed logging, delivery metrics, and a clean API for easy integration align with developer needs. 4. **Support for modern languages**: Resend offers SDKs for various modern programming languages such as JavaScript, Python, Go, Rust, and more. 5. **SMTP support**: In addition to their APIs, Resend supports sending emails via SMTP, allowing seamless integration with traditional email clients and legacy systems. ### Integrating Appwrite Messaging and Resend Appwrite Messaging offers an [SMTP provider](https://appwrite.io/docs/products/messaging/smtp) that lets you send emails through a variety of email providers, which is also what we shall use to integrate Resend. There are several steps we must follow to achieve this: 1. Verify your domain on Resend 2. Create a Resend API key 3. Create SMTP provider on Appwrite Messaging (with Resend authentication details) 4. Test the provider > Create an account on [Resend](https://resend.com) and [Appwrite Cloud](https://cloud.appwrite.io) to proceed further with this tutorial. #### Step 1: Verify your domain on Resend To send emails via Resend, you must own and [verify a domain](https://resend.com/docs/dashboard/domains/introduction). To do so, visit the [Domains page](https://resend.com/domains) on Resend and add your domain. Resend will then share with you a set of entries that you must add to your domain's DNS configuration. Those entries will be of three types: - **DKIM**: list of IP addresses authorized to send email on behalf of your domain - **SPF**: public key used to verify email authenticity - **DMARC**: builds additional trust with mailbox providers Adding the **DKIM** and **SPF** DNS entries is mandatory; however, the **DMARC** DNS entry is optional to add. > Note: Resend recommends using subdomains instead of the root domain, which you can learn more about in their [docs](https://resend.com/docs/knowledge-base/is-it-better-to-send-emails-from-a-subdomain-or-the-root-domain). ![Verify your domain](/images/blog/integrate-resend-smtp/domain.png) Once you add the records, Resend will verify the domain and notify you on the same page. #### Step 2: Create a Resend API key Next, you must create an API key to send emails using Resend. To do so, visit the [API Keys page](https://resend.com/api-keys) on Resend and create one. Ensure you save your key once it is created as Resend will not let you view it again for security purposes. ![Create a Resend API key](/images/blog/integrate-resend-smtp/api-key.png) #### Step 3: Create SMTP provider on Appwrite Messaging Once we have our verified domain and Resend API key, we must create an SMTP provider on Appwrite to send emails to our app’s users. To do so, create a project on Appwrite Cloud, go to the **Messaging** page from the left sidebar, click on the **Providers** tab, and create a new **Email** provider. In the Email provider setup wizard, select the **SMTP** provider. ![Add email provider](/images/blog/integrate-resend-smtp/provider.png) The SMTP provider will require you to mandatorily add the following details: | **Field name** | | | --- | --- | | **Server host** | `smtp.resend.com` | | **Server port** | `587` | | **Username** | `resend` | | **Password** | Enter your **Resend API key** | | **Encryption** | **TLS** *(enable the Auto TLS checkbox)* | | **Sender email** | Enter the **email address** in the format `@` | ![Add SMTP info](/images/blog/integrate-resend-smtp/smtp.png) With these details, Appwrite Messaging will now use Resend to send email messages to your users. #### Step 4: Test the provider > For this step, you must either have a [topic](https://appwrite.io/docs/products/messaging/topics) or a [target](https://appwrite.io/docs/products/messaging/targets) ready on Appwrite. To test the provider via the Appwrite console, you must go to the **Messaging** page on your Appwrite project, click on the **Messages** tab, and create an **Email** message. You can create a test message, add a topic or target, and set the schedule option to `Now` to send the message immediately. Once the message is sent, you will be able to find it on both the **Messaging** page in your Appwrite project and the [Emails page](https://resend.com/emails) on Resend. ![Send an email](/images/blog/integrate-resend-smtp/email.png) You can also [send emails programmatically](https://appwrite.io/docs/products/messaging/send-email-messages#send-emails) via Appwrite’s server-side SDKs. ### More resources And with that, you can now send emails to your users using Resend and Appwrite Messaging. If you would like to learn more about both, we have some resources that you should visit: - [Resend’s developer documentation](https://resend.com/docs/introduction) - [Appwrite Messaging explained](https://appwrite.io/blog/post/messaging-explained) - [Send email messages through Appwrite Messaging](https://appwrite.io/docs/products/messaging/send-email-messages) - [Appwrite Messaging API reference](https://appwrite.io/docs/references/cloud/server-nodejs/messaging) --- ## Integrate SQL, NoSQL, Vector, Graph, or any database into your Appwrite project https://appwrite.io/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project Databases allow you to store and query your application’s information persistently. Storing is usually straightforward, but querying data optimally is a major challenge for applications at any scale. Instead of simply saying `it depends`, we have prepared a list of Cloud database providers, explained their unique features, and shared examples where they shine. For each database, we also provide an Appwrite Function that allows you to integrate the database into your Appwrite project. #### Appwrite Database Most applications don’t include complex database logic, at least not at the beginning. Typically, applications start with basic features that bring value to some customers and gradually improve their features to provide value to a wider range of users. For such database needs, you can use Appwrite Database directly. It comes included in your Appwrite project, so no additional setup is needed to connect to it. Appwrite Databases include the most commonly needed database features such as filtering, sorting, pagination, and more. It’s best to use Appwrite Databases as your main database for an Appwrite project since it’s inside Appwrite’s ecosystem and easily integrates with Appwrite Auth permissions. For more specific use cases, different databases may offer more features and better performance. For example, a search query in the Appwrite Database is perfect for a search input, but if primary feature of your product is searching, you could look for a robust search engine. Database with dedication towards searching could offer mistake tolerance, synonym awareness, or result personalization. Alternatively, you might be building a project on top of data you already have in an external database. Your team may be very experienced with self-hosting a database, or your client may require the use of a specific database. There are wide variety of reasons why a separate database might be a right choice for your project. We are well aware of those situations, and Appwrite was built with that in mind. It’s deeply embedded in our philosophy to meet developers where they are and allow them to work with any tools or technologies that they need and love. #### Neon - Serverless Postgres Neon provides a managed Postgres database, which is famously known for its SQL syntax and ability to store structured data. For most databases, it’s beneficial for data to be structured. Structure gives data predictability and reliability, which is key to any production-ready application. SQL databases are also preferred when relations between data play a big role in preventing data duplication and ease of updating the data in one place. Postgres specifically allows you to add extensions to store and query geospatial and vector data efficiently. To integrate with Neon, you can set up an Appwrite function and prepare a Postgres client. ```jsx import postgres from 'postgres'; let client; export default async ({ req, res, log, error }) => { // Connect to database if(!client) { const { PGHOST, PGDATABASE, PGUSER, PGPASSWORD, ENDPOINT_ID } = process.env; client = postgres({ host: PGHOST, database: PGDATABASE, username: PGUSER, password: PGPASSWORD, port: 5432, ssl: 'require', connection: { options: `project=${ENDPOINT_ID}`, }, }); } // Rest of the code }; ``` Next, you can run SQL queries to create a table, insert data, read it, and much more. ```jsx // Prepare table await client`CREATE TABLE IF NOT EXISTS warehouses ( id SERIAL PRIMARY KEY, location VARCHAR(255), capacity INT )`; // Insert data const location = `Street ${Math.round(Math.random() * 1000)}, Earth`; // Random address const capacity = 10 + Math.round(Math.random() * 10) * 10; // Random number: 10,20,30,...,90,100 await client`INSERT INTO warehouses (location, capacity) VALUES (${location}, ${capacity})`; // Query data const page = 1; const limit = 100; const warehouses = await client`SELECT * FROM warehouses LIMIT ${limit} OFFSET ${(page - 1) * limit}`; return res.json(warehouses); ``` You can find an entire example in our [query Neon Postgres](https://github.com/appwrite/templates/tree/main/node/query-neon-postgres) function template, which is also available on your Appwrite Console in the Functions Marketplace. ![Neon Postgres Console](/images/blog/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project/neon.png) #### MongoDB Atlas Atlas, a managed cloud platform by the MongoDB team, is a cloud provider of MongoDB clusters. MongoDB database is well-known for its JSON document structure. This approach allows you to store unstructured data optimally, with a huge improvement boost to write operations. For instance, usage stats or sales history could be stored in MongoDB and later processed to gain more insights into app analytics. While MongoDB shines the most in storing unstructured data, nowadays, it provides all the capabilities developers expect from a relational database. If you don’t like SQL queries, MongoDB is a great choice. With MongoDB, integration becomes simpler, since you don’t need to define structure of collections or documents. After preparing a client, you can directly start writing and querying data. ```jsx import { MongoClient, ServerApiVersion } from 'mongodb'; let client; export default async ({ req, res, log, error }) => { // Connect to database if(!client) { client = new MongoClient(process.env.MONGO_URI, { serverApi: { version: ServerApiVersion.v1, strict: true, deprecationErrors: true, } }); await client.connect(); } // Insert data const location = `Street ${Math.round(Math.random() * 1000)}, Earth`; // Random address const capacity = 10 + Math.round(Math.random() * 10) * 10; // Random number: 10,20,30,...,90,100 await client.db("main").collection("warehouses").insertOne({ location, capacity }); // Query data const page = 1; const limit = 100; const cursor = client.db("main").collection("warehouses") .find().limit(limit).skip((page - 1) * limit); const docs = []; for await (const doc of cursor) { docs.push(doc); } return res.json(docs); }; ``` A better-structured example can be found in the [query Mongo Atlas](https://github.com/appwrite/templates/tree/main/node/query-mongo-atlas) function template and also on your Appwrite Console. ![MongoDB Atlas Console](/images/blog/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project/mongodb.png) #### Redis Redis is a widely used key-value database, most often serving as a cache layer in front of another database. It’s faster than the underlying database since Redis stores data in memory. While the cache is a typical use case for Redis, it can be used for much more since it can also store data persistently to disk. Its key-value approach renders very fast read and write operations, with the latest Redis version also supporting querying capabilities. Other use cases for Redis include pub/sub, atomic counters, distributed locks, and message queues. You can start integrating with Redis by creating and connecting a client. ```jsx import { createClient } from 'redis'; import { createHash } from 'node:crypto'; let client; export default async ({ req, res, log, error }) => { // Connect to database if(!client) { const { REDIS_PASSWORD, REDIS_HOST } = process.env; client = createClient({ password: REDIS_PASSWORD, socket: { host: REDIS_HOST, port: 14714 } }); await client.connect(); } // Rest of the code }; ``` Next, you can start inserting data. Since Redis is a key-value store, we need to create a unique key for every value. In the example below, an MD5 hash is used because the MD5 hash of the query is what’s commonly used as a key. ```jsx // Insert data const location = `Street ${Math.round(Math.random() * 1000)}, Earth`; // Random address const capacity = 10 + Math.round(Math.random() * 10) * 10; // Random number: 10,20,30,...,90,100 const hash = createHash('md5').update(location).digest('hex'); await client.hSet(`warehouses:${hash}`, { location, capacity }); ``` Querying data is as simple as fetching it using the key (MD5 hash). Many more operations are supported, such as JSON or array manipulation, but for simplicity, you can read a value by a key. ```jsx // Query data const warehouses = await client.hGetAll(`warehouses:${hash}`); return res.json(warehouses); ``` A working example can be found in our [query Redis Labs](https://github.com/appwrite/templates/tree/main/node/query-redis-labs) function template or in your Appwrite Console. ![Redis Labs Console](/images/blog/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project/redis.png) #### Neo4j AuraDB AuraDB is a cloud offering of Neo4j, the leading graph database. Using Cypher, a graph query language, Neo4j allows you to store data with attention to their relations. Non-relational data can live in the Neo4j database but it’s a very rare use case. Usually, every node is connected to another node, creating a large net of relations. Graph database gives benefits similar to relational databases, but to a much larger extent. Compared to databases like Postgres, Neo4j is much more focused on relations and encourages a large chain of relations. With relations between all data, features such as recommendation feed or friend discoverability become effortless. To use Neo4j with Appwrite Functions, you can create a function and connect using the Neo4j driver. ```jsx import { driver, auth, int } from 'neo4j-driver'; let client; export default async ({ req, res, log, error }) => { // // Connect to database if(!client) { const { NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD } = process.env; client = driver(NEO4J_URI, auth.basic(NEO4J_USER, NEO4J_PASSWORD)) } // Rest of the code } ``` Next, you can insert data as nodes with relations between them. You can use `MERGE` clause to ensure a node is re-used instead of creating duplicates. ```jsx // Insert data const categories = [ 'electronics', 'fashion', 'furniture', 'cars' ]; const category = categories[Math.floor(Math.random() * categories.length)]; // Random category const location = `Street ${Math.round(Math.random() * 1000)}, Earth`; // Random address const capacity = 10 + Math.round(Math.random() * 10) * 10; // Random number: 10,20,30,...,90,100 await client.executeQuery( ` MERGE (c:Category {name: $category}) CREATE (w:Warehouse {location: $location, capacity: $capacity}) CREATE (w)-[:STORES]->(c) `, { location, capacity, category }, { database: 'neo4j' } ); ``` Finally, you can view your data using `RETURN` clause in a query. ```jsx // Query data const page = 1; const limit = 100; const offset = (page - 1) * limit; const graph = await client.executeQuery( ` MATCH (w:Warehouse)-[:STORES]->(c:Category) RETURN w, c SKIP $offset LIMIT $limit `, { limit: int(limit), offset: int(offset) }, { database: 'neo4j' } ); return res.json(graph); ``` You can look at the entire example put together in the [query Neo4j AuraDB](https://github.com/appwrite/templates/tree/main/node/query-neo4j-auradb) template or in your Functions Marketplace in Appwrite Console. ![Neon4j AuraDB Console](/images/blog/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project/neo4j.png) #### Upstash Vector Upstash is a data platform that offers services such as Redis, Kafka, or QStash, but most importantly, a vector database. Vector database stores multi-dimensional numeric representations of your data (embeddings) in an index, allowing you to find similarities between them quickly. There are AI models to generate embeddings from text, music, or images, which is the main purpose of vector databases. Using a AI model such as a large language model (LLM), you could create a vector database of your product names. Later, a visitor may be looking for a flower pot, but thanks to the vector database, your website can now recommend watering cans and fertilizer. Integrating with Upstash Vector is unbelievably simple. Usually, you would have to connect to an AI model yourself to generate embeddings. In latest Upstash, you can pick model when creating an index, and the embedding is created for you automatically, for any input you provide. With that in mind, you can create an Appwrite Function that connects to Upstash, and inserts your data. ```jsx import { Index } from "@upstash/vector" let client; export default async ({ req, res, log, error }) => { // Connect to database if(!client) { const { UPSTASH_URL, UPSTASH_TOKEN } = process.env; client = new Index({ url: UPSTASH_URL, token: UPSTASH_TOKEN }); } // Insert data const categories = ['electronics', 'fashion', 'furniture', 'cars']; const category = categories[Math.floor(Math.random() * categories.length)]; // Random category const name = `Product #${Math.round(Math.random() * 1000)}`; // Random name const price = 10 + Math.round(Math.random() * 90); // Random number between 10 and 100 const id = `${new Date().getTime().toString(16)}${Math.round(Math.random() * 1000000000).toString(16)}`; // Unique ID await client.upsert({ id, data: `product name ${name} in category ${category} priced at ${price}€`, metadata: { name, category, price }, }); // Rest of the code } ``` After inserting data, Upstash takes a few seconds to generate embeddings and store it in the database. Once the index is ready, you can query your database and look for similar products. ```jsx const embedding = await client.query({ data: "product name Airpods in category electronics priced at 36€", topK: 1, includeVectors: true, includeMetadata: true, }); return res.json(embedding); ``` If needed for your use-case, you can disable automatic embedding generation from Upstash, and use any AI model of your choice. You can use the [query Upstash Vector](https://github.com/appwrite/templates/tree/main/node/query-upstash-vector) Appwrite Function template as a reference for your implementation. Additionally, you can find this example in Appwrite Console when setting up a new function. ![Upstash Vector Console](/images/blog/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project/vector.png) #### **Next steps** Your database journey doesn’t end here. There are other databases you might already love, or look forward to trying in your next project. Thanks to nature of Appwrite Functions, your imagination is the limit. We look forward showcasing more databases and how you can integrate them with Appwrite. If you want to share your favorite database with us, or need help integrating with your database, I invite you to [Join our Discord](https://appwrite.io/discord) community. Lastly, if you are feeling inspired, you can contribute to templates you have read about. In our [Appwrite Functions templates](https://github.com/appwrite/templates) repository, we implemented integrations in Node.JS, but developers use a dozen other languages. We would love to see issues and pull requests converting the function into other languages. --- ## Introducing Appwrite's React Native SDK in open beta https://appwrite.io/blog/post/introducing-appwrite-react-native-sdk If you’re a mobile developer who doesn’t (want to) use Flutter, we have great news for you. Appwrite now has a React Native SDK in open beta. This will allow more mobile developers to benefit from Appwrite, giving everything you need to build your mobile applications backend, without the hassle of building it yourself. #### A brief history of React Native [React Native](https://reactnative.dev/) has been a game-changer since its inception. Launched by Facebook in 2015, it emerged from the need to unify the development stacks for iOS and Android platforms, enabling a single codebase for both. Its importance to the developer community is monumental, enabling rapid development, offering native capabilities, and using one codebase for both iOS and Android platforms. With over 2,000 contributors on [GitHub](https://github.com/facebook/react-native) and used by thousands of apps worldwide, React Native has significantly contributed to the mobile development landscape by making app development more accessible, efficient, and scalable. #### Appwrite and React Native The addition of the SDK to Appwrite is great news for React Native mobile developers. Appwrite's commitment to providing a secure, scalable backend, combined with React Native's efficiency and native capabilities, creates a strong toolkit for building mobile applications. This integration means developers can now enjoy the best of both worlds: Appwrite's [ready-to-use APIs](/docs/references) for Authentication, Database, Storage, Messaging, Realtime, and more, alongside React Native's seamless developer experience and native feel. Appwrite already has a Web SDK, so why a dedicated React Native SDK? React Native requires abstractions of device and system APIs to access device permissions, cameras, storage, gyroscope, and more. Appwrite leverages [Expo](https://docs.expo.dev/) to implement APIs such as Appwrite Storage that require access to device and system APIs. Why is this good news for the [React Native community](https://www.reddit.com/r/reactnative/)? It simplifies the development process significantly. With Appwrite's React Native SDK, setting up backend services becomes as straightforward as developing the front end. This means less time worrying about backend complexities and more time crafting exceptional user experiences. As the SDK is released in open beta, we will be working with the React Native community to improve it. We invite all developers to use the SDK and share feedback to help us make it more stable in the next few months. #### Building mobile apps What can you build with Appwrite and React Native? Basically, anything you can think of from social media platforms and e-commerce apps to productivity tools and interactive games. Appwrite even supports push notifications for your mobile app with [Messaging](/docs/products/messaging/send-push-notifications). We’ve created a playground on [GitHub](https://github.com/appwrite/playground-for-react-native) where we will add simple ideas for getting started with Appwrite and React Native. You can also find inspiration from other Appwrite projects on [builtwithappwrite.io](http://builtwithappwrite.io) to see how other mobile developers have used Appwrite as their backend. If you are used to working with [Firebase and React Native](https://stackshare.io/react-native-firebase/alternatives) but have been looking for an open-source alternative, Appwrite is now a solid choice. #### Getting started with Appwrite and React Native Diving into Appwrite's React Native SDK is easy. Here's how to get started: ##### Step 1: Set up Appwrite First thing first, you must set up an Appwrite project. To do so, you must either [create an Appwrite Cloud account](https://cloud.appwrite.io) or [self-host Appwrite](/docs/advanced/self-hosting) on your system. ![qa.appwrite.org_console_project-test_overview_platforms.png](/images/blog/introducing-appwrite-react-native-sdk/appwrite.png) Once that is done, under **Add a platform**, add an **Android app** or an **Apple app**. You can skip optional steps. - **iOS steps:** Add your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode. - **Android steps:** Add your app's **name** and **package name.** Your package name is generally the **applicationId** in your app-level `build.gradle` file. #### Step 2: Install the React Native SDK Once Appwrite is up and running, install the React Native SDK in your project. This will connect your React Native app with Appwrite's suite of backend services. You can install the SDK (and necessary dependencies) using the following command: ```bash npx expo install react-native-appwrite react-native-url-polyfill ``` ##### Step 3: Explore the Documentation Familiarize yourself with [Appwrite's documentation](/docs). It's packed with tutorials, examples, and API references designed to get you up to speed in no time. ##### Step 4: Start Building With everything in place, you're ready to start building your app. The React Native SDK is designed to be intuitive, allowing you to implement features like authentication, database operations, and file storage with ease. ```js import { Client, Account, ID } from 'react-native-appwrite'; const client = new Client(); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint .setProject('455x34dfkj') // Your project ID .setPlatform('com.example.myappwriteapp'); // Your application ID or bundle ID const account = new Account(client); // Register a user account.create({ userId: ID.unique(), email: 'me@example.com', password: 'password', name: 'Jane Doe' }) .then(function (response) { console.log(response); }, function (error) { console.log(error); }); ``` Read the [quick start](/docs/quick-starts/react-native) to get started or find a [tutorial](/docs/tutorials/react-native/step-1) to build an ideas tracker with React Native. #### Resources Visit our documentation to learn more about our SDKs, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our source code. - [Docs](/docs/sdks) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Introducing Database Backups https://appwrite.io/blog/post/introducing-database-backups Database integrity is critical for any data heavy application. That's why we're happy to introduce Database Backups on Appwrite, a feature designed to give you full control over the safety and recoverability of your data without any downtime. This is also a crucial milestone for Appwrite Cloud to get closer to the much anticipated GA release. Whether you're running production workloads or handling large datasets, Appwrite now makes it easier to back up your databases, securely and efficiently with just a few clicks on your [Appwrite Console](https://cloud.appwrite.io/). **Database Backups are now available on Appwrite Cloud for all paid plans.** ### What are Appwrite Database Backups? Appwrite Database Backups help you take a backup of your database with minimal impact to your production performance, ensuring your operations continue smoothly. #### Encryption Security is a top priority, and Appwrite ensures that all database backups are fully encrypted both in transit and at rest. This encryption protects your sensitive information from unauthorized access or breaches. Even if a backup was intercepted or compromised, the encryption ensures that your data remains unreadable to unauthorized parties, providing an extra layer of security essential for meeting compliance requirements and ensuring data integrity. Appwrite uses standard TLS communication to transmit all backups securely and ensures that the files are encrypted while at rest. #### Hot backups All database backups are hot, meaning they are created without causing any downtime or interruptions to your services. This is especially critical for production environments where uptime and performance are key. By utilizing hot backups, your application can continue to operate as usual. At the same time, your data is being securely copied in the background, ensuring that backups are taken without impacting user experience or slowing down your operations. #### Remote destination Backups are stored in a geographically separate location, isolated from our primary data center in Frankfurt. This ensures full redundancy and strengthens your disaster recovery strategy. In the event of localized incidents—such as hardware failures, security breaches, or natural disasters—your data remains secure and recoverable, enabling rapid failover and minimizing operational downtime. ### Automating Backups with Policies You can enable automated backup policies to run in the background. This will ensure you have a daily backup that is stored for 7 days as part of the Pro plan. For added flexibility, manual backups can be initiated at any time. This is particularly useful for pre-deployment snapshots or in cases where immediate backup and restoration are required, giving you complete control over your data integrity with minimal downtime. #### Scale and Enterprise plan We offer more customization for the [Scale and Enterprise plans](https://appwrite.io/pricing) using backup policies that offer granular control over your backup strategy, allowing you to: - Schedule backups at custom intervals or predefined schedules (e.g., daily, weekly, monthly) to match your operational requirements. - Set retention periods, ensuring backups are kept as long as necessary without accumulating unnecessary storage. - Specify precise execution times to align with low-traffic periods or maintenance windows, seamlessly integrating into your workflow. ### Secure your databases Database Backups is designed to offer comprehensive, secure, and automated backups for your project's Databases. With hot, encrypted backups, automated policies, and full control over your recovery process, it's an effective tool to safeguard your data. We're excited to see you use this new feature. Do you have feedback? Let us know on [Discord](https://appwrite.io/discord). Visit our [documentation](https://appwrite.io/docs/products/databases/backups) to learn how to set up a backup policy. --- ## Introducing Enum SDK support: enhanced DX across SDKs https://appwrite.io/blog/post/introducing-enum-sdk-support We are excited to announce a significant enhancement to the development experience across all Appwrite client and server-side SDKs, Enums SDK Support. This enhancement is designed to streamline the development process, improve code quality, and facilitate seamless integration with Appwrite's backend services. ### What are Enums? Enums, or enumerations, are a powerful programming construct that enables developers to define a set of named constants. These constants make code more readable and less error-prone by providing meaningful names for values rather than using numbers or strings directly. Enums are widely used for representing a set of possible values for a variable, making code easier to understand, maintain, and debug. ### Introducing Enum SDK support in Appwrite Currently, when you want to use an endpoint with a predefined set of allowed values, you must look up the exact value in the documentation. This can be time-consuming and error-prone. This new feature, however, ensures you can use more readable and self-documenting code instead of having to remember and type out raw values. Bringing along benefits such as massively reducing friction when using routes that have predefined parameters. One of the most common examples of SDK Enums is OAuth providers. To log in with Apple, you must pass the `apple` string as the provider. With enums, you'll be able to pass `OAuthProvider.Apple` instead. ```client-web import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); account.createOAuth2Session({ provider: OAuthProvider.Apple, success: '', failure: '', scopes: ['email', 'profile'] }); ``` This new feature will improve the experience of building with Appwrite and lower the barrier for new developers to start. As always, we aim to make life easier, not harder, and we look forward to your feedback. ### Resources Visit our documentation to learn more about Enums SDK support, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our source code. - [Docs](/docs/sdks#enums) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) Enums will be available as part of the Appwrite 1.5 release on [GitHub](https://github.com/appwrite/appwrite) and [Cloud](https://cloud.appwrite.io/register) in March 2024. --- ## Leveling up the Appwrite Functions ecosystem https://appwrite.io/blog/post/introducing-functions-ecosystem We're excited to bring you major improvements to Appwrite Functions, making them more versatile and powerful than ever before. With the new Functions, you can now stay within the Appwrite ecosystem. The fully integrated features reduce friction points, allowing you to focus on development while trusting that your Functions will run smoothly and automatically. Previously, Appwrite Functions faced limitations in handling file-based tasks and real-time operations due to slow synchronous execution and cumbersome API key management. With the introduction of easier Appwrite SDK integration within Functions, these constraints are lifted, opening doors to more use cases and more efficient workflows. ### Faster Functions cold-starts We've significantly accelerated Functions cold-start times through infrastructure optimizations. This means you can expect faster response times for all your Functions, both new and existing. Cold starts occur when a Function is invoked for the first time or after a period of inactivity. This initial delay is caused by the system provisioning resources and loading the function's code. Now, all your Functions, regardless of when they were created, benefit from faster startup times without any additional effort on your part. There's no need to make any changes to your code or configuration – the improvement is automatic. ### Dynamic API keys API key management is a time-consuming, error-prone task that can distract developers from building core features. Keeping track of multiple API keys across different environments (development, staging, production) can be overwhelming, but if compromised, they can lead to unauthorized access, data breaches, and financial loss. The stakes are high. To combat this, we've introduced automatically generated, short-lived API keys for each execution. They’re designed to simplify managing and updating your API keys, saving you time and reducing security risks. Here's how you can use them: ```jsx import { Client, Databases } from 'node-appwrite'; export default async ({ req, res }) => { const client = new Client() .setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT) .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(req.headers['x-appwrite-key']); const databases = new Databases(client); // Your function logic here return res.empty(); }; ``` With this setup, you no longer need to manually create and manage API keys. The `APPWRITE_FUNCTION_API_ENDPOINT` and `APPWRITE_FUNCTION_PROJECT_ID` environment variables are automatically provided, and the API key is available in the request headers. ### Delayed executions Now, you can schedule Functions to run at a specific time in the future, unlocking new opportunities for time-sensitive workflows. Here's how to create a delayed execution: ```jsx import { Functions, ExecutionMethod } from 'appwrite'; const functions = new Functions(client); const invoiceDate = new Date(); invoiceDate.setDate(invoiceDate.getDate() + 30); await functions.createExecution({ functionId: 'invoicesApi', // Function ID body: '{"userId":"ngu9ife0efwed"}', // Body async: true, // Async execution xpath: '/v1/invoices/exports', // Path method: ExecutionMethod.POST, // Method headers: {}, // Headers scheduledAt: invoiceDate.toISOString() // New scheduledAt attribute }); ``` This feature is perfect for scheduling marketing emails, cleanup tasks, or precisely timed events. ![Delayed-executions](/images/blog/init-day2/1.png) ### Binary executions We've long relied on JSON and XML for structured data exchange, but these formats fall short when it comes to handling files like images, audio, or video. With the introduction of binary executions, Appwrite Functions can now seamlessly process and transfer these file types. Functions can now handle binary data, both as input and output, which is particularly useful for file processing, AI interactions, and more. This allows you to work with formats like multipart form-data, Protocol Buffers, or raw binary data directly within your functions. Here’s how you can use them: ```jsx import OpenAI from "openai"; import fetch from "node-fetch"; export default async ({ req, res, log, error }) => { // Receiving binary data const buffer = req.bodyBinary; // Using binary data (create image variation) const openai = new OpenAI(); const images = await openai.images.createVariation({ model: "dall-e-2", image: buffer, n: 1, size: "1024x1024" }); const image = await fetch(images.data[0].url); // Sending binary data return res.binary(await image.arrayBuffer()); } ``` This allows for new use cases like sending files to AI services or generating files on the fly. ### Execution and deployment filtering You can now filter Function executions and deployments based on various attributes, making it easier to monitor and debug your Functions: ```jsx // Example of filtering executions (specific implementation may vary) const executions = await functions.listExecutions({ functionId: 'functionId', queries: [ Query.equal('status', 'completed'), Query.equal('requestMethod', 'POST'), Query.equal('deploymentId', 'latest') ] }); ``` Of course, it’s also possible to filter executions from the Appwrite Console: ![Execution-filters](/images/blog/init-day2/2.png) You can now quickly find and analyze specific executions based on criteria like status, request method, or deployment ID. ### Build anything With the new Functions ecosystem, you can now use Functions for a wider range of applications, from AI-powered image generation to scheduled tasks and beyond. We’re excited to see what developers will create with this expanded toolkit. Here are a few ideas to get you started: - Build AI-powered image generation services - Create scheduled data backup and cleanup routines - Implement sophisticated marketing automation workflows - Develop real-time data processing pipelines - And more… Check out these resources to start building with the new Functions ecosystem: - [Appwrite Functions documentation](https://appwrite.io/docs/products/functions) - [Join our Discord community](https://discord.gg/appwrite) - [Learn about Appwrite Init](https://www.appwrite.io/init) --- ## Introducing Imagine: from ideas to real products https://appwrite.io/blog/post/introducing-imagine When we started Appwrite, our mission was clear: reduce the operational and infrastructural burden of building software so developers could focus on what actually matters - product, logic, and user experience. We set out to remove unnecessary complexity, give teams real control over their backend, and make production-grade infrastructure accessible without forcing developers to fight it. That principle has guided every major decision we have made. Over the past few years, we have also watched a new wave of tools emerge. AI made software creation feel almost magical. You could describe an idea in plain language and get something that looked like an application within minutes. But in practice, most of these tools stopped at the surface. They produced shallow prototypes, tightly constrained demos, or code that could not realistically be deployed, scaled, or maintained. The magic was real, but it ended too early. Today, we are taking the next step in addressing that gap. We are introducing [Imagine](https://imagine.dev), a platform that uses AI to translate ideas into real, production-ready applications, backed by Appwrite Cloud. Imagine does not generate isolated code snippets or disposable demos. It produces applications that are wired to real services - authentication, databases, storage, and functions - and designed to evolve beyond the first prompt. The goal is not to abstract infrastructure away entirely, but to remove it as a constant obstacle in the earliest and most fragile stages of building. {% youtube src="https://www.youtube-nocookie.com/embed/xXYCVfA8joM" thumbnail="/images/blog/introducing-imagine/imagine-1.png" /%} Imagine is built on a simple observation: most ideas never fail because they were engineered poorly. They fail because they never make it far enough to be tested, shared, or used. The gap is not between "AI-generated" and "expert-built" software. The gap is between something that exists and something that never gets created at all. Strong engineering remains rare and deeply valuable. That does not change. What does change is how quickly ideas can move from concept to something tangible. When experienced builders use AI on top of solid infrastructure, they move faster and explore more. When less technical creators have access to the same foundations, more ideas get a chance to exist in the first place. This is the direction we are taking with [Imagine](https://imagine.dev). Not to lower the bar for production software, but to lower the cost of getting started, communicating intent, and turning an idea into a system that can grow. ### What is Imagine, and how it connects to Appwrite Imagine is an agentic application-building platform designed and operated by the same team that builds Appwrite. It is not layered on top of the platform, and it is not an external system stitched together through adapters. Imagine is natively integrated into Appwrite’s developer platform and operates within a controlled environment defined by Appwrite’s primitives, APIs, and operational guarantees. Every part of what Imagine produces - frontend structure, backend services, infrastructure configuration, and deployment - is generated with a first-party understanding of Appwrite’s data models, permission system, and execution constraints. Imagine is trained on Appwrite’s patterns and best practices, and it generates applications that align with how Appwrite Cloud is actually meant to be used in production. Because of this, there is no glue code, no schema translation layer, and no brittle dependency chain between systems. When Imagine creates an application, it provisions real Appwrite Cloud resources: databases and tables, columns and indexes, functions, authentication settings, storage buckets, permissions, and deployment targets. The result is a real Appwrite project, not an abstraction that has to be reconciled later. ![Imagine](/images/blog/introducing-imagine/imagine-2.png) All generated code is transparent and approachable. You can inspect it, extend it, and incrementally take more control as your application evolves. As Imagine matures, we are working toward making this transition even smoother, but from day one the system is designed to grow alongside real development workflows rather than block them. The frontend is built on top of [TanStack Start](https://tanstack.com/). This is a deliberate choice that supports the controlled environment approach. By relying on proven, explicit patterns for state management, data fetching, and routing, Imagine can generate applications that are predictable, composable, and easier to reason about, both for AI and for humans. Combined with Appwrite Cloud handling identity, data, storage, and execution behind the scenes, this allows Imagine to focus on assembling real systems rather than improvising fragile solutions. This tight integration is intentional. By building both the platform and the system that generates against it, we can optimize for coherence, reliability, and long-term product evolution. That is what allows Imagine to support real products, not just early-stage demos. With Imagine, you can build anything from internal tools and dashboards to full web applications and marketing sites. Mobile support is not available yet, but it is actively being worked on and will be added. ### What you can build with Imagine Imagine is not a single feature or workflow. It is a complete environment for turning an idea into a running application, with the core building blocks already in place. Instead of assembling tools, services, and infrastructure piece by piece, Imagine provides a coherent set of capabilities that work together from the first prompt through deployment and iteration. Below is an overview of the core capabilities available in Imagine today. #### AI application builder The AI builder lets you describe an application using natural language or by uploading visual references. From that input, Imagine generates a complete frontend with real, readable code. You can iterate conversationally, refine behavior, and adjust structure as the application takes shape. Behind the scenes, Imagine is powered by a system of specialized agents rather than a single model or long, simplistic and complex system prompt. Each agent focuses on a different part of the workflow, such as product structure, frontend architecture, backend modeling, data schemas, permissions, and infrastructure. Together, they simulate the way a strong product team breaks down and builds an application. The agents operate in a coordinated, controlled workflow where outputs are validated and refined before being assembled. The system is designed to detect inconsistencies, correct mistakes, and recover from failed attempts without requiring a full restart. In internal testing, this approach has shown promising results across both simple projects and more complex applications with multiple dependencies. #### Built-in code editor A built-in code editor is included to give you direct visibility into the output. You can inspect what is generated, make manual changes, and debug issues without leaving the environment. The goal is not to hide the code, but to make it easier to reach a useful starting point. #### Built-in cloud backend Every Imagine project runs on Appwrite Cloud by default. There is no separate provisioning step and no need to wire external services together. You get access to: - Authentication, including OAuth, email and password, anonymous users, JWT, and more - Databases with built-in permission models - File storage with previews and transformations - Serverless Functions with logs and debugging tools (coming soon) - Site hosting backed by a global CDN - Messaging and notifications (coming soon) - Realtime subscriptions for database and system events Because Imagine is built directly on Appwrite, the backend is not just an add-on or a generated afterthought. It is the foundation the application is built on. #### SEO-ready by default Applications generated with Imagine include core SEO fundamentals out of the box. This includes static optimization, metadata handling, structured data, sitemap generation, and search-engine-friendly rendering. These capabilities are built in, not layered on through plugins or third-party services, so teams can ship with sensible defaults from the start. #### Managed infrastructure, production by design Every application inherits Appwrite Cloud’s global infrastructure, high-availability architecture, and low-latency routing. Scaling, uptime, backups, and performance tuning are handled at the platform level. This allows teams to focus on application behavior and user experience without needing to operate or tune infrastructure early on. Appwrite Cloud auto-scale for you. #### Security as a baseline Security is treated as a default property of every generated application, not something added later, same as the approach Appwrite is known for, in Imagine all settings are tuned to be secure by default and as a last minute requirment before or after going to production. Imagine applications benefit from: - Hardened authentication flows - DDoS protection - Abuse and anomaly detection - Built-in encryption - Data migration tooling - Simple configuration controls for advanced policies in Imagine Studio These protections are applied consistently using the same security posture Appwrite Cloud applies across all projects. #### Compliance without additional overhead Applications built with Imagine inherit Appwrite’s compliance readiness, including GDPR, HIPAA, SOC-2, and CCPA. This reduces the need for bespoke audits, custom compliance setups, or additional third-party services during early and growth stages. ### How we think about pricing Imagine’s pricing is built around a simple principle: you should pay in proportion to the actual work required to turn an idea into a real product. Instead of pricing based on abstract tiers or feature gates, Imagine uses credits to represent the relative complexity of what the agent is asked to generate. A simple task, such as creating a static landing page or a basic CRUD interface, consumes fewer credits. More complex requests, such as generating an application with multiple data models, permission rules, background functions, and integrations, require more planning, validation, and iteration by the agent and therefore consume more credits. This approach allows pricing to scale naturally with the depth of what you are building. The free tier is designed to be genuinely useful. It includes enough daily credits to explore Imagine, test ideas, and experiment with real applications at an occasional scale. It is intended to let people understand what Imagine can do without pressure, rather than acting as a limited demo. Every Imagine account also includes a generous allocation of cloud resources by default. These resources are sized to comfortably support small to medium-scale websites and applications, covering hosting, storage, bandwidth, and core backend services without requiring additional configuration. For teams running production workloads, the Pro plan allows applications to grow beyond included limits using a pay-per-usage model, similar to how Appwrite Cloud operates at scale. This ensures that applications can expand smoothly as usage increases, without forcing premature upgrades or hard caps. For organizations with more advanced requirements, higher scale, or specific commercial or security needs, our team works directly with customers to provide tailored plans and configurations. If you need custom adjustments or are planning to operate at significant scale, we encourage you to [contact our team to discuss the most appropriate setup](https://imagine.dev/enterprise). ![Imagine](/images/blog/introducing-imagine/imagine-3.png) ### **Get started** The results blew us away in early tests, and we can’t wait for you to test it. Projects that took three months to build were done in just a few minutes. This is not a vibe coding tool or a simple landing page generator. Imagine building full platforms with everything that Appwrite has created and refined in collaboration with the open-source community over the years. This is the start of a new chapter for Appwrite. We urge you not to take our word for it. [Sign up](https://imagine.dev/) and be one of the first to try out Imagine and see what it can do, and as always, help us spread the word. [**Try it. Break it. Build something real with it.**](https://imagine.dev/) --- ## Introducing the new Appwrite CLI https://appwrite.io/blog/post/introducing-new-appwrite-cli We're excited to announce the new Appwrite CLI. This iteration focuses on local development and an enhanced CI/CD experience. Now, you can test changes to your functions locally, and easily apply changes to your Appwrite collection. Let’s dive into the updates to the new Appwrite CLI and how it will improve your building experience. ### Understanding past limitations Appwrite developers use the current generation of Appwrite CLI to initialize functions and collections and deploy those resources. When deploying collections, the only option is to override and delete all the collection's existing data, which is not the use case for most scenarios. The only way to test an Appwrite function is to continue deploying the function changes to the Appwrite instance, which can be aggravating for small changes. In addition, we listened to the community's requests, suggestions, and demands for Appwrite CLI and incorporated the most popular ones into the next generation of our CLI. ### The next generation of Appwrite CLI The new CLI’s main features are: - **Local development**: Test your Appwrite function locally - **Data sync**: Push and pull data between Appwrite and your CLI in a non-destructive way. - **Headless login**: Login to your Appwrite account in a non-interactive manner for CI/CD pipelines Besides those features, this release includes many improvements, bug fixes, and optimizations. ### CLI Flow When starting to work with the Appwrite CLI, there are two commands you'll need to run. The first is `appwrite login` ```bash appwrite login > ? What you like to do? Login to an account > ? Enter your email user@appwrite.io > ? Enter your password ***** ``` *If you have one or more accounts, Appwrite CLI now supports multiple accounts. Run `appwrite login` again to log in or switch to another account* After successful login, it's time to initialize the project by running `appwrite init project` ```bash appwrite init project > ? How would you like to start? Link directory to an existing project > ? Choose your organization 668d4668d4668d4 > ? Choose your Appwrite project. 668d4701668d4701 > ✓ Success: Project successfully linked. Details are now stored in appwrite.config.json file. > ? Would you like to pull all resources from project you just linked? No ``` *You can optionally pull all existing resources* from the new or linked project*.* The command will create a file named `appwrite.config.json` in the current directory. This file will include all the project settings and resource details. A good practice is to add this file to your project repo; this will let you use your version control for version management. To make it easy, we've decided to refactor and unify our terminology for the CLI commands as follows: - `appwrite init [service]` - for creating Appwrite service locally in an interactive manner - `appwrite pull [service]` - for pulling Appwrite resource from your remote Appwrite instance - `appwrite push [service]` - for pushing your local changes to your remote Appwrite instance > This terminology uses git's terminology. We don't have appwrite commit yet, but who knows. > ### Local development Appwrite Functions is one of the most loved Appwrite products. It lets you write logic in your favorite language (we support so many languages that it's hard to remember them all) and execute it only when needed! In this generation, we added the `appwrite run` command that lets you run your function locally. The only requirement from your side is to make sure you have [Docker](https://www.docker.com/products/docker-desktop/) installed before running the command. After running the command `appwrite run`, Appwrite CLI will prepare and compile your function code (for compiled languages such as `Go`) and start a local server using the available port access. Click the provided URL shown on the terminal to request your function directly. Appwrite CLI is now in listening mode. Try changing your code and seeing how the CLI rebuilds your function to match your recent changes. How cool is that! ### Database migration GitOps is a common way of tracking and migrating database changes. The latest Appwrite CLI generation includes a few features to help you migrate your database changes easily. When running `appwrite push collection`, the CLI will compare your local `appwrite.config.json` collection definition against the currently deployed remote collection and will present you with a detailed table awaiting your decision, for example: ``` Key │ Action │ Reason ──────────┼────────────┼───────────────────────────────────────────── times │ deleting │ Field isn't available in appwrite.config.json file ──────────┼────────────┼───────────────────────────────────────────── time │ adding │ Field isn't available on the remote server ──────────┼────────────┼───────────────────────────────────────────── timezone │ recreating │ size changed from 256 to 255 ℹ Info: Attribute deletion will cause loss of data ℹ Info: Attribute recreation will cause loss of data ? Would you like to apply these changes? Type "YES" to confirm. ``` In this example, we can see that because we've renamed the attribute `times` to `time,` it will get deleted and read. We must also recreate the `timezone` attribute because we've changed its size from 256 to 255. To help with the decision, you can notice two warnings: deleting or recreating a field will cause data loss. It's important to know that the data loss will affect only the recreated/deleted attribute and not the whole collection. As you can read in the next section, when pushing collections in CI/CD pipelines, you'll need to add the `--force` flag. ### CI/CD Adapting CI/CD pipelines ensures robust deployments. To support this, we have rewritten many parts of our CLI to fully accommodate non-interactive actions for all deployment-related commands. You can add the `--force` flag to any command that may ask you questions, such as `appwrite push collections,` to pre-answer all of them with `YES.` Additionally, you can use the `--all` flag to push/pull all services' available resources. Till this generation, Appwrite CLI supported non-interactive login for API-key-based authorization only, as follows: ```bash appwrite client --project-id PROJECT_ID --key APY_KEY ``` In this version, we've added a headless login, which allows users to log in and execute commands from the user level. ```bash appwrite login --email user@appwrite.com --password password ``` ### Misc - We've added the `whoami` command to get a glance at the user who is currently logged in. - Add the `-report` flag to your commands to get an automated link for creating GitHub issues on error. - Use the `-console` flag in Get/List commands to get a direct console URL in your Appwrite instance. ### Resources Visit our docs to get a detailed look at the new Appwrite CLI. If you want to start with local development immediately, check out our tutorial. Join us on Discord to participate in the discussion, and keep an eye out for more announcements this Init week. - [Appwrite CLI docs](https://appwrite.io/docs/tooling/command-line/installation) - [Discord server](https://appwrite.io/discord) - [Video tutorials](https://www.youtube.com/@Appwrite) --- ## Introducing new compute capabilities for Appwrite Functions https://appwrite.io/blog/post/introducing-new-compute-capabilities-appwrite-functions At Appwrite, we're always working to make our serverless platform better for developers like you. Our latest update to Appwrite Functions brings some exciting improvements, especially when it comes to customizing CPU and memory settings for your runtimes. This means better performance, more flexibility, and the chance to explore new and innovative use cases. We've also updated our pricing model to keep things sustainable and fair for everyone. Keep reading to learn more about these updates and how they can benefit your projects. Starting today, Appwrite developers have the ability to customize runtime specifications, allowing for tailored performance and resource allocation. Previously, all functions operated with a standard configuration of 0.5 CPU and 512MB memory. Now, Pro and Scale users can select from a range of configurations to suit their application needs better: - 0.5 CPU & 512MB RAM - 1 CPU & 512MB RAM - 1 CPU & 1GB RAM - 1 CPU & 2GB RAM - 2 CPU & 4GB RAM - 4 CPU & 4GB RAM These options enable greater performance and flexibility, allowing developers to optimize their functions based on specific requirements. For instance, resource-intensive tasks such as real-time data processing or complex computational operations can now be executed more efficiently. Additionally, enhanced memory configurations support larger datasets and more demanding applications, broadening the scope of what can be achieved with Appwrite Functions. ### Pricing Effective **March 1st,** Appwrite is introducing changes to our [pricing](https://appwrite.io/pricing) structure to accommodate the enhanced compute capabilities and ensure sustainable platform growth. Central to this update is the **GB-hours** metric, which measures usage based on the compute resources consumed over time. #### Understanding GB-hours GB-hours is calculated by multiplying the build and execution time of your functions by the amount of CPU and memory they utilize. For example: **Example**: If you run a function with 1 CPU and 1024MB memory for 2 hours, the GB-hours consumed would be: `GB-hours = CPU cores × Memory (GB) × Time (hours) = 1 × 1 × 2 = 2 GB-hours` #### Allocation and pricing plans - Free plan users will have up to 100GB of execution and build time per month. - Pro, and Scale users will have up to 1,000GB of execution and build time per month. Additional usage is available at a rate of $0.09 per GB-hour. Once the monthly GB-hours limit is reached, additional usage will automatically apply add-ons to your Pro or Scale account. To prevent unexpected charges, we strongly recommend setting a **budget cap**. The budget cap is designed to safeguard your organization by pausing services once the budget is reached for additional usage, allowing you to adjust your usage or modify your budget as needed. If you or your organization expect to use these new features extensively, you can contact our [sales team](https://appwrite.io/contact-us/enterprise) to learn more about the custom offerings available for our enterprise customers. ### Ensuring sustainability Introducing the GB-hours metric is an important step in maintaining a sustainable and fair platform for all users. By accurately tracking resource consumption, we can better manage extensive usage and ensure that our infrastructure remains robust and reliable. This approach allows us to effectively support both small-scale projects and large enterprises, fostering a balanced ecosystem. #### Future possibilities Looking ahead, the customization of runtime specifications paves the way for even more advanced features. One exciting prospect is the potential to support heavy compute runtimes that were previously only feasible through self-hosting, such as **Swift** and **Java** runtimes. This expansion will provide Appwrite developers greater versatility in choosing the best tools and languages for their applications, meeting developers where they need and feel most comfortable. --- ## Introducing new Database operators: or & contains query methods https://appwrite.io/blog/post/introducing-new-database-operators We've added two new query methods, `or` and `contains`, to Appwrite Databases. By adding array element matches, partial text matches, as well as logical OR queries, we allow for more flexibility in managing your data. These two query methods have been highly requested by the Appwrite community, and we’re excited to show you how to use them, so let’s jump in and take a look! - `contains` - partial text matches on string attributes, array element matching on array attributes - `or` - write logical OR queries ### Contains operator The contains operator is a great addition to the existing text search operators such as `startsWith` & `endsWith`, and can be used in combination with these two. With contains, we can now perform a broader search by matching against any text within a substring. This is extremely useful when searching a large body of text or when the placement of keywords is unknown. ```js db.listDocuments({ databaseId: '', collectionId: '', queries: [ Query.contains('content', ['happy', 'love']) ] }); ``` It’s important to note that the contains operator also works on array attributes as well. For example, if we set a string attribute to act as an array, you could search this array in the same way you would search any other string. ```js Query.contains('tags', ['mystery', 'comedy', 'PG-13']) ```   ### Or operator The logical OR operator allows us to nest queries in an OR condition. This gives us the ability to group queries together for more dynamic search. To use the OR operator pass `Query.or([...])` to the queries array and provide at least two queries within the nested array. ```js db.listDocuments({ databaseId: '', collectionId: '', queries: [ Query.or([ Query.contains('name','ivy'), Query.greaterThan('age',30) ]) ] }); ``` The example shown will return all people that contain the substring 'ivy' somewhere in their name OR are older than 30 years old. Previously, there was no native way to do this kind of search from Appwrite's SDKs. ### Database improvements With these new database improvements you have more possibilities for data retrieval and manipulation, and add powerful new queries and logic to Appwrite Databases for you to manage your data. Making Appwrite Databases an even more powerful and complete product for you to build with. ### Resources Visit our documentation to learn more about Database operators, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/databases/queries) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) Database operators will be available as part of the Appwrite 1.5 release on [GitHub](https://github.com/appwrite/appwrite) and [Cloud](https://cloud.appwrite.io/register) in March 2024. --- ## Introducing the Python machine learning runtime https://appwrite.io/blog/post/introducing-python-machine-learning-runtime If you're looking to build AI powered applications, you've probably considered using Python. And in case you haven't, it's probably time you did. We at Appwrite have thought about it, and our conclusion is simple. To improve the experience of building AI powered apps with Appwrite, a new runtime needs to emerge to cater to AI specific needs. We're excited to present Appwrite's newest Function runtime, **Python ML.** The new runtime has been tailored and optimised for machine learning use cases, saving you a lot of hassle and time, making building AI powered applications a whole lot easier. #### Python and Machine Learning Python's simplicity and it's rich ecosystem of data science libraries have made it the preferred languages for machine learning research and implementation. The jewels of this ecosystem are libraries and frameworks specifically designed for machine learning, such as [TensorFlow](https://www.tensorflow.org/), [PyTorch](https://pytorch.org/), [scikit-learn](https://scikit-learn.org/stable/), [Keras](https://keras.io/), and [NLTK](https://www.nltk.org/). These libraries provide pre-built functions and algorithms for common machine-learning tasks, enabling developers to build and train models quickly and efficiently. #### Why build a new runtime Appwrite's Python runtime has been one of the most popular Function runtimes. However, it has a limitation with machine learning use cases. The image was built on an Alpine Docker image. We chose Alpine because it's images offer a minimal and clean distribution of Linux. The current Python runtime has a compressed [size of under 22MB!](https://hub.docker.com/layers/openruntimes/python/v3-3.9/images/sha256-f6d89b6d2f570b8176af767fddc0b268ebeb8a9532243410e03cb60f9d9780b3?context=explore) Unfortunately, Alpine doesn't support installing machine learning libraries like `numpy` and `Tensorflow` due to the absence of some core libraries and build tools. We decided to build a new runtime image to overcome this limitation and at the same time benefit from all the hardware-specific optimisations required for running models at scale. The Python ML runtime will allow you to install all the libraries and tools required for Machine Learning with ease. While we procure and build a world class GPU infrastructure for training, fine-tuning and inference, we'd like to bring to your notice that the Python ML runtime is currently CPU-only during the Beta. We understand this can be a challenge for certain use cases, however we have some actionable tips and tricks to overcome these limitations in the interim. #### Getting started 1. Head to the [Appwrite Console](https://cloud.appwrite.io/console) then click on **Functions** in the left sidebar and then click on the **Create Function** button. ![Create Function](/images/blog/introducing-python-machine-learning-runtime/create-function.png) 2. To get started with the new runtime, navigate to your Appwrite console and select `Python (ML) 3.11` when creating a new function. 3. Follow the step-by-step wizard and create the function. Here's an example function that trains a simple neural network on the MNIST dataset using TensorFlow and TensorFlow Datasets: ```python import tensorflow as tf import tensorflow_datasets as tfds def normalize_img(image, label): return tf.cast(image, tf.float32) / 255.0, label def main(context): (ds_train, ds_test), ds_info = tfds.load( "mnist", split=["train", "test"], shuffle_files=True, as_supervised=True, with_info=True, ) ds_train = ds_train.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE) ds_train = ds_train.cache() ds_train = ds_train.shuffle(ds_info.splits["train"].num_examples) ds_train = ds_train.batch(128) ds_train = ds_train.prefetch(tf.data.AUTOTUNE) ds_test = ds_test.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE) ds_test = ds_test.batch(128) ds_test = ds_test.cache() ds_test = ds_test.prefetch(tf.data.AUTOTUNE) model = tf.keras.models.Sequential( [ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation="relu"), tf.keras.layers.Dense(10), ] ) model.compile( optimizer=tf.keras.optimizers.Adam(0.001), loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[tf.keras.metrics.SparseCategoricalAccuracy()], ) model.fit( ds_train, epochs=6, validation_data=ds_test, ) return context.res.json({"success": True}) ``` Find the full source code in our [GitHub repository](https://github.com/open-runtimes/open-runtimes/tree/main/runtimes/python-ml-3.11). #### Best practices The Python ML runtime is in *beta*. We plan to iterate in response to developer demand and needs. For now, the runtime is CPU-only. Here are some tips and tricks to help you overcome this limitation: ##### Smaller models & Tensorflow Lite Smaller models are faster to load and execute. Avoid using large models that require a lot of memory and CPU resources. Tensorflow Lite models are optimized for mobile and edge devices. They are smaller in size and can be run on the CPU. A typical tensorflow model can be 10x smaller when converted to a Tensorflow Lite model, with only a small loss in accuracy. You can convert your Tensorflow models to Tensorflow Lite models using the [Tensorflow Lite Converter](https://www.tensorflow.org/lite/convert). ##### Async executions Synchronous executions have a global timeout limit of 30s on Appwrite Cloud. Therefore, it is recommended to use asynchronous function executions for long running tasks. This will allow you to run multiple tasks concurrently, set your own timeout period and make the most of the compute resources available. Create an asynchronous execution using the `async` parameter in the [createExecution endpoint](https://appwrite.io/docs/references/cloud/client-web/functions#createExecution). If you see `FUNCTION_SYCHRONOUS_TIMEOUT` errors when executing your function, navigate to your **Function settings** and try increasing your timeout period to a larger number such 50 seconds or more. ![Settings timeout](/images/blog/introducing-python-machine-learning-runtime/settings-timeout.png) #### Training vs inference The Python ML runtime is more suitable for inference tasks. Training models on the runtime is not recommended as it can be slow and resource-intensive due to it being CPU only during the beta. We recommend training your models on a local machine or a cloud-based GPU instance and then deploying the trained model to the Python ML runtime for inference. If you have training that does not require intensive GPU resources, you can train your models during the build step of the function and then deploy the trained model to the runtime. This will allow you to run inference tasks without the need for repeated training. #### Third-party integrations For all other use-cases, you can use third-party AI services with Appwrite Functions. Perplexity, Replicate and OpenAI offer powerful APIs that can be used to train and run models on their infrastructure. We've built a range of example functions and technical guides in our [AI documention](https://appwrite.io/docs/products/ai). --- ## Introducing support for server-side rendering https://appwrite.io/blog/post/introducing-support-for-server-side-rendering We're excited to introduce support for server-side rendering (SSR) authentication patterns. This change enhances the developer experience when building with popular frameworks that provide SSR as an option, such as Next.js, SvelteKit, Nuxt, Astro, Remix, and more. Until now, Appwrite’s authentication system has been optimized for client-side rendering, which worked well with single page applications (SPAs), but had limitations on how SSR could be implemented. While it was possible to implement SSR authentication, it was hacky and undocumented. In the latest release, we are officially introducing support for SSR with new methods and workflows to the server SDK's. ### Understanding past limitations To understand the new changes and workflows, let's first take a step back and address the challenges we aimed to solve in this new release. When building applications with SSR, we need a way to generate and store a session secret server-side to protect API routes and pages and a way to make authenticated requests. The problem we faced was that there was no way to access a session secret when using authentication methods. When using methods such as `createEmailPasswordSession`, Appwrite’s web SDK automatically stores the session in the browser's cookies and does not make it available to you. For client-side rendering, this is a non-issue since we don't need to access the session manually. However, when it comes to SSR we need a way to access a session and set it in the server's cookies for subsequent requests. ### Getting started with SSR As you get started with SSR it’s important to note that all methods we will be working with are based on Appwrite’s server SDKs. In the examples below, we will use the [Node JS SDK.](/docs/sdks#server) It’s also recommended that as you follow along with this article, you do not install any client SDK’s as that may lead to confusion. All examples use `node-appwrite`. ```bash npm install node-appwrite ``` #### Create sessions server-side To solve this issue, all existing server SDK methods that create a session now return a `secret` attribute. The following methods are: - `account.createEmailPasswordSession({email, password})` - `account.createAnonymousSession()` - `account.createSession({userId, secret})` You can use these methods now to create a session, and to set a session cookie on your domain. ```jsx const session = account.createEmailPasswordSession({ email, password }) console.log(session.secret) // Output: 'eyJpZCI...sdfahfkjjy' ``` #### Using session secrets With a session cookie set, we can now authenticate users and protect routes. When using the Appwrite SDK, you can use the new `setSession` method to authenticate a user for any request. ```jsx client.setSession(session.secret) const currentUser = await account.get() ``` {% info title="Appwrite client security" %} It’s important to note that the client instance should be re-created for every request. Failure to do so on your server SDK could result in leaked/shared session cookies between request {% /info %} #### Admin and Session Clients With the new authentication patterns for SSR, you’ll need to create two different types of instances of an Appwrite client when initializing the SDK. An admin client for performing admin request, and a session client for performing authenticated request on behalf of an end user. ##### Admin Client The admin client will need to be initialized with an API key in order to bypass rate limits when performing unauthenticated request or when we need to perform actions that bypass permissions. Without an API key our server will be rate limited when trying to make request to certain endpoints. ```jsx import { Client } from "node-appwrite" const adminClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') .setKey('') ``` ##### Session Client A session client will allow us to make requests as an authenticated end-user with the `setSession` helper method. ```jsx const sessionClient = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') const session = req.cookies.session if (session) { sessionClient.setSession(session) } const user = await account.get() ``` ### Seeing it in action Using Next.js, let’s see how we can create an application with two endpoints: - `/api/signin` - creates a session and sets a session cookie - `/api/user` - retrieves session from cookies and authenticates the request We'll use an admin client to create sessions and a session client to perform authenticated requests. #### Creating admin and session client First, we’ll need to configure our SDK and create two methods to initiate our clients in `/src/appwrite.js`. We'll use environment variables to store our Appwrite endpoint, project ID, and API key. ```jsx import { Client, Account } from "node-appwrite" import { parseCookie } from "next/dist/compiled/@edge-runtime/cookies"; export function createAdminClient() => { const client = new Client() .setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT) .setProject(process.env.PUBLIC_APPWRITE_PROJECT) .setKey(process.env.APPWRITE_API_KEY) return { get account() { return new Account(client) } } } export function createSessionClient(headers) { const client = new Client() .setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT) .setProject(process.env.PUBLIC_APPWRITE_PROJECT) const cookies = parseCookie(headers.get('cookie') ?? '') const session = cookies.get('my-session-cookie') ?? '' if (session) { client.setSession(session.secret) } return { get account() { return new Account(client) } } } ``` ##### **Creating a session** Now, in the `/signin` route, we can use the admin client to generate a session and set a cookie. ```jsx import { createAdminClient } from "@/lib/appwrite" import { cookies } from 'next/headers' export async function POST(request){ const { account } = await createAdminClient() const { email, password } = await request.json() const session = await account.createEmailPasswordSession({ email, password }) cookies().set('session', session.secret, { httpOnly: true, secure: true, sameSite: 'strict', expires: new Date(session.expire), path: '/', }) return Response.redirect('/api/user') } ``` ##### Authenticating requests With a session set, we can now use the session client to authenticate a user and return the user object. This session client can now be used on all requests that require an authenticated user. ```jsx import { createSessionClient } from "@/lib/appwrite" import { headers } from 'next/headers' export async function GET(request){ const { account } = await createSessionClient(headers) try { const user = await account.get() return Response.json(user) } catch (error) { return Response.json(error) } } ``` ### Resources Visit our documentation to learn more about SSR, join us on Discord to be part of the discussion, visit our blog and YouTube channel to learn more, or visit our GitHub repository to see our source code. - [Docs](/docs/products/auth/server-side-rendering) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub repository](https://github.com/appwrite/appwrite) SSR will be available as part of the Appwrite 1.5 release on [GitHub](https://github.com/appwrite/appwrite) and [Cloud](https://cloud.appwrite.io/register) in March 2024. [Go back to Init](/init) --- ## Open-source contributors make the community great https://appwrite.io/blog/post/its-the-contributors-in-open-source-who-make-the-community-great Open source (OSS) seems to be the buzzword in tech these days. A lot of companies claim to be it, and even more, companies seem to be the open source version of their corporate counterpart. Maybe it's true, maybe it isn't, either way, open source is here to stay. But only because of the developers who maintain it. In this post, we want to celebrate the OSS community who has built Appwrite, with a special shout-out to the contributors in our latest release and the best open source event there is, Hacktoberfest. #### Appwrite loves open source For those of you who have been following Appwrite, you might have noticed how much we value open source and the community that keeps it going. Appwrite has been a part of that community since 2019, but the team has been contributing to open source way before Appwrite became an entity. Most of our team members were first community members before they joined. To us, this embodies an important mindset that instates open source into the core of Appwrite. It is the foundation we build on and will always be at the heart of everything we do. So if one thing in life is certain, it is that Appwrite is open source. #### To be open source But what does this actually mean to be open source as a company? It means that our code is open for other developers to use, copy, and fork, but most importantly, to contribute to. It's about being transparent and about building in the open, it is about giving back to those who helped us get here. And with over 700+ contributors contributing to Appwrite, it's fair enough to say that the Appwrite you see today was built by you, the open source community. ![open source community](/images/blog/global-community-2.png) #### 1.4 contributors The latest version of Appwrite was built with the help of over 40 developers, truly showing off what building together can achieve and how the community comes together. The collective knowledge and skills of the open source community make it possible to do a lot more with less. With this in mind, we want to shed light on the contributors of the Appwrite 1.4 release and thank every single dev in the community who helped us launch. It was the biggest release to date, and that also led to having the most contributions ever. We are grateful for each contribution, from coding to translations to documentation, with each pull request Appwrite improved. We want to celebrate each and every one of you. Therefore our deepest gratitude goes out to: - [@keul](https://github.com/keul) who [contributed to our Readme](https://github.com/appwrite/appwrite/pull/4537) - [@sarthakroy2002](https://github.com/sarthakroy2002) who updated [codeql-action to v3](https://github.com/appwrite/appwrite/pull/4534) - [@vrabe](https://github.com/vrabe) who [updated traditional Chinese translation](https://github.com/appwrite/appwrite/pull/4285) - [@kalpeshT101](https://github.com/kalpeshT101) who [contributed to our Readme](https://github.com/appwrite/appwrite/pull/4298) - [@Sushrut1101](https://github.com/Sushrut1101) who [updated actions/checkout to v3](https://github.com/appwrite/appwrite/pull/4332) - [@karniv00l](https://github.com/karniv00l) who helped us [fix a bug that appeared when user not found exception happened](https://github.com/appwrite/appwrite/pull/4506) - [@vimode](https://github.com/vimode) who helped us [update Windows powershell commands](https://github.com/appwrite/appwrite/pull/4533) - [@megatank58](https://github.com/megatank58) who helped us [fix Appwrite upgradation bug](https://github.com/appwrite/appwrite/pull/4341) - [@pomarec](https://github.com/pomarec) who [fixed French translation](https://github.com/appwrite/appwrite/pull/4783) - [@aayushbisen](https://github.com/aayushbisen) who [added a restart policy in the `appwrite-worker-messaging` service](https://github.com/appwrite/appwrite/pull/4994) - [@motasimmakki](https://github.com/motasimmakki) who [contributed to docs](https://github.com/appwrite/appwrite/pull/4556) - [@BoynChan](https://github.com/BoynChan) who [updated Chinese documentation](https://github.com/appwrite/appwrite/pull/5107) - [@ks129](https://github.com/ks129) who helped us [fix a bug that appeared with Function status code 500](https://github.com/appwrite/appwrite/pull/4610) - [@munyoudoum](https://github.com/munyoudoum) who helped us [fix the `expire` format in authentication](https://github.com/appwrite/appwrite/pull/4985) - [@singhbhaskar](https://github.com/singhbhaskar) who helped us [fix the `session create` bug in OAuth sessions](https://github.com/appwrite/appwrite/pull/5083) - [@rinkuhasija](https://github.com/rinkuhasija) who [contributed to a code sample in documentation](https://github.com/appwrite/appwrite/pull/5285) - [@yatharth1706](https://github.com/yatharth1706) who [helped us fix `email already exists` error that occurred when the same phone number was used](https://github.com/appwrite/appwrite/pull/5163) - [@Atsurak](https://github.com/Atsurak) who [contributed to documentation](https://github.com/appwrite/appwrite/pull/5360) - [@lucasctd](https://github.com/lucasctd) who [helped us set correct values to LogOwl adapter](https://github.com/appwrite/appwrite/pull/5166) - [@faisalill](https://github.com/faisalill) who helped us [fix a bug in databases](https://github.com/appwrite/appwrite/pull/5718) - [@jaivix](https://github.com/jaivix) who helped us [fixed a bug that occurred while creating a new team with existing `teamid`](https://github.com/appwrite/appwrite/pull/5808) - [@vaibhavagarwal220](https://github.com/vaibhavagarwal220) who helped us [add an error message for invoking verification on previously anonymous users](https://github.com/appwrite/appwrite/pull/5745) - [@Pranav2612000](https://github.com/Pranav2612000) who helped us [fix an email verification bug](https://github.com/appwrite/appwrite/pull/5211) - [@mendelgordon](https://github.com/mendelgordon) who [contributed to our documentation](https://github.com/appwrite/appwrite/pull/5755) - [@Miguelii](https://github.com/Miguelii) who [contributed to our Portuguese email template](https://github.com/appwrite/appwrite/pull/5694) - [@safwanyp](https://github.com/safwanyp) who [fixed a bug that solved team membership appearing after the team is deleted](https://github.com/appwrite/appwrite/pull/5928) - [@Suven-p](https://github.com/Suven-p) who [fixed a bug in `x-appwrite-header`](https://github.com/appwrite/appwrite/pull/5550) - [@pranjalg13](https://github.com/pranjalg13) who [contributed to our documentation](https://github.com/appwrite/appwrite/pull/5327) - [@fliitor](https://github.com/fliitor) who [fixed message inversion error](https://github.com/appwrite/console/pull/429) - [@Suven-p](https://github.com/Suven-p) who helped us [fix deployment issues in CLI](https://github.com/appwrite/console/pull/431) - [@rohan220217](https://github.com/rohan220217) who helped us [fix a bug in the session delete modal](https://github.com/appwrite/console/pull/375) - [@byawitz](https://github.com/byawitz) who helped us [fix deployment issues](https://github.com/appwrite/console/pull/471) - [@erezhod](https://github.com/erezhod) who helped us [fix Apple code snippet bugs in our documentation](https://github.com/appwrite/console/pull/468) - [@Chaitanya-Upadhye](https://github.com/Chaitanya-Upadhye) who helped us [fix a bug that appeared in indexes](https://github.com/appwrite/console/pull/486) - [@safwanyp](https://github.com/safwanyp) who helped us [fix a bug that prevented the document from updating after deleting the array element](https://github.com/appwrite/console/pull/495) - [@KaranJagtiani](https://github.com/KaranJagtiani) who helped us [fiix CDN code sample](https://github.com/appwrite/console/pull/504) Every single contributor and every single contribution made the product better for the next developer. That is what makes it all the more exciting, knowing that your PR actually has value for the next person. With that in mind, shouldn't we all contribute to OSS projects? Maybe, but where to begin? #### Hacktoberfest to kick off your open source journey As mentioned previously, many of our team members were first contributors before they joined. Our very own [Christy Jacob](https://github.com/christyjacob4) once started as a contributor during [Hacktoberfest](https://appwrite.io/blog/post/hacktoberfest-2023), the largest celebration of open source, and is now a Lead Engineer building Appwrite Cloud. It all starts with that first step and pull request. ![hacktoberfest partecipants](/images/blog/badge.png) So there is more to open source contributing than just helping build a project. It can help you progress in your future career by learning, building your portfolio, networking, and even finding job opportunities. All the more reasons to jump on the wagon. With that said, we also understand that for some, contributing might seem daunting, but it really isn't if you get the right help and guidance. This is where [Hacktoberfest](https://appwrite.io/blog/post/hacktoberfest-2023) comes in. It is an amazing event that allows you to contribute to many different projects in many different ways together with others, so you don't have to start your journey alone. It started on the 1st of October 2023, and you can join the entire month. This is your chance to kickstart your open source journey. We want to invite developers to come join us during Hacktoberfest. Together, we continue to build Appwrite through the power of open source. Allowing every developer to have the capabilities of hundreds of developers through one platform. Come join us and build like a team of hundreds. ![Build like a team of hundreds](/images/blog/slogan.png) --- ## Leveraging backend as a service tools to scale faster https://appwrite.io/blog/post/leveraging-baas-tools-to-scale-faster There are many tools that developers use every day to abstract away complexity, but many are seriously underutilizing the benefits of "Backend As A Service" tools for developing the entire backend of an app, or at least the key components. The idea of trusting such a vital part of the development process to a 3rd party tool can be scary; we get it. It's natural to want to have control over every bit of code and build things from scratch, but at what cost? Many of the features we require in most apps are necessary but don't represent the core product. This includes authentication, security, file storage and database management. Do you really need to reinvent the wheel and build this from scratch every time? We believe in most cases there is an easier way, which is where the idea of "Backend As A Service" can help. ### What is a Backend as a Service (BaaS) A backend as a service is a third party service that lets you develop the backend part of your application without building things from scratch. It's about NOT reinventing the wheel for every app you launch. Each BaaS platform has its own set of tools and features, so let's use [Appwrite](https://appwrite.io/) as an example of offerings you may see. A service like Appwrite provides you with a console from which you can manage your entire backend. From your console's dashboard you'll have the ability to create and manage services such as: - A Database - Authentication & User Management - File Storage - Real-Time Functionality - Webhooks - Cloud Functions The beauty of a good BaaS service is that setting up your backend takes just a few minutes. You can design a database, add some data, and connect to it from a frontend via a REST API, an SDK, or GraphQL. The features that normally take hours or days to build are ready to go in minutes. ### Using BaaS to your benefit Being able to successfully use these tools will in no doubt speed up your development process and save you money. The question is, how will you leverage them to your benefit? There are two schools of thought we typically see: First, there are those that use a BaaS platform to build and scale their entire backend. Many have done so successfully and built large-scale apps. The second are those that use BaaS tools incrementally. They pick and choose the tools they need and adopt accordingly. For example, an organization may only need to utilize a BaaS for User Authentication and file storage. The beauty of BaaS tools is that you can choose how deep you want to integrate them into your platform. In the process of writing this article, we did some research to find out which companies were leveraging BaaS platforms and how they were using them. Amongst our findings were organizations such as Twitch.tv, Instacart, Square, Trustpilot and Zendesk. In one case study Twitch.tv, the streaming service, utilized a BaaS for user authentication and a real-time database for transmitting messages in real time. Many developers at the highest level are adopting the idea of using these tools to optimize their effectiveness and efficiency. We've seen this in cases from developers trying to get a product to market in the shortest possible time or integrating features with efficiency into large-scale applications. In one interview, the CEO of Appwrite, [Eldad Fux](https://twitter.com/eldadfux), said, "Appwrite was born out of necessity". After working on multiple startups and years of experience in the industry, Eldad said, "I didn't want to suffer from doing the same thing over and over again." ### Choosing a BaaS provider When searching the market, there are many solutions that thrive in one area or another. However, there is a common theme amongst the developer community with concerns they have with some of most widely adopted platforms. #### Vendor lock-in One of the most common concerns with the largest BaaS provider in the market, Firebase, is vendor lock-in. When building with Firebase, you get locked into Google's ecosystem, making migrating your code away a big headache. #### Scalability & pricing Many providers, especially Firebase, also have had issues with Pricing. Oftentimes, an attractive free tier is offered to developers and startups. However, as your application grows, you may encounter pricing issues. To be specific, Firebase's pricing model can become expensive once you surpass the free tier's limitations, and it may not be cost-effective for larger-scale applications. #### Privacy and data ownership concerns One of the main concerns with using Firebase is that the data stored in Firebase is not owned by the users, but by Firebase itself. When you use Firebase, you entrust Google with your application's data. Google's policies and terms of service may not align with your specific requirements, and you might have limited control over how your data is managed and stored. #### The open-source solution These concerns are exactly why Appwrite was developed as the truly 100% open source Backend As A Service. A platform that is committed to giving you control over your tools and data while developing with the open source community. Appwrite has addressed many of the problems developers in the community face, which is why it is being adopted at an incredible pace with amazing support! Since its launch in 2019, Appwrite has raised $37 million in seed rounds, was named Product Hunt's #1 product of the day after launching version 1.0, has gained over 33k GitHub stars with over 700 open source contributors, and has grown one of the most active developer communities on discord with over 17,000 members! Appwrite is committed to providing the best developer experience while building the open source community. Want to give Appwrite a try? You can get started in just a few minutes with the [Appwrite console](https://cloud.appwrite.io/). --- ## Make the best use of Appwrite’s MCP server https://appwrite.io/blog/post/make-best-use-appwrite-mcp MCP servers are the next big thing in the AI space. Everyone is talking about them. An MCP server allows your AI agents to interact with external services. The use cases are legit, so we launched our own Appwrite MCP server that allows your AI agents to interact with your Appwrite projects. This MCP server unlocks some handy use cases, which we will look at in this article. Our documentation has a [guide for installing and setting up the Appwrite MCP server](/docs/tooling/mcp) if you haven't already. ### Generating documentation for your project Since your AI agents can now access your Appwrite projects, you can generate detailed documentation about any resource present in your project. This is particularly useful when generating documentation for your database schema. This way, you don't need to give end developers access to your project and keep them in the loop about the database schema and purpose for each table and column. Once connected to your project, you can run a prompt similar to the following to generate documentation for your database schema. ```text Create a new markdown file called `DATABASE.md` and write a detailed documentation for my database named ``, make sure you cover all the tables and their columns. ``` By running this prompt, the AI agent should start accessing your database (if it has access) and analyze all the tables and their columns. A final summary should appear in your `DATABASE.md` file shortly. ![Database documentation generated](/images/blog/make-best-use-appwrite-mcp/doc-generated.png) ### Creating tables dynamically Instead of opening the Appwrite Console or writing custom scripts, you can ask your AI agents to create/modify tables in your database. This is especially useful when you're vibe coding to build a prototype and you need the model to create tables according to its judgment without you needing to go to Appwrite Console to do it manually. Once connected to your project, you can run a prompt similar to the following to create tables using your AI agent. ```text Create a new table in the database `db` called `products`. Within the table, create columns for name (string), price (float), and stock (integer). All fields must be required. ``` By running this prompt, the AI agent should create a new table and then add the specified columns. You can mix this up with a new feature asking your agent to “create required database table schema for the feature”. ![Table created by the AI agent](/images/blog/make-best-use-appwrite-mcp/table-created.png) ### Selective CSV export If you ran the above prompt and now have a products table with all the relevant columns, you can make your AI agents perform queries on your database and take actions with it. For example, you can run a query and export the final data to a CSV file. ```text In the `products` table in the database `db`, export all the products that cost more than $100 into a new `costly.csv` file. ``` This will run the necessary database queries and create a CSV file with all the extracted records. ![CSV file created by the AI agent](/images/blog/make-best-use-appwrite-mcp/csv-created.png) Similarly, you can run this the other way around and import a CSV file with selective requirements, i.e., only importing if the product price is less than $100, for example. ### Replicating database schema Sometimes you need a separate database with the same structure as your main database to test new updates. It could be tedious to manually replicate the database schema each time. Instead, you can ask your AI agent to do the job. ```text Replicate the database schema for the database `db` (with tables and columns), into a new database called `db2`. Don’t transfer any data; only the schema needs to be replicated. ``` This should make the AI agent scan through all your database tables, record the schemas, and replicate them into a new database for you. ![Database schema replicated](/images/blog/make-best-use-appwrite-mcp/db-replicated.png) ### Wrapping up Appwrite's MCP server can unlock many different possibilities when powered by an AI agent. These were just a few of the many more use cases you could imagine. If you have any questions, please join our [Discord server](https://appwrite.io/discord) to connect with the team. - [Appwrite MCP documentation](/docs/tooling/mcp) - [What exactly is MCP, and why is it trending?](/blog/post/what-is-mcp) --- ## Make open source healthier by being a better contributor https://appwrite.io/blog/post/make-open-source-healthier For the last few years, every single time Hacktoberfest comes, one challenge that has constantly been discussed is how to make open source healthier for everyone. This isn’t to say that open source is an unhealthy space; rather, it has a much larger positive impact than most people can imagine. However, Hacktoberfest sees the entry of a flurry of new contributors trying their hands at getting in their first Pull Requests. This period is a particularly impressionable phase for new contributors to open source, and gaining a better understanding of how contributors should participate in a healthy manner only makes for a better, more welcoming community. Therefore, in this blog, we will explore what community health means in the context of open-source communities and the role contributors play in making it better. #### Understanding what community health in open source means In the context of open-source communities, **community health** refers to the overall well-being, sustainability, and effectiveness of the community working on a project. It involves various factors that contribute to a productive and supportive environment, encouraging collaboration, inclusivity, and growth. A healthy open-source community typically has the following characteristics: - Active participation and continuous engagement - An inclusive and open environment for everyone, regardless of their backgrounds, skills, etc. - Effective communication channels, such as mailing lists, forums, or chat rooms - Transparent decision-making about the project's direction, priorities, and governance - Strong leadership to guide the community, set the vision, and help resolve conflicts - A well-defined code of conduct to establish expectations for behavior - Well-maintained documentation for the project and its processes - Recognition and appreciation of contributors' efforts - Collaboration and teamwork among members - Opportunities to learn and grow as a professional While some of these characteristics, like strong leadership, transparent decision-making, and creating a code of conduct, are responsibilities primarily held by maintainers, most of these are shared between maintainers and contributors. #### Building a healthy set of contribution practices Having discussed what community health means, it is necessary to then discuss what contributors can do to improve the community experience for everyone involved in the contribution process. Here are some practices I recommend from my experience as a contributor and maintainer: - **Wait till you’re assigned an issue** The majority of open-source projects have processes of selecting what issue is necessary to work on and who works on what issue. This is why it is important to ensure that your raised issue is either selected or you’re assigned to work on one. Not doing so before you start contributing may cause you to repeat someone else’s work. - **Make objectively valuable contributions** When making a contribution, it is important to ensure that your contribution is objectively valuable and not subjective. Only then does it create value for everyone in the community and not a specific group only. For example, fixing spelling mistakes in documentation is objectively valuable, but changing a paragraph from active to passive voice is not. - **Focus on quality rather than quantity** Rather than making too many contributions quickly and carelessly, it is better to focus on a smaller number and ensure they’re in the best shape possible. This way, your work leads to lesser maintenance debt and more positive value addition. - **Review previously merged contributions** Looking through past accepted contributions gives you a better idea of the project's development practices and communication processes. Better clarity on these makes it much easier for you to integrate within the operations of the projects. - **Don’t call dibs on issues** There’s plenty of work to go around for every person in the community, whether around code, documentation, maintenance, etc. Let’s make sure we don’t end up adding more barriers in the process by spamming requests to get assigned on every issue we find. - **Wait for a couple of days before you ask for an issue to be reassigned** When contributing to open source, it is necessary to remember that most people participate here outside of their day-to-day lives. With everything else in our lives, with work, academics, families, etc., sometimes people can’t prioritize open source immediately. A little patience here goes a long way for everyone. - **Communicate your progress with the maintainers** After you are assigned an issue, it’s good to update the project’s maintainers at regular intervals. This way, everyone in the process can track progress and challenges, thus keeping transparency for the community. - **Be patient with the maintainers** Most open-source work is unpaid and voluntary. Maintainers do it out of their love for the technology and community. And as lovely as maintaining their project can be, as the communities around their projects grow, it can very easily become overwhelming. Therefore, if your contribution has been under review for a while, patience goes a long way. - **Help with Pull Request reviews** Most open-source projects tend to have a much higher ratio of contributors to maintainers. Therefore, once you know what maintainers look for in changes, you can leave appropriate feedback for the project to help make life easier for everyone. - **Give and accept feedback constructively and humbly** The beauty of open source is that you could end up collaborating with people from drastically different places, walks of life, or knowledge levels. You never know whether the person you’re working with is a college sophomore or a veteran software engineer. Giving constructive feedback means that you enable others to learn and grow from your experiences. Accepting it with humility enables you to achieve the same. #### Going forward Ensuring that open source remains healthy for everyone is a proactive effort across maintainers and contributors. As long as we all take the necessary steps, we can keep this community welcoming for new and experienced individuals alike in the coming times. --- ## How to use Appwrite Labels and Team to manage user permissions https://appwrite.io/blog/post/manage-user-permissions-with-labels-and-teams Teams and Labels allow us to categorize and group users together, allowing us to set permissions to resources at the Team and label level instead of at the individual user level. Grouping users together makes managing permissions to documents, files and functions much more efficient this way. ![Labels vs Teams](/images/blog/manage-user-permissions-with-labels-and-teams/labels3.png) To think of this in real-world terms, imagine for a second we were building the next social media application. Teams can be used to create admins and moderators, and these admins and moderators would have permission to delete and flag posts that don’t meet community guidelines. The owner at the document level would have the ability to update and delete this post, but anyone who is on the moderator Team would also have the ability to delete the post, something that is normally restricted to only the owner of the post. We can also change which users have these permissions at any point by adding and removing users and updating the group-level permissions. Another example we can take a look at would be a streaming service like Amazon Video. How do we give users access to a movie or show they paid for? This is where Teams and Labels make our lives easier. By simply adding Labels to users we could decide which users have access to specific resources like movies or shows in our application. Without Teams and Labels, we could always assign permissions to users individually, but this can become problematic very quickly when we start trying to modify these permissions and update who has access to what. So let’s dive into the next section and see how Teams and Labels work and the differences between the two. #### Teams Vs Labels Teams and labels accomplish much of the same thing, however there are some technical and fundamental differences between the two. While deciding which one to use will be based on your preferences and specific needs, I’ll point out the key differences and try to guide you in the right direction by providing an example of when one may be preferred over the other. ##### Teams Teams are designed to group users together, allowing for shared access to resources within an application. For example, in an app like Discord or Slack, we can create Teams and invite users to be a part of the Team (Ex: Server or chat room). ![Labels vs Teams](/images/blog/manage-user-permissions-with-labels-and-teams/labels6.png) One of the key differences between Teams and Labels is that Teams have the ability to set roles within a Team and assign permissions to those roles. This means all users in a Team will inherit permissions from the Team such as read access to all messages in the chat room, but will also inherit permissions from their role as well if they have been assigned one, such as moderator or admin. This allows for granular control over what each Team member can do based on their role within the Team. ##### Labels Labels are essentially custom tags that you can assign to a user. They act as custom-defined markers or classifications that we can place on specific users. ![Labels vs Teams](/images/blog/manage-user-permissions-with-labels-and-teams/labels1.png) Just like Teams, we can grant permissions to specific Labels. Labels are attached to individual user accounts and are used to categorize users on a one-to-one basis. Labels can be used in a similar manner to Teams but are a more lightweight and flexible way to manage users and permissions. This would make Labels a great option for managing which users can view restricted content behind a paywall like a course on Udemy or an e-book on a digital library platform. In this example you would simply attach a label that is unique to a user after they pay to access a product, and from that point on, the label would grant the user permission. If this access was subscription-based and the user stopped paying, we could simply remove the label from the user, and their access would be revoked since they no longer carry the label that gives them access. ##### Summary - Labels are great for tagging users and assigning permissions based on those tags, while Teams are for grouping users together and allowing role-based management within a Team. - Labels classify users based on attributes or behaviors, whereas Teams facilitate collaboration and shared access among a group. - Labels offer flexibility in user segmentation, while Teams provide structure for collaborative environments. #### How permissions work with Teams and labels ![Labels vs Teams](/images/blog/manage-user-permissions-with-labels-and-teams/labels4.png) Here’s the process of granting users permission to resources through Teams and labels. Be sure to pay attention to step 3 in the process. There’s a slight difference in how users connect to Teams Vs. labels. 1. Create a Team or Label 2. Assign permissions to that Team or Label 3. **Team**: Add users to the Team / **Label**: Add a Label to the user 4. Users now inherit permissions from the Team they are on or the Labels they have been given. Did you catch the difference between Labels and Teams in step #3? With Teams, we are adding users to the Team, and with Labels, we do the opposite by assigning Labels to a user. #### Managing Teams & Labels from the Console When getting started with Teams and Labels, it could be helpful to set things up from the console manually so you can better visualize how things work. #### Teams Let's start with creating a new Team, adding members, and assigning roles to those members of the Team. 1 - To create a Team, you can go to the “Auth” tab from your console and select “Teams”. From here, you can click on “Create Team” and complete the process of creating by giving your new Team a name 2 - Once your Team is created, you can add a Member to the Team by selecting the “Members” tab and clicking “Create Membership”. Here you will enter the user's email address (name can be left blank) and assign a role to the member. Roles are optional, so you can leave this part blank. That’s it for creating a Team and adding members from the appwrite console. Now you can assign document, storage, and function permissions to your Teams. As an example, for collection level permissions, you can go to the “settings'' tab in a collection, and in the “permissions” section, choose “Select Teams'' to give permissions to an entire Team or “Custom permissions” if you want to assign permission to only users with a particular role within the Team. #### Labels Labels can also be assigned from the auth tab. To assign a label select a user and add a label in the “Labels” section. Assigning permissions to labels is the same as it is for Teams, only you would choose the “Label” section in the drop down menu. ##### SDK Usage From a technical perspective, it’s important to note a key difference in how we use Teams and Labels when it comes to the SDK. Labels can only be created & modified from the console or Server SDK, while Teams can be created and modified with both the client and server SDK’s. ##### Teams Server SDK - Yes Client SDK - Yes ```javascript import { Client, Teams } from "appwrite"; const client = //... const Teams = new Teams(client); const promise = Teams.create({ teamId: '', name: '' }); ``` #### Labels Server SDK - Yes Client SDK - No ```javascript const sdk = require('node-appwrite'); const client = //... const users = new sdk.Users(client); const promise = users.updateLabels({ userId: '', labels: ['subscriber'] }); ``` ##### Resources Learn more about Labels and Teams in our documentation: - [Labels](https://appwrite.io/docs/products/auth/labels) - [Teams](https://appwrite.io/docs/products/auth/teams) Watch the video tutorial on 'how to manage user permissions with Labels and Teams' on [YouTube](https://www.youtube.com/watch?v=xJTj5Ye8-W0&feature=youtu.be). --- ## Mastering prompt engineering tools for AI apps https://appwrite.io/blog/post/master-prompt-engineering-tools Prompt engineering is changing how we [build with AI](/docs/tooling/mcp). It's no longer just about the model, it’s also about the inputs given to the AI models. As AI-driven development takes off, prompt engineering becomes the bridge between human intent and machine execution. It’s how you get AI to do what you want. If you’re building with AI, this isn’t optional. Understanding prompts means understanding how to steer AI. It’s a must-have skill for the next generation of developers. This guide will explore everything you need to know about prompt engineering. From tools to skills, we’ll cover it all. ### What is prompt engineering? Prompt engineering is a specialized area within AI development focused on designing inputs for AI models. These prompts guide AI to generate specific outputs, making AI systems more predictable and effective. This technique is crucial for models that utilize natural language processing. It helps the systems understand the context better and respond accurately. The process involves crafting well-structured prompts to instruct AI on what is expected. This might include parameters like style, tone, or data types needed. ### Key aspects of prompt engineering: - **Contextual understanding**: Helps AI models grasp the specifics of the input. - **Output guidance**: Directs the AI to generate relevant responses. - **Task optimization**: Fine-tunes performance for better results. The importance of prompt engineering in AI can't be overstated. It's an emerging field that optimizes AI functioning and minimizes output errors. ### Why prompt engineering matters in AI development? Prompt engineering plays an important role in creating efficient [AI applications](/blog/post/ai-agent-startup-tips). It makes AI models more reliable and accurate by offering precise instructions. This precision is especially crucial for virtual assistants and automated customer service applications. Without well-engineered prompts, AI responses can be inconsistent or irrelevant. Effective prompts ensure that AI systems understand user queries correctly, leading to better user satisfaction and making AI systems more user-friendly and intuitive. By optimizing prompts, developers can fine-tune [AI models for specific tasks or industries](/blog/post/saas-to-vertical-ai). This specialization can greatly enhance AI system performance and adaptability, making it essential for sectors such as healthcare, finance, and customer service. ### Why prompt engineering is essential: - **Improves accuracy**: Reduces errors in AI responses. - **Enhances user experience**: Makes AI interactions more meaningful. - **Task specialization**: Adapts AI models to particular needs. The growing demand for intelligent AI systems underscores the need for robust prompt engineering practices. This field is at the heart of modern AI development, enabling machines to perform tasks that once required human insight. ### Key skills for prompt engineering Successful prompt engineering requires a mix of technical and soft skills. Understanding natural language processing (NLP) is vital. NLP enables developers to create prompts that are easily understood by AI models. Another crucial skill is creativity. One must think imaginatively to construct prompts that elicit the desired AI responses. This creativity helps in addressing complex queries effectively. Technical proficiency in AI platforms is also important. Familiarity with leading AI technologies and systems empowers engineers to implement and test prompts seamlessly. Problem-solving skills are essential as well. Engineers often face unexpected challenges, requiring innovative solutions to refine prompts and improve AI responses. Being adaptable is key. ### Essential skills for prompt engineers: - **NLP proficiency**: Master the nuances of AI language comprehension. - **Creative thinking**: Craft compelling and effective prompts. - **Technical acumen**: Leverage AI platforms and tools adeptly. - **Problem solving**: Tackle unforeseen issues with tact and creativity. Soft skills also play a significant role, as collaboration and communication enhance team output. By blending these skills, prompt engineers drive AI advancements. ### How to get started with prompt engineering Getting started with prompt engineering is an exciting journey. Start with the basics of AI and Machine learning. Understanding foundational concepts will ease your path toward mastering prompt engineering. Enroll in online courses to learn natural language processing (NLP). These courses equip you with the skills to craft effective prompts. Exploring existing prompt engineering tools is a practical way to gain insights. Familiarize yourself with tools such as LangChain and PromptAppGPT. Get hands-on, play with these tools. Participate in AI communities and forums. Engaging with like-minded individuals provides support and new perspectives. It's an excellent way to stay updated with industry trends as well. Start small by practicing prompt engineering on personal projects. Experiment and refine your approach as you learn. This experimentation is key to honing your skills and gaining confidence. ### Steps to Get Started: - **Learn AI basics**: Build foundational knowledge of AI and machine learning. - **Study NLP**: Enroll in online courses to understand language processing. - **Explore tools**: Familiarize yourself with popular prompt engineering tools. - **Engage with communities**: Join AI forums for insights and networking. - **Practice**: Gain experience through personal project experimentation. ### Overview of prompt engineering tools Prompt engineering tools are essential in developing AI applications. These tools help in crafting the perfect prompts to improve AI interaction. They provide a framework for designing and executing complex tasks. Various tools cater to different project needs. Some focus on language processing, while others offer a broader range of functionalities. Selecting the right tool depends on your specific application and desired outcomes. Here are some popular tools to get you started: - LangChain - PromptAppGPT - OpenPrompt - PromptLayer - Promptmetheus Each tool has unique features and benefits for AI developers. If you’re looking to integrate AI tools with your backend, [Appwrite](https://appwrite.io/) provides simple APIs and docs to get started. ### Top prompt engineering tools in 2025 As AI continues to grow, prompt engineering tools are evolving rapidly. In 2025, new and improved tools will enhance application development, offering better accuracy and efficiency for complex AI tasks. ![LangChain](/images/blog/master-prompt-engineering-tools/langchain.png) **LangChain** [LangChain](https://www.langchain.com/) is renowned for its top-tier language processing. It simplifies complex AI tasks and enhances model performance. Developers choose LangChain for its advanced algorithm capabilities. **Key Features**: - Sophisticated NLP functions - AI model performance boosters - User-friendly interface It's excellent for creating applications that require deep language understanding. ![PromptAppGPT](/images/blog/master-prompt-engineering-tools/promptappgpt.png) **PromptAppGPT** [PromptAppGPT](https://promptappgpt.wangzhishi.net/) is valued for its intuitive design. This tool makes prompt engineering accessible for beginners. Its interface simplifies crafting prompts effectively. **Core Benefits**: - Easy-to-use layout - Quick setup and deployment - Ideal for newcomers It's perfect for users looking to get started promptly with minimal complexity. ![PromptLayer](/images/blog/master-prompt-engineering-tools/promptlayer.png) **PromptLayer** [PromptLayer](https://www.promptlayer.com/) excels with its cloud-based design. This tool fosters collaboration, making it ideal for teamwork-focused projects. It allows seamless sharing and integration. **Attributes**: - Cloud support for team projects - Integrated sharing features - Great for collaborative environments Its cloud capabilities streamline collaborative efforts effectively. ![Promptmetheus](/images/blog/master-prompt-engineering-tools/promptmetheus.png) **Promptmetheus** [Promptmetheus](https://promptmetheus.com/) leverages AI to provide insightful prompt crafting. It offers suggestions based on data analysis, optimizing outcomes. Its analytics capabilities set it apart. **Highlighted Features**: - AI-based suggestions - Data-driven insights - Prompt optimization It's favored by developers focusing on data-informed prompt creation. ### Best practices for effective prompt engineering For successful prompt engineering, clarity is crucial. Ensure that prompts are explicit and specific to minimize ambiguity. Clearly defined objectives lead to better AI outputs. Avoid vague instructions, as they can confuse the AI model. Feedback plays a vital role in prompt refinement. Regularly review AI outputs to identify patterns and make necessary adjustments. This iterative process helps in improving accuracy and effectiveness over time. Consider the context and relevance of your prompts. Contextual cues are vital for AI to generate meaningful responses. Tailor prompts to the specific use case or industry for enhanced results. It's essential to match the tone and style with end-user expectations. Here's a quick rundown of best practices for prompt crafting: - Clarity: Be explicit and specific - Feedback: Regular review and adjustment - Context: Tailor to use case, match tone and style Effective management and adaptive strategies contribute greatly to enhancing AI applications. By adhering to these practices, you're more likely to build robust AI solutions. ### Real-world applications and case studies Prompt engineering finds numerous applications across different industries. In healthcare, AI systems help diagnose diseases by analyzing patient records. This involves creating prompts that guide AI to focus on specific symptoms or medical conditions. In finance, prompt engineering aids in fraud detection. AI models analyze transaction patterns and flags inconsistencies. Well-crafted prompts allow these models to identify unusual activities effectively. E-commerce platforms utilize prompts for personalized product recommendations. AI systems scan user preferences and behavior, offering tailored suggestions that improve user experience and increase conversion rates. Here are some key applications of prompt engineering: - Healthcare: Assisting in diagnosis - Finance: Enhancing fraud detection - E-commerce: Personalizing recommendations Successful case studies highlight the transformative power of prompt engineering. Companies have streamlined operations and enhanced customer satisfaction using AI-driven solutions. These stories demonstrate the potential of well-implemented prompts in real-world scenarios. ### Prompt engineering for AI writing tools AI writing tools rely heavily on prompt engineering to generate coherent and relevant content. These tools use prompts to guide AI models in producing text that meets user requirements, whether for articles, emails, or creative writing. A key aspect of using AI writing tools is the design of effective prompts. Users can significantly influence the output by specifying tone, structure, and context. This level of customization ensures that content aligns with specific needs and audience expectations. Prompts enable AI writing tools to adapt to diverse writing tasks: - Blog articles - Marketing copy - Fictional stories The flexibility of AI writing tools lies in prompt design. As a result, users can unlock AI's full potential in content creation, making writing tasks faster and more efficient. ### Career opportunities: Prompt engineering jobs Prompt engineering offers exciting career paths as AI continues to grow. Demand for skilled professionals in this field is rising, and companies are leveraging AI to innovate and improve their processes. Prompt engineering roles are diverse. They often involve developing and refining prompts for AI models, which ensures accurate and efficient AI outputs in different applications. Prompt engineering jobs may include: - AI Prompt Designer - Natural Language Processing (NLP) Engineer - AI Content Specialist These roles require a blend of technical skills and creativity. Strong knowledge of AI and machine learning is essential. Additionally, effective communication and problem-solving skills are valuable. Career prospects in prompt engineering are promising. As AI technology evolves, the need for skilled prompt engineers will continue to grow. Professionals can expect opportunities for advancement in this dynamic and innovative field. ### Challenges and future trends in prompt engineering Prompt engineering presents several challenges. Ensuring prompt accuracy and relevance is a key difficulty. AI models can sometimes misinterpret inputs, leading to errors. Another challenge involves data privacy. Managing sensitive information within AI prompts requires careful consideration, and ensuring compliance with regulations is crucial. Despite these challenges, exciting trends are emerging. Continuous advancements in AI and NLP are shaping prompt engineering's future, and tools are becoming more sophisticated and user-friendly. Future developments in prompt engineering may include: - Improved AI model training - Enhanced personalization capabilities - Increased focus on ethical AI These trends are promising. They offer potential for innovation and increased effectiveness in AI applications. The field is poised for significant growth, with new technologies emerging rapidly. Staying informed and adaptable is important for professionals in this evolving landscape. ### Resources to learn prompt engineering Learning prompt engineering requires access to quality resources. Many online platforms offer courses and tutorials. These can help you acquire the foundational skills needed. Community engagement is also beneficial. Join forums and discussion groups. They provide insights from experienced practitioners and foster collaboration. Here are some valuable resources to explore: - Online courses on Coursera and Udacity - Tutorials and articles on Medium - Interactive forums like Reddit and Stack Overflow Utilizing these resources will broaden your understanding. They'll help you stay updated with industry trends and innovations. Always seek continuous learning to refine your prompt engineering skills. This ensures you remain competitive in the rapidly evolving AI landscape. ### Conclusion Prompt engineering is pivotal in shaping AI's future. It offers innovative ways to develop efficient AI applications. By harnessing its power, creators can revolutionize their products and services. As tools evolve, prompt engineering will only become more integral. Businesses and developers must adapt to stay ahead. Embracing these advancements ensures they leverage AI's full potential. Ultimately, prompt engineering drives AI's success. Its capabilities open new avenues for personalized and intelligent solutions. As technology progresses, its impact will resonate across industries worldwide. If you’re building AI-powered applications, choosing the right backend is just as important as designing the right prompts. [Appwrite](https://appwrite.io/) gives you the flexibility, scalability, and developer-friendly APIs you need to bring your AI ideas to life. --- ## 5 MCP startup ideas to build in 2025 https://appwrite.io/blog/post/mcp-startup-ideas This has to be the best time in history to start a business. With so many advancements in AI tools and agents, it has never been this easy to build and scale your startup. The next breed of billion-dollar companies will be tiny teams backed by AI agents and workflows. And one such recent advancement is Model Context Protocol (MCP), which has gone viral for the past couple of months and is a significant breakthrough in how LLMs(Large Language Models) interact with external tools. In this blog, we'll cover MCP and five startup ideas that you can start building today. ### What is MCP? Forget the fancy words for a second. MCP is just a way for AI assistants to interact with real-world tools like databases, file systems, APIs, and apps. Currently, AI assistants can reason, process information, and answer questions but don't know where anything is and can't actually do anything outside of its box. Now imagine you give it a phone with direct lines to your company's database, your Slack account, your email inbox, and your documents. That's exactly what MCP does. It's the system that allows an AI assistant to: - Fetch real data. - Look up information from live sources. - Trigger actions (like posting messages, running queries, or updating records). Instead of just guessing based on past knowledge, the AI can now pull actual facts from the places that matter. MCP is just the standardized way of making this happen. Instead of every AI needing custom integrations for every tool, MCP provides one universal way to hook everything together. MCP was developed by Anthropic and released as an open standard in November 2024, making it a relatively new but rapidly growing solution. We've covered everything you need to know about MCP in a recent [blog](https://appwrite.io/blog/post/what-is-mcp), but let's dive into 5 MCP or AI agents startup opportunities you can start building today. ### 1. MCP App Store Create an MCP APP Store, where users can browse and deploy various MCP servers. Think of it like an app store, but for MCP servers. Developers could look through different MCP servers, find the ones they need, and deploy them with just a few clicks. The idea is to simplify the process of integrating different services with LLMs, making it much easier for anyone to use the power of MCP without having to deal with the technical complexity. ### 2. Incident Tracer When your app breaks, whether it's a bug or an outage, an MCP agent traces every log, commit, and Slack message to give you a full incident report in seconds. So, instead of manually digging through logs or trying to piece together what went wrong, the MCP agent automatically collects all the relevant information, creating a detailed report that helps you quickly understand the issue. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} ### 3. Task Assistant An MCP-powered agent that shadows founders, reading emails, meetings, tasks, and documents. It provides daily summaries and suggests the next best actions, acting like your personal Chief of Staff. This allows founders to stay focused on what matters while it keeps track of everything and ensures nothing slips through the cracks. ### 4. Dev Onboarder DevOnboarder onboards devs 10x faster by spinning up an agent trained on your internal codebase, docs, and Slack threads. So, instead of spending weeks, the tool can help new devs get familiar with the system and start contributing right away, cutting down on the time spent training and ramping up. ### 5. AI Changelog AIChangelog tracks everything every AI agent does and why, creating a detailed, auditable record. It keeps a running log of every action taken, ensuring full transparency and accountability for agent teams. It helps you trace every step, providing clarity and insight into how your AI agents are performing. ### Conclusion MCP changes everything. Before, AI agents were isolated. Now, they connect, collaborate, and scale. This is a huge opportunity to build AI agents and create connected, intelligent solutions that automate tasks, streamline processes, and solve real-world problems. The Appwrite's Startups Program can help you do that with the tools and support you need to create these AI-driven products. [Apply today](appwrite.io/startups) and start building. ### Further reading - [Appwrite MCP documentation](https://appwrite.io/docs/tooling/mcp?doFollow=true) - [What exactly is MCP, and why is it trending?](https://appwrite.io/blog/post/what-is-mcp) - [Anthropic MCP documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp) --- ## Meet the new Appwrite https://appwrite.io/blog/post/meet-the-new-appwrite At Appwrite, we are constantly collaborating with the Appwrite community to improve Appwrite's products, services, and content. All for the sake of improving your developer experience, as well as staying true to the open-source values. Today, we took another big step in improving this experience by elevating the Appwrite brand. Not only do we have an improved look and feel, but we also focused on delivering a better experience navigating our website and docs. We are excited to share our new brand and to see it match the advancement of our products, services, and content. And how, together, and through the power of open source, we have the capabilities of hundreds of developers. ### Aligning visual identity with growth The very first design of Appwrite was created by our Founder & CEO, Eldad, in 2019 when Appwrite launched as an open-source project. Since then, the Appwrite Console has been through a design upgrade, but as Appwrite grew, so did the team. Making it possible to reach new heights with Appwrite’s overall brand identity. > With the Appwrite team and community growing, our product started to mature, and there was a disconnect between our broader visual identity and our product With the Appwrite team and community growing, our product started to mature, and there was a disconnect between our broader visual identity and our product. Recognizing the need for a fresh and more mature appearance, we worked to align our visual identity with the growing advancement of our product. This rebranding effort represents our commitment to delivering a polished and sophisticated experience to developers, throughout the developer journey. From discovery to scaling in using Appwrite. ### Designed for the community Our rebranding journey began with a fresh perspective on our logo. We wanted to emphasize the importance of the Appwrite community, so we redesigned it to feature a globe and lines of code. This represents our global community members, working and coding together to form the letter 'a,' for Appwrite. ![New Appwrite logo](/images/blog/new-logo.png) > Our rebranding journey began with a fresh perspective on our logo. We wanted to emphasize the importance of the Appwrite community, so we redesigned it to feature a globe and lines of code. These code lines have become a recurring theme in our new 3D visuals, symbolizing progress and unity. We've also expanded our color palette to reflect our diverse and creative community, moving away from just pink. Warmer colors were added, to reflect the human and open side of our vibrant community. ![Appwrite's new visual assets](/images/blog/socials.png) Additionally, we've added a new element: glass. This represents our commitment to transparency and openness, echoing our open-source values. It's integrated into our website and brand visuals, highlighting our dedication to collaboration within our global community. ![Appwrite's glass elements](/images/blog/glass-elements.png) ### Introducing our enhanced website and docs Our upgraded website is the main product of our new brand bringing more pages explaining everything you need to know about Appwrite. We have more web pages to come explaining our products and features in more depth. ![Appwrite's new website](/images/blog/new-website.png) As part of our rebranding effort, we've revamped not only our website but also our documentation. Recognizing that our documentation plays a pivotal role in a developer's workflow, we've invested in enhancing both its design and content. Our new documentation now features specialized tutorials to guide you through project setup and feature implementation step by step. ### Build like a team of 100 With open source at the heart of everything we do, community plays an important role at Appwrite. We believe that if we stay true to our philosophy, together, we will empower developers with the capabilities of hundreds of developers. Giving them the freedom to build, create, and innovate. --- ## Memberships privacy is now available for all Appwrite plans https://appwrite.io/blog/post/memberships-privacy-announcement Protecting user data is fundamental to building secure and reliable applications. We're excited to announce that memberships privacy is now available on all Appwrite plans, enabling you to safeguard your members' personal information effectively. ### Why memberships privacy matters Many applications don't require members' personal details to be visible to other users or team members. In collaborative environments, for instance, exposing information like email addresses or multi-factor authentication status may be unnecessary and potentially risky. This feature ensures you can implement appropriate privacy controls while maintaining full application functionality. ### What you can do with memberships privacy Memberships privacy allows you to designate specific membership details as private, giving you precise control over information sharing within your app. You can now configure the following details as private: - userName: The member's name - userEmail: The member's email address - mfa: Whether the member has enabled multi-factor authentication When set to private, these details remain hidden in team or project workflows unless specifically required. ### How to configure memberships privacy Implementing memberships privacy is straightforward: 1. Open your Appwrite Console 2. Navigate to Auth > Security > Memberships privacy 3. Select which details (userName, userEmail, or mfa) should be private This setup ensures sensitive information remains protected where unnecessary. Have questions or feedback about this feature? Join us on [Discord](https://appwrite.io/discord) - we look forward to hearing how you'll use memberships privacy to enhance your applications' security. ### More resources - [Memberships privacy documentation](https://appwrite.io/docs/products/auth/security#memberships-privacy) - [Introducing database backups](https://appwrite.io/blog/post/introducing-database-backups) - [WebP support now available for Safari on all devices](https://appwrite.io/blog/post/webp-support-for-safari) --- ## Messaging explained https://appwrite.io/blog/post/messaging-explained Recently, Appwrite launched its newest product, [Appwrite Messaging](https://appwrite.io/products/messaging), an open-source messaging solution that allows you to send push notifications, emails, and SMS directly to your users. In this blog, we will discuss all the fundamental features of the Messaging product and how a message is actually sent through the internal service. ### Unified messaging platform To give you a quick refresher, Messaging covers three communication channels under one unified API, allowing you to send email, SMS, and push notifications from your Appwrite project. It connects with a variety of third-party providers, such as Vonage, Twilio, Mailgun, and more, to deliver your messages. Our [documentation](https://appwrite.io/docs/products/messaging) provides a full overview of providers. ![Messaging features](/images/blog/messaging-explained/features.gif) ### Messaging concepts Appwrite Messaging helps you communicate with your users through push notifications, emails, and SMS text messages. With Messaging, you can deliver messages to groups of users through topics or address individual users' email, phone, or devices through targets. This enables several use cases, such as - Personalized communication for marketing - Real-time app alerts to increase user engagement and retention - Transactional messages, such as payment updates, invoices, etc. - Custom security checks and authentication flows Let’s now discuss the three core Messaging concepts: **topics**, **targets**, and **messages**. #### Topics In Appwrite Messaging, you can use topics to deliver messages to groups of users at once. Topics should have a clear meaning or goal. For example, a topic can represent a group of customers that receive common announcements or product updates. ![Topics](/images/blog/messaging-explained/topics.png) #### Targets Targets are different ways a user can be reached. For example, a user might have two emails, a phone number, and a phone and tablet installed with your app. This means the user has five different targets where you can deliver messages. You can send a message directly to a set of targets or add them as subscribers to a topic. For example, if you want to send a payment reminder to a user manually, you could choose to send it just to a specific target. If you want to send out your monthly newsletter, you could have a topic many targets can subscribe to and then send the message to the topic instead. ![Targets](/images/blog/messaging-explained/targets.png) #### Messages Messages contain information that is sent to a user from the application. A message can be of any of the following types: - Push notifications - Emails - SMS ![Messages](/images/blog/messaging-explained/preview.png) Here’s an example of how you would programmatically send an SMS to a topic using the Appwrite Node.js SDK. ```js const sdk = require('node-appwrite'); const client = new sdk.Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const messaging = new sdk.Messaging(client); . . . const message = await messaging.createSms( sdk.ID.unique(), 'Hello from Appwrite Messaging!', [smsTopic], // topic to send SMS to [], [], false, '' ); ``` You can also watch the [product tour](https://www.youtube.com/watch?v=QdDgPeuBZ1I) on YouTube to get a full overview of Messaging from the Console. ### How Messaging works Now that we have learned about the three core concepts of Appwrite Messaging, let us dive deeper into how the product works. Each time you send or schedule a push notification, email, or SMS text, it's recorded in Appwrite as a **message** is displayed in the **Messages** tab. However, there is a lot more that goes on under the hood. Here's a step-by-step breakdown of how Appwrite Messaging works: 1. Validate input on API request - The process starts when the Appwrite API is called to create a message, which validates the input. 2. Create message document - A document containing details about the message, including recipients and content, is created on the internal database. 3. Create schedule document - A schedule document is created on the internal database, specifying when the message should be sent. This document is necessary for the message scheduler. 4. Queue the message via the message scheduler - Every minute, the scheduler checks the internal database for scheduled messages. - For each scheduled message, it checks if the message should be sent at that time. - If yes, the message is queued for sending. 5. Preparing and sending the message via the messaging worker: - First, the worker fetches users and targets from topics, where targets can be email addresses, devices, or phone numbers. Targets that don't match the message type are ignored. - The worker then fetches the appropriate provider for each target based on the message type. If no specific provider is set, the first enabled provider matching the message type is used. - Targets are grouped based on the corresponding provider. - The message content is built for the specific provider and sent to the targets in batches. The worker then checks for other pending providers and repeats this step accordingly. - After sending the messages, the message status and logs are updated on the internal database and displayed on the Appwrite Console. ### Going forward We hope this blog improved your understanding of how Appwrite Messaging works. With that said, we have numerous additional resources to help you learn more about messaging in applications and build with Appwrite Messaging: - [Play around with Messaging on the product page](https://appwrite.io/products/messaging) - [Best practices for sending push notifications](https://appwrite.io/blog/post/push-notifications-best-practices) - [How Twilio simplifies messaging for developers](https://appwrite.io/blog/post/simplify-messaging-twilio) - [Documentation](https://appwrite.io/docs/products/messaging) - [Product video tour](https://www.youtube.com/watch?v=QdDgPeuBZ1I) --- ## Migrate Firebase projects to Appwrite https://appwrite.io/blog/post/migrate-firebase-projects-to-appwrite If you’re ready to move from Firebase to Appwrite, or you just want to explore your BaaS options, we can give you a jump start with Appwrite Migrations. ![Appwrite Console Migrations page](/images/blog/migrate-firebase-projects-to-appwrite/migrations-overview.png) Moving is frustrating. Packing, unpacking, renting a truck, wondering if your bed fits through the door. Moving data between cloud platforms feels much the same, so we built Migrations to help lighten the load. Migrations will help you move users, data, and files out of Firebase and into your Appwrite project. #### Limitations Just like moving houses, not every piece of old furniture will suit your new house. You’ll need some creative rearranging or maybe some new furniture. Appwrite is different from Firebase in a similar sense. - Appwrite automatically moves user accounts or files, but documents and cloud functions will require some work to move. - Nested documents will require some clever engineering to be transformed into Appwrite relationships. In a future version, they might be added automatically, but you will need to migrate them with a script for now. - Appwrite Functions also use different syntax and deployment methods than Firebase’s Cloud Functions. These will need the most work to migrate and likely need to be partially rewritten. #### Prepare for your move To help you with your move, Appwrite Migrations will require keys to your old home to access your project’s users, files, and data. This means Appwrite will need your Firebase service account’s API key. ![Firebase key management screen.](/images/blog/migrate-firebase-projects-to-appwrite/firebase-key.png) The quickest way to get a key is to use your **Firebase Admin SDK** service account and key, found under **Project settings > Service accounts > Generate private key**. If you want to use an API key with a more fine-grained permission scope, checkout the [Appwrite documentation on this topic](/docs/advanced/migrations/firebase). #### Starting the migration The Migration process is simple. Give Appwrite your keys, and it’ll pack up and move everything for you. 1. Create a new project and click on the **Migrations** tab in **Project Settings**. 2. Click on the **Create Migration** button and select **Firebase** as your source. 3. Paste your Firebase API key’s JSON contents into the account credentials box and click **Next.** 4. Select which resources you want Migrations to import and click **Create** to start the migration. Migrations will run in the background, get a cup of tea or coffee, and return in a few minutes. #### Wrapping up the move After your migration is completed, you’ll need to do a few more things to wrap up your move to Appwrite. First, you register your existing web, mobile, and native apps as platforms. Follow one of these [quick starts](/docs/quick-starts) to learn how. Next, checkout our docs for [Database relationships](/docs/products/databases/relationships) and install one of Appwrite’s [Server SDKs](/docs/sdks#server) to migrate your nested data. Finally, learn about [Appwrite Functions](/docs/products/functions/quick-start) to migrate your Firebase functions. Appwrite’s functions support more runtimes, more flexible execution schemes, and follow patters found in HTTP controllers that you’re already familiar with. #### Join the discussion We’re always having a blast on [Discord](https://appwrite.io/discord). With members in the community from all over the world, you’ll always find someone to support and share your Appwrite journey. --- ## 7 reasons to not think twice before migrating from Vercel to Appwrite Sites https://appwrite.io/blog/post/migrate-from-vercel-to-appwrite-sites If you’ve been hosting on Vercel, you’ve probably grown used to a deployment workflow that feels effortless. Git push, automatic builds, instant previews, and a global network behind you. Migrating away from that level of convenience can feel like a step backwards, but with Appwrite Sites, it’s the exact opposite. You get the same smooth developer experience, with more power, more flexibility, and a better cost structure behind it. Here’s why teams are making the move without hesitation. ### 1. Same great git push workflow The most painful part of switching platforms is often the workflow disruption. Appwrite Sites eliminates that entirely. Your developers don’t have to learn new [deployment](/docs/products/sites/deployments) commands or rewire their CI/CD pipelines. You push your code to Git, Appwrite detects your repository, builds your project, and automatically deploys it. You get instant [preview environments](/docs/products/sites/previews) for every pull request, just like you’re used to on Vercel. For teams that have invested years into refining their workflows, this kind of familiarity matters. It allows you to migrate without slowing down development or retraining your team. The move becomes almost invisible, except for the benefits that follow. ### 2. Full Next.js support without quirks If you’ve ever had to adjust a Next.js project to fit a hosting provider’s limitations, you know how much friction that can introduce. Appwrite Sites runs on containers, not restrictive serverless runtimes. That means your Next.js app runs exactly as it was designed to. No workarounds. No “open next” patches. No custom build steps. Whether you rely on static rendering, SSR, middleware, or API routes, everything behaves as expected. It’s the same Next.js your team has already built and optimized, just deployed on a platform that gives you full control over your runtime. ### 3. Global delivery powered by multiple CDNs Appwrite’s network architecture is built for scale from day one. Under the hood, Appwrite Sites uses a [multi-CDN](/docs/products/network/cdn) setup that combines several top-tier CDN providers into a single, globally distributed edge layer. This means your users automatically get routed through the fastest and most reliable network paths, no matter where they are in the world. For you, that translates into consistent performance, lower latency, and high availability without having to think about the infrastructure behind it. You get to work with a modern, shiny product, but the actual delivery layer is backed by some of the most battle-proven CDN technologies in the industry. It’s the best of both worlds, modern DX with enterprise-grade reliability. ### 4. A cost structure built to scale Great developer experience shouldn’t come at a premium that scales out of control. One of the biggest reasons teams are switching to Appwrite Sites is cost. Every deployment comes with 2TB of bandwidth included in the base plan, and volume discounts are available to any team migrating from Vercel. This isn’t just about being cheaper on paper. It’s about predictability. You can scale your traffic, onboard more users, or ship larger apps without constantly worrying about hidden overages or sudden spikes in your bill. Appwrite’s pricing was designed to support growth, not penalize it. ### 5. More than hosting. A full cloud platform Appwrite Sites is just the entry point. Behind it stands the full Appwrite Cloud platform: a set of deeply integrated products designed to help teams build entire applications in one place. Your frontend can be hosted on Sites, your backend logic can run in [Appwrite Functions](/docs/products/functions), your data can live in [Appwrite Databases](/docs/products/databases), and your assets can be stored in Appwrite Storage. Realtime updates and messaging can keep your apps dynamic and engaging, while [Appwrite Auth](/docs/products/auth) provides secure and flexible authentication built right into the platform. Everything works great individually, but when combined, they create a single cohesive development and deployment experience. For teams tired of stitching together multiple services, this integrated approach is a huge win. It simplifies architecture, improves maintainability, and removes the need for countless third-party dependencies. ### 6. Open source by design Appwrite is fully open source, and that’s not just a nice-to-have. It’s a fundamental difference in how the platform operates. You can look under the hood, understand exactly how things work, and contribute if you want to. There’s no black box or hidden lock-in. If your company ever outgrows the cloud platform or needs to move workloads on-prem, you can [self-host](/docs/advanced/self-hosting). That level of control and transparency is rare in modern cloud platforms, and it gives technical leaders the confidence to build on top of Appwrite for the long term. ### 7. A migration path that doesn’t hurt Migrations are often underestimated in complexity. Appwrite Sites was built to make that process as painless as possible. If you’re running a Next.js app, a React SPA, or any modern frontend framework, [you can migrate](/docs/products/sites/migrations/vercel) without refactoring your application. Your repos stay the same. Your build commands stay the same. Your deployments stay the same. You just point your project to Appwrite and push. In most cases, teams are able to go live within a day, and in many, within an hour. ### Wrapping up Migrating from Vercel doesn’t mean giving up the workflow you love. It means keeping the same great developer experience, while gaining a more flexible runtime, better network performance, a lower and more predictable cost structure, and access to a full cloud platform that can power your entire stack. For teams that want modern DX without compromise, Appwrite Sites is the logical next step. If your team is ready to make the move, we’ve made it simple to get started - and we can support you through the migration if you need a hand. ### More resources - [Appwrite Sites documentation](/products/sites) - [Announcing Appwrite Sites: The open source Vercel alternative](/blog/post/announcing-appwrite-sites) - [Appwrite Sites vs Vercel: Choosing the right web hosting platform](https://appwrite.io/blog/post/open-source-vercel-alternative) --- ## Mock numbers for phone sign-in: Use cases and best practices https://appwrite.io/blog/post/mock-numbers-best-practices If you've ever struggled with testing phone sign-in flows without incurring the cost of real SMS messages or waiting for delivery delays, you'll appreciate the convenience of mock numbers. In Appwrite, mock numbers are predefined phone numbers and verification codes that you can use to simulate phone sign-in scenarios during development and testing. In this guide, we'll explore the use cases, setup, and best practices for using mock numbers in your development and testing processes. ### Use cases for mock numbers Mock numbers offer several benefits for developers, testers, and other stakeholders involved in the app development lifecycle. Here are some common use cases for using mock numbers in your projects: **1. Development and debugging** Mock numbers make it easier to develop and debug phone authentication flows. You can quickly test authentication scenarios without waiting for SMS delivery or using real phone numbers. **2. Quality assurance testing** QA teams can consistently test various authentication scenarios with mock numbers, including edge cases, to ensure the app behaves correctly under different conditions. **3. App Store review process** Authentication flow testing might be required when submitting apps to platforms like Google Play and the Apple App Store. Mock numbers allow reviewers to test these features without using personal phone numbers. **4. Demonstrations and demos** Mock numbers simplify the demonstration of phone sign-in functionality during presentations. You can showcase the complete authentication flow without delays or interruptions caused by SMS delivery. ### Setting up mock numbers in Appwrite To set up mock numbers in Appwrite, you need to follow a few simple steps. #### Step 1: Access the Appwrite console Log in to your Appwrite account and select the project you want to configure. Ensure phone authentication is enabled for your project. #### Step 2: Configure mock numbers 1. **Navigate to `Project > Auth > Security` :** Scroll to the **Mock phone numbers** section 2. **Generate Mock Number**: Click the **generate number** button. This will generate a mock phone number and verification code that you can use to test user authentication. ![Mock-numbers-Appwrite](/images/blog/mock-numbers-use-cases/1.png) You can regenerate the number and verification code by clicking the icon beside the input field or you can edit it directly. To generate another mock phone number and verification code, click the **Add number** button. 3. **Save Changes**: Click the `Update` button to apply the mock number configurations. #### Step 3: Implement mock numbers in your application Use Appwrite’s SDK to integrate phone sign-in and test the authentication flow with mock numbers. Let's walk through an example using the Appwrite JavaScript SDK. #### Initialize Appwrite client and account First, you need to set up the Appwrite client and account objects: ```jsx import { Client, Account } from 'appwrite' // Initialize Appwrite client const client = new Client() client.setEndpoint('https://.cloud.appwrite.io/v1').setProject('') // Initialize Account const account = new Account(client) ``` In this code: - We import the necessary modules from the Appwrite SDK. - We create and configure a `Client` instance with the Appwrite endpoint and project ID. - We create an `Account` instance associated with the client. #### Create a phone session using a mock number Next, create a function to handle phone sign-in with mock numbers: ```jsx async function createPhoneSession(phone, secret) { try { // Create a phone token const token = await account.createPhoneToken({ userId: 'unique_id', phone: phone.trim() }) const userId = token.userId // Create a session using the provided secret const session = await account.createSession({ userId, secret }) console.log('Session created:', session) return session } catch (error) { console.error('Error creating phone session:', error) } } ``` Here's what happens in this function: - The `createPhoneToken` method generates a phone token for the provided phone number. - We extract the `userId` from the token. - The `createSession` method creates a session using the `userId` and the provided secret (verification code). - If successful, the session details are logged; otherwise, the error is logged. #### Example usage Finally, use the function to create a session with a mock number and verification code: ```jsx const mockPhoneNumber = 'mock_phone_number'// Replace with your mock numberconst mockSecretCode = 'mock_verification_code'// Replace with your mock verification codecreatePhoneSession(mockPhoneNumber, mockSecretCode) ``` In this example: - Replace `'mock_phone_number'` with your actual mock number. - Replace `'mock_verification_code'` with the verification code set for the mock number. #### Step 4: Validate and test 1. **Run tests**: Execute tests using the mock numbers to ensure that the phone sign-in flow behaves as expected. 2. **Check results**: Verify that the session is created successfully and that all parts of the authentication flow are functioning correctly. ### Best practices for using mock numbers #### Consider security Use mock numbers only in development, testing, and demo environments. Avoid using them in production to maintain the integrity and security of your application’s authentication system. #### Maintain consistency in test scenarios Ensure that mock numbers and verification codes are used consistently across different testing environments. This consistency helps achieve reliable and repeatable test results. #### Document clearly Document the mock numbers and codes used in your testing processes. Include details about their purpose, the scenarios they are used for, and any specific configurations. #### Separate environments Maintain a clear separation between mock setups and production environments. Use environment-specific configurations to avoid accidental use of mock numbers in live applications. #### Recognize testing limitations Remember that mock numbers are useful for simulating the authentication flow but may not replicate all real-world conditions, such as network delays or SMS delivery issues. For comprehensive testing, real phone numbers and actual SMS delivery may still be necessary. #### Clean up unused mock numbers Remove mock numbers that are not in use, or delete them once the tests are completed. This practice helps maintain a clean testing environment and prevents the accumulation of obsolete data. ### Conclusion Mock numbers in Appwrite offer a practical solution for testing phone sign-in flows, facilitating development, QA testing, app reviews, and demonstrations. Mock numbers are available to Pro users (introduced in 1.6). By using mock numbers effectively and following best practices, you can streamline your testing processes and ensure a robust authentication experience for your users. Further reading and resources: - [Appwrite phone (SMS) auth docs](https://appwrite.io/docs/products/auth/phone-sms) - [Appwrite custom token docs](https://appwrite.io/docs/products/auth/custom-token) - [Our Discord community](https://appwrite.io/discord) --- ## Choosing the right platform to deploy your web apps: Vercel, Netlify, Amplify, and Appwrite Sites compared https://appwrite.io/blog/post/netlify-vs-vercel-vs-amplify-vs-appwrite-sites Deploying a web app today is nothing like it was a few years ago. What used to mean renting servers and wiring up your own pipelines has turned into choosing between platforms that do most of the heavy lifting for you. That’s great for speed, but it also means each platform has its own opinion on how apps should be built and deployed. Some aim to make frontend performance effortless, others double down on the simplicity of static-first sites, some tie you directly into massive cloud ecosystems, and newer options try to package everything, hosting and backend, into one stack. None of these approaches is wrong, but they shape your workflow in very different ways. So the real question isn’t just *“where do I host my app?”,* it’s *“what kind of experience (and bill) am I signing up for?”* In this blog, we’ll break down **Vercel, Netlify, AWS Amplify, and Appwrite Sites** to see how each fits into the way developers are shipping web apps today. ### Quick overview - **Vercel**: Think of Vercel as the **framework-native host**. It’s tightly coupled with Next.js, and its entire model revolves around serving modern frontend frameworks at the edge with minimal friction. Backend logic exists, but always as small serverless functions or external APIs. - **Netlify**: The **Jamstack generalist**. Netlify built its reputation on static-first workflows: push code, build, deploy, done. It doesn’t lean into any one framework, instead, it’s about a consistent, git-driven pipeline that works for React, Vue, Svelte, or plain HTML. - **AWS Amplify**: Amplify is built to connect your frontend with AWS services. It gives you hosting, but the main focus is making it easy to add things like authentication (Cognito), databases (DynamoDB), file storage (S3), and serverless functions (Lambda). Hosting is just one part; the bigger goal is tying your app into AWS’s cloud tools. - **Appwrite Sites**: The hosting platform built on top of Appwrite. It’s designed for deploying static sites and frontends that connect directly to Appwrite’s backend services like [Auth](/docs/products/auth), [Databases](/docs/products/databases), and [Functions](/docs/products/functions). Unlike Vercel or Netlify, which are hosting-first providers, Appwrite Sites is an extension of the broader Appwrite open-source backend platform, giving developers hosting plus a backend in one cohesive stack. | Platform | Best fit for | Approach | Lock-in | Open source | | --- | --- | --- | --- | --- | | **Vercel** | Next.js and modern frontend apps | Framework-optimized, frontend-first | Medium (proprietary infra) | No | | **Netlify** | Static sites & Jamstack projects | Git-based deploys, static-first | Medium (plugins, infra) | No | | **AWS Amplify** | Teams already in AWS ecosystem | Hosting + AWS backend services | High (AWS services) | No | | **Appwrite Sites** | Full-stack apps, open-source users | Hosting + built-in backend stack | Low (self-host or cloud) | Yes | ### Vercel ![Vercel](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/vercel.png) #### Pricing of Vercel - **Free**: Hobby projects, personal sites (limited bandwidth & function executions). - **Pro**: ~$20/user/month, adds better bandwidth, analytics, and team features. - **Enterprise**: Custom pricing, advanced security, scaling, and SLAs. #### Key features of Vercel - Optimized for **Next.js** (ISR, edge functions, fast refresh). - Global edge network for low-latency delivery. - Built-in CI/CD with Git integration. - Analytics and observability baked in. #### Pros and cons of Vercel | Pros | Cons | | --- | --- | | Best-in-class support for Next.js | Pricing grows quickly because it’s per-user and per-resource based | | Great developer experience | Limited backend, mostly serverless only | | Strong ecosystem and integrations | Proprietary platform, no self-host option | ### Netlify ![Netlify](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/netlify.png) #### Pricing of Netlify - **Free**: Personal projects with basic builds and bandwidth. - **Pro**: ~$19/user/month, includes more team features and higher limits. - **Business/Enterprise**: Custom pricing with SLAs and advanced integrations. #### Key features of Netlify - Git-based deployments with automatic builds. - Strong Jamstack focus (static site generation). - Netlify Functions for serverless logic. - Plugin marketplace for integrations. #### Pros and cons of Netlify | Pros | Cons | | --- | --- | | Very easy to use and set up | Advanced workflows often depend on community plugins, which can be hit-or-miss in quality and long-term maintenance | | Great for static and Jamstack apps | Limited backend services | | Plugin ecosystem adds flexibility | Vendor lock-in, no open-source option | ### AWS Amplify ![Amplify](/images/blog/netlify-vs-vercel-vs-amplify-vs-appwrite-sites/amplify.png) #### Pricing of Amplify - **Free tier**: 12 months free with AWS (limited usage). - **Pay as you go**: Charges based on hosting, storage, function invocations, and connected AWS services. - **It can get expensive** depending on the scale and usage of AWS backend services. #### Key features of Amplify - Hosting for static sites and SPAs. - Easy integration with AWS services (Cognito, DynamoDB, Lambda, S3, AppSync). - GraphQL and REST API support. - CLI and libraries for frontend frameworks. #### Pros and cons of Amplify | Pros | Cons | | --- | --- | | Tight integration with AWS stack | Steeper learning curve than others | | Scales seamlessly with AWS infra | Can get expensive quickly as costs scale across multiple AWS services (hosting, storage, functions, APIs). | | Supports GraphQL and REST APIs | Heavier setup, less beginner-friendly | | Good fit for AWS-heavy teams | Strong AWS lock-in | ### Appwrite Sites ![Appwrite Sites](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/sites.png) #### Pricing of Appwrite Sites - **Free & Open Source**: Self-host anywhere. - **Cloud**: Free tier with limits, paid plans for scaling (transparent pricing, lower lock-in than others). #### Key features of Appwrite Sites - Static site and frontend hosting. - Built-in backend: auth, database, storage, functions. - Open-source and self-hostable. - Developer-first [APIs](/docs/references) and [SDKs](/docs/sdks). #### Pros and cons of Appwrite Sites | Pros | Cons | | --- | --- | | Open-source, no vendor lock-in | Ecosystem is newer, still growing | | Combines hosting + backend | Smaller community vs Vercel/Netlify | | Flexible: self-host or cloud | Not as battle-tested at massive scale | | All-in-one stack | | ### Frequently Asked Questions **1. What is the best open-source alternative to Vercel?** Appwrite Sites is one of the good open-source options to consider. It lets you host static sites and connect directly to built-in backend services like authentication, databases, and functions. Unlike Vercel or Netlify, you can self-host Appwrite if you want full control. **2. How does Appwrite compare to Vercel and Netlify?** Vercel is optimized for Next.js, Netlify is great for Jamstack projects, while Appwrite Sites goes beyond hosting by including backend services out of the box. If you want hosting plus authentication, database, and storage in one place, Appwrite Sites is worth considering. **3. Can I host a React app with a backend on one platform?** Yes. With Appwrite Sites, you can deploy a React frontend and connect it to Appwrite’s backend services (database, storage, authentication, and functions) without needing separate providers. **4. Are there cheaper alternatives to AWS Amplify for static hosting?** Vercel and Netlify both have generous free tiers, but scale costs differ. Appwrite Sites provides a free self-host option (since it’s open-source), making it a cost-effective alternative to Amplify if you don’t want to commit to AWS pricing. **5. Is Appwrite a good option for Git-based deployments like Netlify?** Yes. Appwrite Sites supports GitHub Actions and CI/CD pipelines, so you can deploy your frontend directly from your repo, similar to the workflows developers are used to with Netlify or Vercel. **6. What’s the most secure way to host with a custom domain?** All four platforms: Vercel, Netlify, AWS Amplify, and Appwrite Sites, support custom domains with SSL. The difference is in control: with Appwrite, you can self-host and manage certificates yourself if you prefer more security and ownership. **7. Does Appwrite Sites have enterprise support?** Appwrite offers enterprise support plans along with the open-source and cloud offerings, making it viable for both indie developers and larger organizations that need SLAs and dedicated support. --- ## Appwrite Sites vs Netlify vs Vercel vs Azure Static Web Apps: Which platform should you choose in 2025? https://appwrite.io/blog/post/netlify-vs-vercel-vs-azure-vs-appwrite-sites Static site hosting has come a long way from just serving HTML files on a basic server. Today, platforms like **Vercel, Netlify, Azure Static Web Apps, and Appwrite Sites** make it possible to push code from a Git repo and have a live site up in seconds. But with more choice comes more confusion. Developers often find themselves asking: - “Should I go with Vercel or Netlify?” - “Is Azure a good option for static sites?” - “Are there open-source alternatives that give me more control?” Each platform has its own philosophy. Vercel doubles down on Next.js, Netlify pioneered the JAMstack movement, Azure caters to enterprise teams, and Appwrite Sites adds an open-source spin with backend services included. In this blog, we’ll look at all four side by side: what they offer, how much they cost, and the trade-offs you’ll want to keep in mind before choosing your stack. ### What is static hosting and why does it matter Static hosting has become the default for modern web projects. Instead of managing heavy servers or complex configurations, you can simply deploy HTML, CSS, and JavaScript files to a global CDN and let users access them instantly. ### Benefits of static hosting - Speed by default: Static sites are served directly from a global CDN, so your content is cached close to the user. That’s why a **Netlify static site** or a **Vercel app** often feels blazing fast without extra optimization. - Simplicity: You don’t need to manage servers, runtime environments, or scaling policies. Deployment is often as easy as connecting a Git repo. - Scalability: Whether you’re serving 10 users or 10 million, static hosting scales effortlessly since files are distributed globally. - Lower costs: Serving static files is cheap compared to dynamic servers. Even with added edge functions or APIs, costs are predictable. With platforms like **Appwrite Sites**, where hosting, authentication, databases, and storage come together in one place, you save money by avoiding the need for multiple separate services. - Security: There are no databases or servers exposed by default, which means fewer attack vectors. For many developers, this is a big reason to switch from traditional hosting. ### Quick comparison: Appwrite Sites vs Vercel vs Netlify vs Azure Static Web Apps | Name | Free plan | Deployments | Custom domains | Open source | Best feature | | --- | --- | --- | --- | --- | --- | | **Appwrite Sites** | Yes – generous free tier + option to self-host | Git-based + CLI | Yes (free with SSL) | Yes | **All-in-one platform** with hosting + backend tools | | **Vercel** | Yes – hobby plan | Git push, instant edge deploys | Yes (free with SSL) | No | Seamless **Next.js integration** + global edge network | | **Netlify** | Yes – starter plan | Git-based, auto CI/CD | Yes (free with SSL) | No | JAMstack pioneer with broad framework support (**Netlify static site**) | | **Azure Static Web Apps** | Yes – limited free tier | GitHub Actions + Azure DevOps | Yes (included) | No | Deep **Microsoft Azure integration** | ### Appwrite Sites ![Appwrite Sites](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/sites.png) [Appwrite Sites](/products/sites) is an **open source alternative to Vercel and Netlify** that goes beyond static hosting. Instead of just pushing your frontend live, it bundles in backend essentials like [authentication](/products/auth), [databases](/docs/products/databases), messaging, and serverless functions. That makes it a good choice for developers who want to move fast without juggling multiple providers. #### Key features of Appwrite Sites - Fully **open source** with self-hosting option - Offers SSR and static support for a [wide range of frameworks](/docs/products/sites/frameworks) - All-in-one cloud tool: [hosting](/docs/products/sites) + backend services (auth, DB, storage, functions) - [Git-based deployments](/docs/products/sites/deploy-from-git) with CLI and dashboard support - Designed to avoid vendor lock-in and keep costs predictable - [Optimized CDNs](/docs/products/network/cdn), [edge capabilities](/docs/products/network/edges), and autoscaling are built in and fully abstracted through the [Appwrite Network](/docs/products/network). #### Pricing of Appwrite Sites - Free tier for small projects and testing. - Pro plan at $25/month with 2TB API bandwidth monthly. - Self-hosting option #### Pros and Cons of Appwrite Sites | Pros | Cons | | --- | --- | | Open-source, transparent, and flexible | Newer ecosystem vs Netlify/Vercel | | All-in-one (frontend + backend tools) | Fewer ready-made integrations/plugins | | Self-host option reduces long-term **costs** | Requires infra management if self-hosted | ### Vercel ![Vercel](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/vercel.png) [Vercel](https://vercel.com/home) has become the go-to choice for teams building with **Next.js**. It offers a polished developer experience with instant Git-based deployments and a global edge network that makes both static and server-rendered apps incredibly fast. While it shines for frontend developers, costs can rise quickly as your app scales. #### Key features of Vercel - Deep **Next.js integration** for seamless SSR and static rendering - Global edge network for performance and speed - Simple Git-based deployments (GitHub, GitLab, Bitbucket) - Built-in analytics, serverless functions, and edge tools #### Vercel pricing - Free hobby plan - Pro plan at $20/mon. - No self-hosting option #### Pros and cons of Vercel | Pros | Cons | | --- | --- | | Best-in-class Next.js integration | Vendor lock-in to Next.js + Vercel stack | | Lightning-fast edge deployments | **Costs** rise sharply with traffic | | Sleek developer UX | Less appealing if not using React/Next.js | ### Netlify ![Netlify](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/netlify.png) [Netlify](https://www.netlify.com/) pioneered the **JAMstack** approach, making it easy to launch static sites with CI/CD built in. It supports a wide range of frameworks beyond React and has a rich plugin ecosystem. It remains one of the easiest ways to ship a **Netlify static site**, though teams often look for **Netlify alternatives** as projects scale due to pricing #### Key features of Netlify - Supports many frameworks (React, Vue, Angular, Svelte, etc.) - Automatic CI/CD pipelines from Git repositories - Plugin marketplace and add-ons for extended features - Edge functions and lightweight APIs for backend logic #### Netlify pricing - Free starter plan for individuals - Paid tiers charge for build minutes, bandwidth, and team members - Netlify costs can rise quickly with scale #### Pros and cons of Netlify | Pros | Cons | | --- | --- | | Wide framework support | Functions limited compared to full backends | | Mature ecosystem with plugins and add-ons | **Costs** increase at scale | | Quick setup for JAMstack projects | Vendor lock-in still applies | ### Azure Static Web Apps ![Azure](/images/blog/netlify-vs-vercel-vs-azure-vs-appwrite-sites/azure.png) [Azure Static Web Apps](https://azure.microsoft.com/en-us/products/app-service/static) is Microsoft’s take on static hosting, tightly integrated with GitHub Actions and Azure DevOps. It’s built with enterprises in mind, offering compliance, scalability, and seamless access to the rest of the Azure ecosystem. For smaller teams, though, it may feel complex compared to Netlify, Vercel or Appwrite Sites. #### Key features of Azure Static Web Apps - Deep integration with **Microsoft Azure** services - CI/CD through GitHub Actions and Azure DevOps - Enterprise-ready with global scalability and compliance features - Combines static hosting with Azure Functions for APIs #### Pricing of Azure Static Web Apps - Free tier with limited bandwidth and functions - Paid plans scale based on storage, bandwidth, and usage - Developers often search for **Azure static web app pricing** or **Azure static web app costs** due to the complexity #### Pros and cons of Azure Static Web Apps | Pros | Cons | | --- | --- | | Strong Microsoft ecosystem integration | Pricing is complex (**Azure static site** confusion) | | Enterprise compliance and global reach | Overkill for small teams | | Seamless GitHub workflows | Steeper learning curve | ### FAQ **1. What makes Appwrite Sites different from Netlify or Vercel?** Unlike Netlify or Vercel, which mainly focus on static hosting, **Appwrite Sites** is part of an all-in-one platform. It combines hosting with backend features like authentication, databases, storage, and serverless functions. This means you can deploy your frontend and backend in one place without relying on multiple services. **2. Does Appwrite Sites support custom domains and SSL?** Yes. You can easily connect your **custom domains** on Appwrite Sites, and SSL is automatically included at no extra cost. This gives you secure hosting out of the box. **3. Can I use Appwrite Sites for enterprise projects?** Yes. Appwrite offers **enterprise plans** that include advanced features, dedicated assistance, and deployment options to fit compliance and scaling needs. For organizations that need more than just static hosting, Appwrite’s broader backend services make it a strong fit. **4. How does Appwrite save costs compared to other hosting platforms?** With Appwrite, you don’t need to pay for separate providers for hosting, authentication, databases, and storage. Everything is bundled into one platform. This helps you **save money** while reducing the complexity of managing multiple services. --- ## Announcing HEIC and AVIF support: modern image formats now in Appwrite https://appwrite.io/blog/post/new-image-formats-avif-heic We’re excited to share we have added support for two new image formats in Appwrite Storage: **HEIC** and **AVIF.** This will give you more tools to manage, manipulate, and serve images the way you need. You can generate image previews directly in **.heic** and **.avif** formats, which are natively supported in Appwrite. No converters. No extra steps. Just fast, flexible image handling built in. ### Native support for next-gen formats Image performance matters. Whether you're building a high-performance photo app or just optimizing media delivery, modern formats like AVIF and HEIC offer massive benefits: smaller sizes, better quality, and native platform support. With this update, Appwrite now supports outputting previews in **HEIC** (Apple’s proprietary format) and **AVIF** (a modern open format with excellent compression). ### Why it matters Previously, if you wanted to serve HEIC or AVIF images from Appwrite, you had to download them, convert them manually, and re-upload or host elsewhere. Now, the Appwrite Storage preview API can do it all automatically. This means: - No more external tools - No extra conversion steps - No breaking your image pipeline Just fast, reliable image delivery in the formats you need. ### Built for Apple and beyond HEIC is Apple’s default image format on iOS. AVIF is increasingly used across modern browsers and devices. This feature was designed for mobile app developers, especially those targeting iOS and working with native image capture and display. But it’s just as useful for any developer looking to serve modern image formats with better compression and quality. ### How to use it Use the image preview endpoint as you normally would, just set the desired output format to HEIC or AVIF. It works the same way as existing format conversions like `jpg` or `png`. You’ll see smaller file sizes, faster load times, and better integration with native apps and devices. ### How to get started HEIC and AVIF support is available on **Appwrite Cloud** and **self-hosted** installations. ```jsx import { Client, Storage, ImageFormat } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const storage = new Storage(client); const resultHeic = storage.getFilePreview({ bucketId: 'photos', fileId: 'sunset.png', width: 1800, gravity: 'center', quality: '90', borderWidth: 5, borderColor: 'CDCA30', borderRadius: 15, background: 'FFFFFF', output: ImageFormat.Heic }); console.log(resultHeic.href); const resultAvif = storage.getFilePreview({ bucketId: 'photos', fileId: 'sunset.jpeg', output: ImageFormat.Avif }) console.log(resultAvif.href); ``` [Visit the documentation](/docs/products/storage/images) to learn more. ### Smarter storage for better apps This feature is part of our ongoing commitment to giving you modern tools that work the way you expect. Whether you're building a media-heavy app, targeting Apple platforms, or just caring about performance, you now have more control and better output without extra overhead. ### More resources Take a look at what else we released this week. - [Appwrite Sites](/blog/post/announcing-appwrite-sites) - [Hosting for Flutter web](/blog/post/hosting-flutter-web) - [Dev Keys](blog/post/announcing-dev-keys) - [Appwrite compared to Vercel](/blog/post/open-source-vercel-alternative) - [Appwrite Sites product tour](https://youtu.be/VtDe6hDw91k) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](https://appwrite.io/discord) --- ## Next.js output modes — Standalone vs Default build https://appwrite.io/blog/post/nextjs-output-modes Next.js is a very popular React framework. People choose it because it blends in with the server very well, using both client-side and server-side capabilities to provide a better user experience. Next.js is packed with cutting-edge React features like RSC. However, such features also bring complexity when deploying your app. Appwrite Sites enables you to deploy your Next.js app seamlessly without any hassle of configuration. Appwrite handles all the complexity involved in deploying such apps, so that you can focus on the core logic of your app. However, we also allow you to fiddle with the behaviour of how deployments work with Appwrite Sites. Output modes allow you to decide how your Next.js app is bundled. Each mode has its own pros and cons. In this article, we will talk about the different Next.js output modes, what would be better for you, and what we have observed when using different output modes with Appwrite Sites. ### The "default" mode The default mode relies on your dependencies stored in your `node_modules` and the build bundle stored in `.next` folder after running `next build`. To run your app in this output mode, you should run `next start` to serve your app in production. It's important to note that if you remove `node_modules`, which usually is the biggest chunk of your app, your app won't run anymore, as your builds also rely on `node_modules` to work properly. So, when counting the size of your app, you should also count in `node_modules` folder as well. The default mode is typically deployed by running `next start`. This is convenient, but gives you less control over the underlying server process than approaches where you own the server entrypoint (e.g., injecting custom server logic/handlers as part of your runtime). You might already think that shipping `node_modules` takes a big hit on storage, and while it's true, but not always a bad thing. We will later look at why. ### The "export" mode Even though we won't be comparing this mode in our benchmarks later in the article, we will still talk about it. The "export" mode is a mode that allows you to export your app as a static site. This mode is useful if you want to deploy your app as a static site. To use this mode, configure `output: 'export'` in `next.config.js` and run `next build`. This will create an `out` folder that you can serve with any static hosting provider. ### Standalone mode When building your app in standalone mode, Next.js traces all the files that are required by your app from your `node_modules`. This means that only the functionality your app uses will be bundled into your Next.js build. In this case, you can exclude `node_modules` when calculating sizes, as the build doesn't rely on `node_modules` as it already bundles all necessary code. This results in bigger `.next/standalone` folder, but we also have to factor in the fact that `node_modules` are essential for the default mode, and not the standalone mode. The `.next/standalone` folder does not contain any static assets, and Next.js recommends serving them through a CDN. For a minimal setup, you can run a few commands to copy static assets into the standalone directory. Even with all the benefits, there's a very good reason for this mode not being the default yet. ### Our observations We prepared a [benchmark](https://github.com/appwrite-community/nextjs-outputs-bench) where we ran the `next build` command for four Next.js apps to check how large their build sizes were: 1. Default mode, no dependencies 2. Standalone mode, no dependencies 3. Default mode, very heavy dependencies 4. Standalone mode, very heavy dependencies Then, we compared the sizes of these builds and observed the following: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📈 Summary & Comparisons ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔹 No Dependencies - Default vs Standalone Default: 431.42 MB (.next + node_modules) Standalone: 57.84 MB 📉 Difference: 373.58 MB smaller 🔹 Heavy Dependencies - Default vs Standalone Default: 712.48 MB (.next + node_modules) Standalone: 57.84 MB 📉 Difference: 654.64 MB smaller 🔹 Impact of Heavy Dependencies Default mode: +281.06 MB (65.1% increase) Standalone mode: +0 KB (0.0% increase) ``` The result was clear: standalone mode consistently showed lower build sizes. Interestingly, even when we installed heavy dependencies in the app, we observed that the build size for the apps with standalone mode remained unchanged. Because those dependencies were not being imported and used in the app, they were not bundled, which is ideal. Lower build sizes also result in faster cold starts for your app, which creates a better user experience. ### Which is the best for me? Standalone mode seems to be the clear winner based on the results. That would make you think—why is it not the default? Because it's not the most reliable. There are some very unclear cases where your standalone build would break or cause problems in production: - If you use monorepos and import packages from it, you might need to configure Next.js to be able to trace those packages. - If you dynamically import packages in your app, standalone mode cannot help you here, as only the code that is used and can be traced is bundled. - There could be many weird edge cases here and there. **So what's the best for you?** Try out standalone. If something goes wrong, you can switch back to default mode. If you're building a very basic Next.js app, you will not go wrong with standalone mode. ### Wrapping up Next.js standalone output is very powerful and could help you a lot when deploying your app. Standalone mode is fully supported on Appwrite Sites, so you can reap the benefits of it now! - [Next.js standalone builds now supported on Appwrite Sites](/blog/post/nextjs-standalone-support-in-appwrite-sites) - [Everything new in Next.js 16](/blog/post/everything-new-in-nextjs16) --- ## Next.js standalone builds now supported on Appwrite Sites https://appwrite.io/blog/post/nextjs-standalone-support-in-appwrite-sites You can now deploy Next.js apps built in [standalone mode](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) on Appwrite Sites, with full support for Next.js 16. In standalone mode, the build process creates a smaller package that includes only the files your app needs to run in production, leaving out development tools and unused parts of the framework. ### Why standalone mode? In our benchmarks, build size dropped by about six times on average, and cold start times were 2x faster. Deployments are lighter with faster build times, and sites start responding more quickly after going live. This work began with a report by a community member, [Warfin](https://github.com/WarFiN123), about build issues in Next.js 16. Those discussions led to deeper improvements in how Appwrite handles builds, which made standalone support possible. ### How to enable Next.js standalone mode To enable standalone mode, open your `next.config.js` file and add `output: 'standalone'` to your existing configuration: ```js /** @type {import('next').NextConfig} */ module.exports = { output: 'standalone', }; ``` Then build your project as usual. Appwrite Sites now recognizes that output automatically, so you can deploy it directly with no further changes. This update also lays the groundwork for broader improvements as we better understand how we can apply the same benefits to all Next.js builds, and eventually to all SSR builds. We're excited to see what you ship next! --- ## How to setup the Next.js starter template on Appwrite Sites https://appwrite.io/blog/post/nextjs-starter-sites Building web applications requires both front-end expertise and back-end infrastructure. Appwrite Sites simplifies this process by providing a platform for deploying, hosting, and scaling web applications. To ease this process even further, Appwrite Sites offers a variety of starter kits for popular frameworks like Next.js, React, Vue, Nuxt, Angular, SvelteKit, and Flutter. In this blog, you will learn how to set up the Next.js starter template and deploy it to [Appwrite Sites](/products/sites). ### Overview of the starter template Next.js is a React framework that enables developers to build fast, scalable web applications with features like server-side rendering, static site generation, and API routes out of the box. Appwrite's Next.js starter template includes: - A clean, single-page UI - Integration with Appwrite's SDK - Pre-configured deployment settings for Appwrite Sites' SSR rendering strategy ![Deployed app](/images/blog/nextjs-starter-sites/deployed.png) ### Deploy the starter template on Appwrite Firstly, you must head to Appwrite Cloud and [create an account](https://cloud.appwrite.io/console/register) if you haven't already (or [self-host Appwrite 1.7](/docs/advanced/self-hosting)). Next, create your first project, which will lead you to the project overview page. ![Add platform](/images/blog/nextjs-starter-sites/add-platform.png) Head to the **Sites** page from the left sidebar, click on the **Create site** button, and select the **Clone a template** option. This will take you to the Appwrite Sites templates listing, where you should search `Next.js starter` and click on the template. ![Starter template](/images/blog/nextjs-starter-sites/template.png) After selecting the template, you can choose to connect a GitHub repository now or at a later time. If you choose to connect a repository, ensure you select a production branch (leave the root directory as is). Then, review the preset environment variables, update the domain name if you want, and click on the **Deploy** button. You can watch the deployment logs as the site is built. ![Deployment logs](/images/blog/nextjs-starter-sites/deployment-logs.png) {% info title="Alternative method to deploy starter template" %} As an alternative to the Appwrite console, you can create and deploy websites using the [Appwrite CLI](/docs/products/sites/deploy-manually#cli). Create your Next.js starter using the following shell command and configuration: ```bash appwrite init sites ? What would you like to name your site? Next.js starter ? What ID would you like to have for your site? unique() ? What framework would you like to use? Next.js (nextjs) ? What specification would you like to use? 0.5 CPU, 512MB RAM ``` You can then make any edits to the website and deploy it using the following command: ```bash appwrite push sites ``` {% /info %} ### Test the starter template After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. You can then either choose to view the site by clicking on the **Visit site** button or view the site configuration (deployments, logs, domains, usage, and settings) by clicking on the **Go to dashboard** button. ![Congratulations](/images/blog/nextjs-starter-sites/congrats.png) ### Next steps And with that, the Next.js starter kit is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. For more information about Appwrite Sites: - [Appwrite Sites product docs](/docs/products/sites) - [Quick start to deploy any Next.js app](/docs/products/sites/quick-start/nextjs) - [Appwrite Discord server](/discord) --- ## What's new in Node.js v25.2: Web Storage, V8 14.1, permissions and more https://appwrite.io/blog/post/nodejs-v25-whats-new Node.js v25.2.1, the current release as of November 17, 2025, represents the latest evolution in the v25 series. This release brings performance improvements, security features, and web standards alignment, though it also demonstrates how the Node.js team handles breaking changes. If you've been following the Node.js ecosystem, you know that odd-numbered releases like v25 aren't designated for long-term support (LTS). But that doesn't make them any less important. These releases serve as a testing ground for features that will eventually stabilize in future LTS versions. In this article, we'll break down what's new in the Node.js v25 series, explore the major features introduced, discuss the localStorage controversy that led to v25.2.1, and examine what these changes mean for your applications. ### V8 14.1 engine upgrade {% #v8-14-1-engine-upgrade %} Node.js v25 ships with **V8 14.1**, Google's JavaScript and WebAssembly engine that powers Chrome. This upgrade brings several performance improvements that directly impact how your code runs. #### Major JSON.stringify improvements One of the most significant improvements in V8 14.1 is the optimization of `JSON.stringify`. If you've ever worked with large objects or arrays that need to be serialized to JSON, you'll appreciate this. According to V8's benchmarks, `JSON.stringify` is now **more than 2x faster** on the JetStream2 benchmark. This matters when you're building APIs that return JSON responses, handling data transformations in serverless functions, or working with any system that relies heavily on JSON serialization. ```javascript const largeObject = { users: Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `User ${i}`, email: `user${i}@example.com`, metadata: { createdAt: new Date(), status: 'active' } })) }; // This operation is now significantly faster in Node.js v25 const jsonString = JSON.stringify(largeObject); ``` #### WebAssembly and JIT pipeline optimizations V8 14.1 also brings ongoing improvements to WebAssembly support and the Just-In-Time (JIT) compilation pipeline. These changes might not be immediately visible in your day-to-day code, but they contribute to better overall runtime performance. ### Native Web Storage API support {% #native-web-storage-api-support %} One of the challenges when building full-stack JavaScript applications has been the gap between browser and server-side APIs. Node.js v25 narrows this gap by enabling **Web Storage API** by default. This means you can now use `localStorage` and `sessionStorage` directly in Node.js, just like you would in the browser. ```javascript // Store data in localStorage localStorage.setItem('user', JSON.stringify({ name: 'Jane Doe' })); // Retrieve data const user = JSON.parse(localStorage.getItem('user')); console.log(user.name); // Output: Jane Doe // Clear storage localStorage.removeItem('user'); ``` #### Why this matters For developers working with frameworks like Next.js, Remix, or Astro, this standardization makes it easier to share code between client and server. You can write utilities that work in both environments without needing to check which context you're in or use different APIs. #### The localStorage controversy Here's where things get interesting. The Web Storage API in Node.js is still marked as **experimental**, and v25.2.0 introduced a change that caused significant problems in the ecosystem. In v25.2.0, Node.js started throwing errors when accessing `localStorage` without a configured storage path. The intent was to align with web specifications, but this broke popular tools like Jest, Vite, and other frameworks that relied on the previous behavior. Developers upgrading to v25.2.0 encountered errors like: ``` SecurityError: Cannot initialize local storage without a `--localstorage-file` path ``` This broke test suites and build processes across the ecosystem. The Node.js team received enough feedback that they decided this change was too breaking for a semver-minor release. **Node.js v25.2.1** (released November 17, 2025) reverted this behavior. The throwing behavior has been postponed to Node.js v26.0.0, giving developers more time to adapt to the change. This episode highlights an important aspect of non-LTS releases: they're testing grounds for features, and sometimes those features need to be adjusted based on real-world feedback. If you're using Web Storage in Node.js v25, it works, but be prepared for stricter behavior in future versions. ### Network permissions with --allow-net {% #network-permissions-with-allow-net %} Node.js has traditionally been permissive when it comes to what your code can access. If you run a script, it can read files, make network requests, and interact with the system without restrictions. This flexibility is convenient, but it also poses security risks, especially when running untrusted code. Node.js v25 expands the **permission model** with a new `--allow-net` flag, giving you finer control over network access. #### How it works The `--allow-net` flag allows you to explicitly grant network permissions to your application. This is similar to Deno's security model, which requires explicit permissions for different operations. ```bash ### Allow network access to specific hosts (requires --permission flag) node --permission --allow-net=api.example.com,cdn.example.com server.js ### Allow all network access (requires --permission flag) node --permission --allow-net server.js ``` This approach encourages **secure-by-default** applications. By requiring explicit permission for network operations, you reduce the risk of unintended behavior or vulnerabilities in dependencies that might make unauthorized network requests. For production systems, multi-tenant environments, or any scenario where you need to limit what your code can do, this is a valuable addition. ### Built-in Uint8Array base64/hex conversion {% #built-in-uint8array-base64-hex-conversion %} Working with binary data has always been a bit cumbersome in JavaScript. Converting between `Uint8Array` and formats like base64 or hex usually required either using the `Buffer` API or pulling in a third-party library. Node.js v25 changes this by adding **built-in methods** to convert `Uint8Array` directly to base64 or hex strings. ```javascript // Create a Uint8Array const data = new Uint8Array([72, 101, 108, 108, 111]); // Convert to base64 const base64 = data.toBase64(); console.log(base64); // Output: SGVsbG8= // Convert to hex const hex = data.toHex(); console.log(hex); // Output: 48656c6c6f ``` #### Practical use cases This is useful when working with: - **File uploads:** Encoding binary data for transmission - **Cryptographic operations:** Converting hash outputs to readable formats - **API integrations:** Many APIs expect base64-encoded binary data - **WebSocket communication:** Encoding binary frames By making these conversions native, Node.js removes the dependency on external libraries and improves performance since these operations are now handled at the engine level. ### WebAssembly JSPI support {% #webassembly-jspi-support %} WebAssembly continues to evolve, and Node.js v25 enables **JavaScript Promise Integration (JSPI)** for WebAssembly modules. JSPI allows synchronous WebAssembly code to interact with asynchronous JavaScript APIs. This is important because WebAssembly is inherently synchronous, but JavaScript's ecosystem is heavily async-based. #### Why this is useful Before JSPI, if you wanted to call an async JavaScript function from WebAssembly, you had to use complex workarounds involving callbacks or state machines. JSPI simplifies this by letting WebAssembly suspend and resume execution while waiting for async operations. This is valuable for: - **High-performance computing:** Running CPU-intensive operations (like scientific simulations or video encoding) in WebAssembly while interacting with async I/O - **Game engines:** Integrating WebAssembly-based game logic with async network calls for multiplayer features or asset loading - **Image and video processing:** Using WebAssembly for fast image manipulation (filters, compression, format conversion) while reading/writing files asynchronously - **Data processing:** Combining WebAssembly's speed for parsing large datasets with JavaScript's async file system or database operations ### Portable compile cache {% #portable-compile-cache %} Node.js compiles JavaScript code to bytecode before executing it. This compilation step takes time, especially for large applications. To speed up subsequent runs, Node.js caches the compiled bytecode. Node.js v25 introduces **portable compile cache**, which allows caches to be reused even when you move your project directory. Previously, compile caches used absolute file paths, meaning if you moved your project or deployed it to a different environment, the cache couldn't be reused. You can enable portable caching in two ways: ```bash ### Enable portable compile cache via environment variable NODE_COMPILE_CACHE_PORTABLE=1 node app.js ``` Or programmatically: ```javascript // Enable portable compile cache via API module.enableCompileCache({ directory: '/path/to/cache/dir', portable: true }); ``` #### Impact on serverless and containers This feature is especially useful for: - **Serverless functions:** Reuse caches across different function invocations - **Docker containers:** Share caches between container builds - **CI/CD pipelines:** Speed up test runs by reusing compilation artifacts ### Type stripping marked as stable {% #type-stripping-marked-as-stable %} Node.js v25.2 marks **type stripping** as stable. This feature allows you to run TypeScript-like syntax directly in Node.js without needing a separate build step. Type stripping removes type annotations from your code at runtime, but it doesn't perform type checking. You still need the TypeScript compiler (`tsc`) for that. ```typescript // This works natively in Node.js v25.2 function greet(name: string): void { console.log(`Hello, ${name}!`); } greet('World'); ``` #### When to use it Type stripping is ideal for: - **Rapid prototyping:** Write TypeScript without setting up a build pipeline - **Small scripts:** Run one-off TypeScript files without compilation - **Learning TypeScript:** Experiment with TypeScript syntax without tooling overhead For production applications, you'll likely still want a proper TypeScript setup with full type checking, but type stripping removes friction for simpler use cases. ### Performance improvements {% #performance-improvements %} Beyond the headline features, Node.js v25.2 includes several performance optimizations: #### Buffer concatenation speedup The implementation of buffer concatenation has been improved using `TypedArray#set`, which reduces the time needed to combine multiple buffers. This is useful when working with streams or assembling data from multiple sources. #### Console logging optimization Single-string logging in the `console` module has been optimized. If you're doing a lot of logging (especially in development), you'll notice faster output. #### Crypto argument validation The crypto module now performs argument validation more efficiently in fast paths, reducing overhead for common cryptographic operations. ### What's specific to v25.2.1 {% #whats-specific-to-v25-2-1 %} While most of the features we've discussed came with v25.0.0 or v25.2.0, v25.2.1 includes a few specific changes worth noting. #### Crypto fix: RSA-PSS saltLength default v25.2.1 fixed an issue where the documented RSA-PSS `saltLength` default wasn't being used correctly. If you're working with RSA-PSS signatures in the crypto module, this ensures the implementation matches the documented behavior. ```javascript const crypto = require('crypto'); // The default saltLength now matches documentation const sign = crypto.createSign('RSA-SHA256'); // ... ``` #### V8 engine backport The release includes a critical V8 engine patch that was backported to improve stability and performance. While the specific details are technical, these backports typically address edge cases or bugs discovered in production environments. #### Documentation clarity on Web Storage Beyond reverting the throwing behavior, v25.2.1 also clarified the experimental status of Web Storage across documentation, source code, and library code. This makes it clearer to developers that while the feature is available, it's still evolving and may change in future releases. ### Deprecated API removals {% #deprecated-api-removals %} Node.js v25 continues the tradition of cleaning up deprecated APIs. Several long-deprecated features have been removed: #### SlowBuffer removal The `SlowBuffer` object has been completely removed due to security vulnerabilities. If you were still using it, you should migrate to `Buffer.allocUnsafeSlow()`. ```javascript // Old (removed in v25) const buf = new SlowBuffer(10); // New const buf = Buffer.allocUnsafeSlow(10); ``` #### Deprecated crypto options Certain crypto options, like default lengths for `shake128` and `shake256`, are now deprecated at runtime and will be removed in future versions. #### Filesystem constants Filesystem permission constants (`fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK`) have been removed. These were already marked as deprecated, and you should use the corresponding constants from the `fs.constants` object instead. ```javascript // Old (removed in v25) fs.access(path, fs.R_OK, callback); // New fs.access(path, fs.constants.R_OK, callback); ``` ### When should you use Node.js v25? {% #when-should-you-use-nodejs-v25 %} Node.js v25 is a **Current** release, not an LTS release. This means it's designed for developers who want to experiment with the latest features and provide feedback, but it's not recommended for production applications that require long-term stability. #### Use Node.js v25 if: - You're building side projects or prototypes - You want to test new features before they land in LTS - You're contributing to the Node.js ecosystem and need to validate changes - You're working on applications that can tolerate breaking changes #### Stick with LTS if: - You're running production applications - You need long-term support and security updates - Your team relies on ecosystem stability - You're working in enterprise environments with strict upgrade policies The next LTS release is expected in October 2026 with Node.js v26, which will incorporate many of the features tested in v25. ### Conclusion Node.js v25.2.1 represents a meaningful step forward for the runtime, though it also demonstrates the value of iterative development. The combination of performance improvements, security features, and web standards alignment shows that Node.js continues to evolve in response to developer needs. While v25 isn't an LTS release, it gives us a preview of what's coming and an opportunity to provide feedback before these features stabilize. Whether you're working with high-performance JSON operations, building cross-platform applications that benefit from Web Storage, or experimenting with WebAssembly, there's something here worth exploring. If you're interested in learning more about modern JavaScript runtimes, you might enjoy reading about [Deno vs Bun](https://appwrite.io/blog/post/deno-vs-bun-javascript-runtime) or exploring [how Deno 2.0 works with Appwrite Functions](https://appwrite.io/blog/post/deno-2-appwrite-functions). You can reach out to us on [Discord](https://appwrite.io/discord) if you have any questions or feedback. ### More resources - [Node.js v25.2.1 release notes](https://nodejs.org/en/blog/release/v25.2.1) - [Node.js v25.2.0 release notes](https://nodejs.org/en/blog/release/v25.2.0) - [Why you need to try the new Bun function runtime](/blog/post/why-you-need-to-try-the-new-bun-runtime) - [Announcing Deno support on Appwrite Cloud](/blog/post/deno-runtime-announcment) --- ## How to setup the Nuxt starter template on Appwrite Sites https://appwrite.io/blog/post/nuxt-starter-sites Building web applications requires both front-end expertise and back-end infrastructure. [Appwrite Sites](/products/sites) simplifies this process by providing a platform for deploying, hosting, and scaling web applications. To ease this process even further, Appwrite Sites offers a variety of starter kits for popular frameworks like Next.js, React, Vue, Nuxt, Angular, SvelteKit, and Flutter. In this blog, you will learn how to set up the Nuxt starter template and deploy it to Appwrite Sites. ### Overview of the starter template Nuxt is a framework built on top of Vue.js that makes it easy to develop server-rendered, statically generated, or single-page applications with features like routing, SEO optimization, and modular architecture out of the box. Appwrite's Nuxt starter template includes: - A clean, single-page UI - Integration with Appwrite's SDK - Pre-configured deployment settings for Appwrite Sites' SSR rendering strategy ![Deployed app](/images/blog/nuxt-starter-sites/deployed.png) ### Deploy the starter template on Appwrite Firstly, you must head to Appwrite Cloud and [create an account](https://cloud.appwrite.io/console/register) if you haven't already (or [self-host Appwrite 1.7](/docs/advanced/self-hosting)). Next, create your first project, which will lead you to the project overview page. ![Add platform](/images/blog/nuxt-starter-sites/add-platform.png) Head to the **Sites** page from the left sidebar, click on the **Create site** button, and select the **Clone a template** option. This will take you to the Appwrite Sites templates listing, where you should search `Nuxt starter` and click on the template. ![Starter template](/images/blog/nuxt-starter-sites/template.png) After selecting the template, you can choose to connect a GitHub repository now or at a later time. If you choose to connect a repository, ensure you select a production branch (leave the root directory as is). Then, review the preset environment variables, update the domain name if you want, and click on the **Deploy** button. You can watch the deployment logs as the site is built. ![Deployment logs](/images/blog/nuxt-starter-sites/deployment-logs.png) {% info title="Alternative method to deploy starter template" %} As an alternative to the Appwrite console, you can create and deploy websites using the [Appwrite CLI](/docs/products/sites/deploy-manually#cli). Create your Nuxt starter using the following shell command and configuration: ```bash appwrite init sites ? What would you like to name your site? Nuxt starter ? What ID would you like to have for your site? unique() ? What framework would you like to use? Nuxt (nuxt) ? What specification would you like to use? 0.5 CPU, 512MB RAM ``` You can then make any edits to the website and deploy it using the following command: ```bash appwrite push sites ``` {% /info %} ### Test the starter template After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. You can then either choose to view the site by clicking on the **Visit site** button or view the site configuration (deployments, logs, domains, usage, and settings) by clicking on the **Go to dashboard** button. ![Congratulations](/images/blog/nuxt-starter-sites/congrats.png) ### Next steps And with that, the Nuxt starter kit is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. For more information about Appwrite Sites: - [Appwrite Sites product docs](/docs/products/sites) - [Quick start to deploy any Nuxt app](/docs/products/sites/quick-start/nuxt) - [Appwrite Discord server](/discord) --- ## Understanding OAuth and OpenID Connect https://appwrite.io/blog/post/oauth-openid When it comes to web security, **OAuth** and **OpenID Connect (OIDC)** have revolutionized how we manage and secure user authentication and authorization. OAuth, primarily a protocol for authorization, enables third-party access to user data without exposing their credentials. On the other hand, OIDC, built on top of OAuth 2.0, extends this protocol by adding user authentication. These technologies provide a secure, efficient way for users to log in and share information across different services without compromising their privacy or security. ### What is OAuth? OAuth is an open standard for access delegation commonly used as a way for internet users to grant websites or applications access to their information on other websites without giving them the actual user passwords. It's particularly useful for providing controlled access to resources hosted by a third-party service. OAuth 2.0, the latest version, simplifies the earlier protocol, offering improved security and interoperability. #### The OAuth 2.0 authorization flow 1. **User authorization request**: The process begins when a user tries to access a resource or service that requires them to log in via a third-party service (like logging into a website using their Google account). The website (client) redirects the user to log in to the third-party service (authorization server). 2. **User login**: The user logs in to the third-party service. This step involves user authentication by the third-party service, but this authentication process is not part of OAuth—it's handled entirely by the third-party service. 3. **Grant access**: After successful login, the third-party service asks the user if they want to grant the requesting website access to their information. If the user consents, the third-party service (authorization server) redirects the user back to the website with an authorization code. 4. **Access token request**: The website (now having the authorization code) makes a separate request to the third-party service, exchanging the authorization code for an access token. 5. **Access token response**: The third-party service responds with an access token (and sometimes a refresh token). 6. **Resource access**: The website uses this access token to make requests to the third-party service’s resource server to access the user’s information. The resource server validates the access token and returns the requested resource. #### Use cases of OAuth 2.0 OAuth 2.0 is widely used for secure, delegated access across various applications. Some of its use cases include: - **Social media logins**: Allowing users to log in to websites and apps using their social media accounts. - **Third-party application access**: Enabling apps to access services like email or calendars on behalf of the user. - **Single Sign-On (SSO)**: Facilitating one-login access to multiple enterprise applications. - **Payment gateway access**: Letting third-party vendors access transaction details or initiate payments via services like PayPal. - **Mobile app integrations**: Connecting mobile apps with other services and platforms for enhanced functionality. ### What is OpenID Connect? OpenID Connect is a simple identity layer built on top of OAuth 2.0. It allows clients to verify the user's identity based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the user in an interoperable way. OIDC uses OAuth 2.0 as its foundation. While OAuth 2.0 provides a framework for authorization, OIDC extends this to include authentication, delivering a more holistic security solution. The core components of OpenID Connect include the ID token (a JWT that contains information about the user's session and authentication information), the User Info endpoint (to fetch user profile data), and the Discovery document (a JSON document listing crucial OIDC endpoints). The authentication process typically involves redirecting the user to an Authorization Server, logging in, and then being redirected back to the application with an ID Token and Access Token. ### Comparing OAuth and OpenID Connect OAuth 2.0 and OpenID Connect are closely related standards used in online identity and access management, but they serve different purposes. Here are some key differences: - **Primary focus**: - **OAuth 2.0** is a framework for authorization. It allows applications to obtain limited access to a user's data on another application or service. - **OpenID Connect** is built on top of OAuth 2.0 and is specifically designed for authentication. It allows applications to verify the identity of a user and obtain basic profile information. - **Token usage**: - **OAuth 2.0** provides access tokens, which are used by applications to access APIs on behalf of the user. - **OpenID Connect** provides an ID Token in addition to the access token. The ID Token is a JWT (JSON Web Token) used for identity verification and contains user profile information. - **User information**: - **OAuth 2.0** doesn't standardize (or provide) information about the user. It focuses on the scopes and permissions given to the access token. - **OpenID Connect** standardizes the way applications can receive identity information about the user in a secure and RESTful manner. - **Scenarios**: - **OAuth 2.0** is used when an application needs to perform actions or access resources from another service on behalf of the user without needing to know the user's identity. - **OpenID Connect** is used when an application needs to authenticate the user and potentially access basic profile information. - **Compatibility and extension**: - **OAuth 2.0** serves as a base for various protocols, including OpenID Connect. - **OpenID Connect** is an extension of OAuth 2.0, adding an additional layer specifically for user authentication. #### Security best practices Implementing OAuth and OIDC securely involves using secure channels for all communications, validating all tokens, employing state parameters to mitigate CSRF attacks, and regularly updating and auditing your security configurations. Here are some best practices for ensuring security in OAuth and OIDC implementations: - **Use HTTPS**: Always use HTTPS to protect the data in transit against interception and tampering. This is essential for all communications in OAuth and OpenID Connect flows. - **Validate redirect URIs**: Strictly validate redirect URIs to prevent redirection attacks. Ensure that the redirect URI in an authorization request matches the one registered with the authorization server. - **Use 'state' parameter**: Implement the 'state' parameter in OAuth flows to mitigate Cross-Site Request Forgery (CSRF) attacks. This parameter should be a random value that is validated at the beginning and end of the flow. - **Utilize PKCE (Proof Key for Code Exchange)**: Especially in mobile and single-page applications, use PKCE to enhance the security of the authorization code flow. - **Secure client credentials**: Store client credentials securely. Avoid exposing client secrets in client-side code. - **Token security**: Use access tokens and ID tokens securely. Avoid storing tokens in insecure locations. Implement token expiration and refresh token rotation. - **Validate ID tokens**: When using OpenID Connect, properly validate the ID token, especially its signature and the issuer. - **Implement scopes and consent appropriately**: Define scopes clearly and request only the permissions that are necessary. Always get user consent where applicable. ### Using OAuth and OpenID Connect with Appwrite Appwrite Authentication features support for over 30 OAuth 2.0 adapters, including a generic OIDC adapter, out of the box for developers to implement. To set up the generic OIDC adapter, you need the following details: - Client ID - Client Secret - Well-Known Endpoint - Authorization Endpoint - Token Endpoint - User Info Endpoint Appwrite provides the necessary redirect URL to add to your OIDC app configuration. Once that is done, you can use any of the Appwrite client SDKs to implement OAuth 2.0 or OIDC authentication in just a few lines of code. ```client-web import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); account.createOAuth2Session({ provider: OAuthProvider.Oidc, success: '', failure: '' }); ``` To learn more about Appwrite and OAuth 2.0, ensure you visit the [Appwrite documentation](https://appwrite.io/docs/products/auth/oauth2). --- ## Build an offline-first journal app with RxDB and Appwrite https://appwrite.io/blog/post/offline-first-journal Ever found yourself staring at a loading spinner in a no-network zone, wishing your app could just work offline? Whether you're building a journal, a field notes app, or anything in between, offline data synchronization is no longer a luxury but a necessity. There is some good news, though. If you’re building with JavaScript, Appwrite Databases now features a direct integration with RxDB, making it easier than ever to build real-world apps that stay in sync online and offline. ### What is RxDB? RxDB (Reactive Database) is a local-first NoSQL database designed for JavaScript-based web and mobile applications. What sets RxDB apart is its reactive, offline-first architecture, making it ideal for apps that need to store and sync data locally, especially when internet connectivity is spotty or intermittent. #### Key features of RxDB - **Local-first storage**: RxDB stores data locally using browser-compatible storage engines like IndexedDB, SQLite, and even filesystem-based storage on mobile platforms. This makes it perfect for apps that need to function completely offline. - **Reactive data streams**: Built on top of RxJS, RxDB turns queries into live data streams. That means when the underlying data changes (locally or remotely), your UI updates automatically in real-time. There is no polling, no refreshing, just smooth reactivity. - **Seamless replication**: RxDB supports push-pull replication with various remote databases, including Appwrite Databases. This allows two-way syncing: it pushes local changes to a backend and pulls new changes down. - **Security and extensibility**: RxDB comes with optional encryption, schema validation, conflict resolution strategies, and plugins for custom behaviors like attachments, migrations, and leader election in multi-tab apps. - **Cross-platform support**: It runs smoothly in browsers, PWAs, React Native, Electron, and other environments, making it a versatile choice for building cross-platform apps with consistent offline sync logic. #### Synchronize data between RxDB and Appwrite Databases RxDB has recently introduced a plugin that allows developers to replicate data to Appwrite Databases, meaning all your client app’s locally stored data can be synchronized. Since RxDB stores all data locally, your app can continue to function with zero internet, and information is synced to an external Appwrite database as soon as connectivity returns. Additionally, Appwrite’s Realtime API and RxDB’s live replication allow data to be instantaneously updated across multiple clients. ### Building an offline-first journal app To understand how to build an offline-first application with RxDB and Appwrite, let’s build a journal app. Our app will be a simplified version of the [demo app](https://offline-journal.vercel.app/) shown below. ![Demo app](/images/blog/offline-first-journal/demo.png) Our tech stack for this app will be: - **SvelteKit**, configured as a Progressive Web App (PWA) - **IndexedDB**, to store local data - **Appwrite Cloud**, for external replication #### Configure your Appwrite project First, [create an Appwrite Cloud account](https://cloud.appwrite.io/) if you haven’t already. Once your project is ready, go to the **Settings** page and copy your project ID and API endpoint for further usage. Next, go to the **Databases** page from the left sidebar, create a new database with the ID `journals`, and then a collection with the ID `entries` (save both IDs for further usage). Next, head to the **Attributes** tab and add the following: | Key | Type | Size | Required | | --- | --- | --- | --- | | `title` | String | 100 | Yes | | `content` | String | 20000 | Yes | | `createdAt` | Integer | | Yes | | `updatedAt` | Integer | | Yes | | `deleted` | Boolean | | Yes | > **Note:** The `deleted` attribute is necessary to add because RxDB does not hard delete any data, only soft deletes to prevent data loss in offline scenarios. Then, head to the **Settings** tab of your collection, scroll down to the **Permissions** section, and add the following: | Role | Create | Read | Update | Delete | | --- | --- | --- | --- | --- | | Any | Yes | Yes | Yes | Yes | #### Prepare the app logic Once our Appwrite project is set up, let’s start building our app. ##### Create a SvelteKit app To create the SvelteKit app, open up your terminal and run the following command: ```sh npx sv create ``` This will load the Svelte CLI, where you can enter the following inputs to create a minimal app: - Where would you like your project to be created? **>** `offline-journal` - Which template would you like? **>** `SvelteKit minimal` - Add type checking with TypeScript? **>** `No` - What would you like to add to your project? **>** `prettier, eslint` - Which package manager do you want to install dependencies with? **>** `npm` Once that is done, enter the app’s working directory and install all dependencies by running the following commands: ```sh cd offline-journal npm install ``` ##### Install the Appwrite Web SDK Now, that the SvelteKit app is created, install the Appwrite Web SDK by running the following command: ```sh npm install appwrite ``` In the root directory of your app, create a `.env` file and add the information you saved from your Appwrite project: ```env PUBLIC_APPWRITE_ENDPOINT=your-appwrite-cloud-endpoint PUBLIC_APPWRITE_PROJECT_ID=your-project-id PUBLIC_APPWRITE_DATABASE_ID=your-database-id PUBLIC_APPWRITE_COLLECTION_ID=your-collection-id ``` Next, in the `src/lib` subdirectory, create a file `appwrite.js` and add the following code: ```js import { Client } from 'appwrite'; import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID, PUBLIC_APPWRITE_DATABASE_ID, PUBLIC_APPWRITE_COLLECTION_ID } from '$env/static/public'; export const appwriteConfig = { endpoint: PUBLIC_APPWRITE_ENDPOINT, projectId: PUBLIC_APPWRITE_PROJECT_ID, databaseId: PUBLIC_APPWRITE_DATABASE_ID, collectionId: PUBLIC_APPWRITE_COLLECTION_ID }; export const client = new Client() .setEndpoint(appwriteConfig.endpoint) .setProject(appwriteConfig.projectId); ``` ##### Setup RxDB To set up RxDB, first install the RxDB library in your app by running the following command: ```sh npm install rxdb ``` Next, in the `src/lib` directory, create a files `databases.js` and add the following imports: ```js // RxDB imports import { createRxDatabase, addRxPlugin, RxCollectionBase } from 'rxdb/plugins/core'; import { RxDBQueryBuilderPlugin } from 'rxdb/plugins/query-builder'; import { RxDBUpdatePlugin } from 'rxdb/plugins/update'; import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; import { replicateAppwrite } from 'rxdb/plugins/replication-appwrite'; // Appwrite imports import { ID } from 'appwrite'; import { client, appwriteConfig } from './appwrite.js'; addRxPlugin(RxDBQueryBuilderPlugin); addRxPlugin(RxDBUpdatePlugin); ``` The RxDB imports include core RxDB functionalities to create databases and collections and to add plugins, the query builder plugin for complex read queries, the update plugin for updating data, the Dexie.js storage plugin to use IndexedDB as the local database, and the Appwrite replication plugin to manage data replication in the external Appwrite database. ##### Create a local database To create a local database, first, we must prepare the database schema. To do so, add the following code in the `databases.js` file: ```js const journalSchema = { title: 'journal entry schema', version: 0, primaryKey: 'id', type: 'object', properties: { id: { type: 'string', maxLength: 100 }, title: { type: 'string' }, content: { type: 'string' }, createdAt: { type: 'number' }, updatedAt: { type: 'number' } }, required: ['id', 'title', 'content', 'createdAt', 'updatedAt'] }; ``` Then, we create the database and collection using the Dexie.js plugin by adding the following code just after the schema: ```js let dbPromise = null; export const getDB = async () => { if (dbPromise) return dbPromise; try { // Create the database dbPromise = createRxDatabase({ name: 'journals', // Name must match the database ID from Appwrite storage: getRxStorageDexie() }); const db = await dbPromise; // Add collections await db.addCollections({ entries: { // Name must match the collection ID from Appwrite schema: journalSchema } }); // Set up replication setupReplication(db); return db; } catch (error) { console.error('Database creation error:', error); throw error; } }; ``` ##### Setup data replication To setup replication in the Appwrite database, add the following code to the `databases.js` file: ```js let replicationState = null; const setupReplication = async (db) => { try { // Start replication replicationState = replicateAppwrite({ replicationIdentifier: 'journals-replication', client, databaseId: appwriteConfig.databaseId, collectionId: appwriteConfig.collectionId, deletedField: 'deleted', collection: db.entries, pull: { batchSize: 25 // Can be updated }, push: { batchSize: 25 // Can be updated } }); // Handle replication events replicationState.error$.subscribe((error) => { console.error('Replication error:', error); }); replicationState.active$.subscribe((active) => { }); return replicationState; } catch (error) { console.error('Replication setup error:', error); } }; ``` ##### Add database operations Lastly, add the following helper functions for different database operations in the `databases.js` file: ```js export const getJournals = async () => { const db = await getDB(); return db.entries.find().sort({ updatedAt: 'desc' }).exec(); }; export const getJournal = async (id) => { const db = await getDB(); return db.entries.findOne({ selector: { id } }).exec(); }; export const createJournal = async (journalData) => { const db = await getDB(); const timestamp = Date.now(); return db.entries.insert({ id: ID.unique(), createdAt: timestamp, updatedAt: timestamp, ...journalData }); }; export const updateJournal = async (id, journalData) => { const db = await getDB(); const journal = await getJournal(id); if (!journal) throw new Error('Journal entry not found'); return journal.update({ $set: { ...journalData, updatedAt: Date.now() } }); }; export const deleteJournal = async (id) => { const db = await getDB(); const journal = await getJournal(id); if (!journal) throw new Error('Journal entry not found'); return journal.remove(); }; ``` #### Develop the UI Now that our database library functions are set up, let’s create all journal-related pages. > **Note:** To maintain conciseness, we will skip all styling-related CSS. You can find examples of the same in our [demo app’s GitHub repo](https://github.com/appwrite-community/offline-journal/tree/main/src). ##### List all journal entries We will list all journal entries on the index page of the app. Head to the `src/routes` directory, create a `+page.js` file and add the following code: ```js import { getJournals } from '$lib/database'; /** @type {import('./$types').PageLoad} */ export async function load({ url }) { let journals = null; try { journals = await getJournals(); } catch (err) { console.error('Error fetching journals:', err); journals = []; } return { journals } } ``` This will pre-load all journal entries before the page renders. Then, open the `+page.svelte` file and edit it to the following code: ```html Journal App

    My Journal

    {#if error}

    {error}

    {/if} {#if journals.length === 0}

    You don't have any journal entries yet.

    Create your first entry
    {:else}
    {#each journals as journal (journal.id)}

    {journal.title}

    View Edit
    {#if journal.content.length > 150}

    {journal.content.substring(0, 150)}...

    {:else}

    {journal.content}

    {/if}
    {/each}
    {/if}
    ``` This page will trigger the creation of a local database the first time it is launched and set up replication with Appwrite. All existing journal entries will then be loaded from IndexedDB and rendered as cards on the page. Each journal entry card allows you to access pages, view, edit, or delete an entry from the database. The page will also allow you to create a new journal entry. ##### View a journal entry In the `src/routes` directory, create a subdirectory `journal`, within which you must create another subdirectory `[id]`, and add a `+page.svelte` file with the following code: ```html {journal ? journal.title : 'Loading...'} | Journal App
    ← Back to Journal
    {#if error}

    {error}

    {/if} {#if loading}
    Loading...
    {:else if journal}

    {journal.title}

    Edit
    Created: {formatDate(journal.createdAt)} Updated: {formatDate(journal.updatedAt)}

    {journal.content}

    {:else}

    Journal entry not found

    Return to Journal
    {/if}
    ``` When accessing this page, the `[id]` in the URL acts as a slug for fetching data pertaining to a specific journal entry and rendering it on the page. ##### Edit a journal entry In the `src/routes/journal/[id]` directory, create a subdirectory `edit`, and add a `+page.svelte` file with the following code: ```html Edit Journal Entry | Journal App

    Edit Journal Entry

    ← Back to Entry
    {#if error}

    {error}

    {/if} {#if loading}

    Loading...

    {:else if journal}
    {:else}

    Journal entry not found

    Return to Journal
    {/if}
    ``` This page will load the data for a specific journal entry and allow the user to edit its title and content. Saving the edited content will also update the entry's “updated at” time. ##### Add a new journal entry In the `src/routes/journal` directory, create a subdirectory `new`, and add a `+page.svelte` file with the following code: ```html New Journal Entry

    New Journal Entry

    ← Back to Journal
    {#if error}

    {error}

    {/if}
    ``` This page features a form that would allow the user to add a new journal entry to the database. #### Configure app as a PWA For easier offline usage, let’s configure the web app to work as a PWA to offer an offline-first experience. For those who aren’t aware, a PWA or Progress Web App is a type of web application that can be installed on a device as a standalone app, offering a native-like experience. To configure our web app as a PWA, you must follow four steps. ##### Create a manifest.json file In the `static/` directory, create a new `manifest.json` file and add the following code: ```json { "name": "Offline Journal", "short_name": "Journal", "description": "A private offline-first journaling application", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#4a76a8", "icons": [ { "src": "icons/icon-192-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, { "src": "icons/icon-512-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } ] } ``` ##### Add app icons In the `static/` directory, create a subdirectory `icons/`and add two icons files of the sizes `192px x 192px` and `512px x 512px`. You can [download our demo app’s icons](https://github.com/appwrite-community/offline-journal/tree/main/static/icons) from our GitHub repo as a placeholder. Ensure that the file names for both images comply with those in the `manifest.json` file. ##### Link manifest file in the app.html file In the `src/` directory, open the `app.html` file and add the following code within the `` tags: ```html ``` ##### Create a service worker In the `src/` directory, create a subdirectory `service-worker`, and add a file `index.js` with the following code: ```js /// // @ts-nocheck import { build, files, version } from '$service-worker'; // Create a unique cache name for this deployment const CACHE = `cache-${version}`; const ASSETS = [ ...build, // the app itself ...files // everything in `static` ]; self.addEventListener('install', (event) => { // Create a new cache and add all files to it async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); } event.waitUntil(addFilesToCache()); }); self.addEventListener('activate', (event) => { // Remove previous cached data from disk async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } } event.waitUntil(deleteOldCaches()); }); self.addEventListener('fetch', (event) => { // ignore POST requests etc if (event.request.method !== 'GET') return; async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE); // `build`/`files` can always be served from the cache if (ASSETS.includes(url.pathname)) { return cache.match(url.pathname); } // for everything else, try the network first, but // fall back to the cache if we're offline try { const response = await fetch(event.request); if (response.status === 200) { cache.put(event.request, response.clone()); } return response; } catch { return cache.match(event.request); } } event.respondWith(respond()); }); ``` #### Test the app To locally deploy and test the app, run the following command in your terminal: ```sh npm run dev ``` You can then visit `https://localhost:5173` in your browser and try out the app. ### Next steps And with that, our offline-first journal app built with RxJS and SvelteKit is ready! We developed a more complex version of this app, featuring an authentication implementation and better styling, and deployed it publicly to try out: https://offline-journal.vercel.app/ You can find the source code for this application in our [GitHub repo](https://github.com/appwrite-community/offline-journal). Learn more about RxDB and Appwrite: - [RxDB docs for Appwrite](https://rxdb.info/replication-appwrite.html#do-other-things-with-the-replication-state) - [Appwrite offline sync docs](/docs/products/databases/offline) - [RxDB in the Integrations catalog](/integrations/replication-rxdb) --- ## Celebrating the open source contributors for Appwrite 1.6 https://appwrite.io/blog/post/open-source-contributors-16 This week, we announced the brand new 1.6 version, which features major updates to the Functions ecosystem, including local development, an updated CLI, support for Go, and more. And, just like every major release before that, Appwrite 1.6 is a testament to the power of open-source collaboration. With over 40 developers contributing, we've been able to push for a faster, better, smoother product for everyone in the community. We're incredibly grateful for the dedication and hard work of our community. Every contribution, big or small, has made a significant impact. Therefore, we’d like to conclude Init by celebrating the people who made 1.6 possible — our open-source contributors. Thank you for helping us build a better Appwrite. ### Thanks to all the contributors We appreciate the work of everyone involved, from coding and translations to documentation. We’d like to extend our deepest thanks to: - [@ItzNotABug](https://github.com/ItzNotABug) who: - added a theme on SPA Init in [#1190](https://github.com/appwrite/console/pull/1190) - fixed redirecting back to the dashboard during password recovery in [#1188](https://github.com/appwrite/console/pull/1188) - fixed PNPM commands in the Contributing guide in [#1256](https://github.com/appwrite/console/pull/1256) - removed unnecessary bullet point marker in [#1240](https://github.com/appwrite/console/pull/1240) - fixed the tooltip location in [#1275](https://github.com/appwrite/console/pull/1275) - fixed input label alignment in [#1234](https://github.com/appwrite/console/pull/1234) - fixed cancel icon shifting in [#1207](https://github.com/appwrite/console/pull/1207) - fixed failing `GitHub` install on preview instances in [#1282](https://github.com/appwrite/console/pull/1282) - removed unnecessary plus icon in [#1283](https://github.com/appwrite/console/pull/1283) - deleted expired tags per project in [#8239](https://github.com/appwrite/appwrite/pull/8239) - added tests to validate headers aren’t being overridden in [#8228](https://github.com/appwrite/appwrite/pull/8228) - added `default` to collection attributes in [#8271](https://github.com/appwrite/appwrite/pull/8271) - fixed document APIs that don't support redirects in [#8233](https://github.com/appwrite/appwrite/pull/8233) - fixed the domain check in [#8472](https://github.com/appwrite/appwrite/pull/8472) - updated Appwrite versions in [#884](https://github.com/appwrite/sdk-generator/pull/884) - [@blackviking27](https://github.com/blackviking27) who fixed the wizard footer overlapping with the language indicator in [#1152](https://github.com/appwrite/console/pull/1152) - [@iamAyushChamoli](https://github.com/iamAyushChamoli) who fixed syntax for Android in documentation in [#1238](https://github.com/appwrite/console/pull/1238) - [@Anush008](https://github.com/Anush008) who - added the sync-with-qdrant template for JavaScript in [#288](https://github.com/appwrite/templates/pull/288) - added the sync-with-qdrant template for TypeScript in [#290](https://github.com/appwrite/templates/pull/290) - added the sync-with-qdrant template for Bun in [#291](https://github.com/appwrite/templates/pull/291) - added the sync-with-qdrant template for Python in [#289](https://github.com/appwrite/templates/pull/289) - [@navjotNSK](https://github.com/navjotNSK) who added Hong Kong in translations by in [#8179](https://github.com/appwrite/appwrite/pull/8179) - [@2002Bishwajeet](https://github.com/2002Bishwajeet) who fixed setting the target field if the existing target document is false by in [#8236](https://github.com/appwrite/appwrite/pull/8236) - [@Achraf112Ben](https://github.com/Achraf112Ben) who added Darija (Moroccan Arabic) translation file in [#7501](https://github.com/appwrite/appwrite/pull/7501) - [@xuelink](https://github.com/xuelink) who removed unused import in templates in [#283](https://github.com/appwrite/templates/pull/283) - [@Snehasis4321](https://github.com/Snehasis4321) who fixed push notifications not working for node in [#282](https://github.com/appwrite/templates/pull/282) - [@kunalArya1](https://github.com/kunalArya1) who fixed variable names and logging in Web and React Native docs templates in [#890](https://github.com/appwrite/sdk-generator/pull/890) - [@MarkPerkins](https://github.com/MarkPerkins) who removed force unwrapping of incoming URL parameters in [#887](https://github.com/appwrite/sdk-generator/pull/887) - [@naman1608](https://github.com/naman1608)  who improved inline docs for Android SDK templates in [#873](https://github.com/appwrite/sdk-generator/pull/873) - [@svenopdehipt](https://github.com/svenopdehipt) who updated the Flutter code to use `js_interop` in [#810](https://github.com/appwrite/sdk-generator/pull/810) - [@us3r001](https://github.com/us3r001) who fixed the `jsonSerialize` return type in PHP query template in [#869](https://github.com/appwrite/sdk-generator/pull/869) - [@Suven-p](https://github.com/Suven-p) who updated the Go client in [#647](https://github.com/appwrite/sdk-generator/pull/647) - [@Im-Madhur-Gupta](https://github.com/Im-Madhur-Gupta) who added inline doc comments to the Web SDK template  in [#721](https://github.com/appwrite/sdk-generator/pull/721) - [@adarshjhaa100](https://github.com/adarshjhaa100) who created a hint to run Init and deploy commands in [#720](https://github.com/appwrite/sdk-generator/pull/720) - [@zlmr](https://github.com/zlmr) who: - fixed closing Android realtime socket properly on unsubscribe in [#814](https://github.com/appwrite/sdk-generator/pull/814) - fixed Apple realtime socket cleanup in [#815](https://github.com/appwrite/sdk-generator/pull/815) - [@StarZeus](https://github.com/StarZeus) who helped fix an Appwrite CLI login issue regarding the setting of cookies in [#802](https://github.com/appwrite/sdk-generator/pull/802) - [@theemaster](https://github.com/theemaster) who updated .NET SDK to allow null value in user model in [#800](https://github.com/appwrite/sdk-generator/pull/800) - [@KillTrot](https://github.com/KillTrot)  who changed `GetValues` to `TryGetValues` when parsing Content-Type header in the .NET SDK in [#806](https://github.com/appwrite/sdk-generator/pull/806) - [@svenopdehipt](https://github.com/svenopdehipt)  who added support for vision OS and fixed warnings & errors in [#777](https://github.com/appwrite/sdk-generator/pull/777) ### How you can contribute Open source is at the core of Appwrite. We believe in building in the open, sharing our code, and welcoming contributions from the community. Many of our engineers came from the open-source community at Appwrite and stayed for the long run. If you would like to play a more substantial role in the development of Appwrite, here are some ways you can make a difference: - Share your suggestions in the [issues](https://github.com/appwrite/appwrite/issues) listed on our GitHub repo. - Make bug fixes and feature contributions by creating [pull requests](https://github.com/appwrite/appwrite/pulls). - Raise feedback and new ideas about Appwrite in our [GitHub Discussions](https://github.com/appwrite/appwrite/discussions). - Participate with other Appwrite developers in our [Discord community](https://appwrite.io/discord). - Share your Appwrite project with our community on the [Built With Appwrite](https://builtwith.appwrite.io/) platform. - Add any Appwrite-related educational content on our [awesome-appwrite](https://github.com/appwrite/awesome-appwrite) repo. Together, let’s continue to build Appwrite through the power of open-source, and help every developer build like a team of hundreds. --- ## Appwrite vs Firebase: An open source alternative for Firebase https://appwrite.io/blog/post/open-source-firebase-alternative Updated on October 6, 2025 If you are looking to build a mobile app, website, tool, or any other application that needs a backend, then you also know the daunting tasks that await. This is probably what brought you to this blog in the first place: looking for a solution to take care of your backend. BaaS provides pre-built backend infrastructure and services to simplify app development, handling server-side tasks like data storage, user management, APIs, server maintenance, security, database management, and more. Two of these services are Firebase and Appwrite. Appwrite and Firebase are both solid options to choose as the Backend-as-a-Service (BaaS) for your app. However, their feature sets can vary substantially. In this article, we will give you a rundown of Appwrite and Firebase to understand how each provider will fit your specific needs. ### Appwrite - Open-source backend platform In 2019, [Appwrite](/) started as an open-source project to make software development more accessible and enjoyable. Appwrite is an open-source, all-in-one cloud development platform that offers built-in backend infrastructure and web hosting, all from a single place. You can build your entire backend within minutes and deploy effortlessly, adding Authentication, Databases, Functions, Storage, Messaging, and more to your projects using the frameworks and languages of your choice. You can self-host Appwrite on your server or utilize [Appwrite Cloud](https://cloud.appwrite.io/). Appwrite is known for its flexibility, security, and extensibility, enabling you to customize backend logic and build applications tailored to your needs. You can view Appwrite’s source code on [GitHub](https://github.com/appwrite/appwrite). ### Firebase - Google-owned BaaS Firebase was founded in 2011 as a real-time chat API. It gradually evolved into a real-time database and later pivoted to the backend-as-a-service it is today. It offers various services, including real-time databases, cloud hosting, user authentication, cloud functions, analytics, and more. Firebase has been well-known and widely used in the market as the only BaaS for a long time. ### Features compared When looking into a BaaS solution, it is good to understand how the products and features can serve your project’s needs. We compiled a list of similarities and differences between Appwrite and Firebase for you to understand both offerings better. #### Authentication Authentication is the process of verifying a user's identity. This is typically done by providing agreed-upon credentials, which are pieces of information shared between the user and the system. There are different authentication methods, including email and passwords, phone verification, or applications that offer single sign-on (SSO) capabilities. It is usually the initial step in application development for developers. BaaS platforms simplify this process by offering SDKs and APIs. *Similarities:* - Both Appwrite and Firebase offer comprehensive user authentication and authorization capabilities. - Both use industry-leading hashing algorithms to protect user passwords. - They provide various authentication methods, including email/password, social login, OTP-based login, and JWTs. - Both platforms support different user roles and permissions for granular access control. *Differences:* - Appwrite's authentication system allows developers to integrate with any existing external authentication systems through [custom token login](/docs/products/auth/custom-token), which allows for various new use cases such as passkey authentication. It also allows for password [history](/docs/products/auth/security#password-history) and [dictionary](/docs/products/auth/security#password-dictionary), [session limits](/docs/products/auth/security#session-limits), and [personal data](/docs/products/auth/security#personal-data) checks on your passwords. - Firebase offers a user-friendly authentication system that seamlessly integrates with other Firebase services, providing developers with a streamlined experience. #### Database A database is an organized collection of information stored in a way that allows for easy access, retrieval, management, and updating. Databases store and manage large volumes of structured and unstructured data, supporting activities such as data storage, analysis, and management. They are integral to any application. Both Appwrite and Firebase provide databases that can be used depending on the needs of your project. *Similarities:* - Both Appwrite and Firebase offer managed databases on their cloud offering. - Both platforms support real-time data updates for reactive applications. *Differences:* - Appwrite's database offers an abstraction layer on top of MariaDB, allowing developers to develop custom schema and use queries without any pre-existing SQL knowledge. Also, you can [configure permissions](/docs/advanced/platform/permissions#appwrite-resource) in Appwrite within the Console, making this an easy task. - Firebase provides a NoSQL, document-oriented database called Cloud Firestore for storing and managing data in a non-relational manner. To manage permissions, they offer a different product called Security Rules, which allows more flexibility but increases the learning curve. #### Storage Storage allows you to manage files in your project. It can store images, videos, documents, and other project files. Storage is a crucial requirement for every application, and here's what Appwrite and Firebase have to offer: *Similarities:* - Both Appwrite and Firebase provide cloud storage for storing files and media content. - They offer both scalable object storage and structured file storage options. - Both platforms provide permissions systems for secure access controls. *Differences:* - Firebase's storage system is a direct extension of Google Cloud Storage and has additional out-of-the-box integrations such as image filtering and video transcoding. - Appwrite Storage offers a dedicated API for [image transformations](/docs/products/storage/images), including functionalities such as manipulating resolution, image reformatting, caching, and compression. - Appwrite Storage offers file encryption for increased security as well as gzip and zstd compression for network transfer optimization. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Start for free" point2="Open source" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/" /%} #### Functions Functions are "self-contained" modules of code that accomplish a specific task. BaaS platforms make it easier for you to extend the business logic by using functions with code snippets. *Similarities:* - Both Appwrite and Firebase provide serverless computing solutions for executing code in the cloud. - Both Appwrite and Firebase provide templates or extensions to build apps easily. - They offer asynchronous, scheduled, and real-time functions. - Functions in both platforms are event-driven, executing in response to specific events, such as HTTP requests, database changes, or authentication events. - Both provide automatic scaling. They handle increasing loads by scaling the functions up or down as required. *Differences:* - Appwrite offers easy and quick deployment of [functions using Git](/docs/products/functions/deploy-from-git), has ready-to-use templates, and supports a large number of runtimes. - Appwrite supports [function runtimes across over 10 different languages](/docs/products/functions/runtimes), including Dart, Bun, Kotlin, and Swift, whereas Firebase only supports JavaScript, TypeScript, and Python. - Firebase offers additional event triggers for different Google Cloud services. #### Realtime Real-time, by definition, means "without significant delay.” You can use it to get constant updates on any activities that occur within the application you are building. This can be applied in various ways, such as a chat application. While real-time features can be complex to build, you can use Appwrite and Firebase to achieve this with much less hassle. *Similarities:* - Appwrite and Firebase provide real-time data updates for applications requiring synchronized data access. - They leverage WebSockets for real-time communication. *Differences:* - Appwrite's real-time system is built on top of our internal events system, which means that any new product/features will automatically support real-time. - Firebase only offers real-time updates from its Realtime Database offering, whereas every Appwrite offering supports real-time updates. #### Messaging Messaging allows you to implement communications in your application. You can use this to send information and updates directly to users through various methods, such as push notifications, SMSes, and emails. Both Appwrite and Firebase provide certain features to implement messaging in your applications. *Similarities:* - You can use real-time features along with databases in both Appwrite and Firebase to create a chat application. - You can use functions in both Appwrite and Firebase to use external providers for SMSes and emails. *Differences:* - Appwrite Messaging features ten different providers to leverage major communications providers for SMS, email, and push notifications. It offers user segregation, message scheduling, and message previews as well. - Firebase offers the infrastructure to implement push notifications via Firebase Cloud Messaging (FCM). Appwrite doesn’t have its own infrastructure but contains a provider to implement FCM. - Firebase offers In-App Messaging to help you engage your app's active users through targeted, contextual messages inside the app. Appwrite does not offer this feature yet but aims to do so in the future. #### Hosting Hosting refers to the service that allows developers to deploy and serve web applications to users over the internet. Appwrite offers native hosting as part of its all-in-one platform. That means you can develop, deploy, and scale your application all from a single platform. Firebase requires connecting separate services like Firestore and Cloud Functions. *Similarities:* - Both Appwrite and Firebase utilize global Content Delivery Networks (CDNs) to ensure low-latency access to hosted content worldwide. - Each platform automatically provisions SSL certificates, ensuring that all content is served securely over HTTPS.. - Both services support popular web frameworks, enabling seamless deployment of applications built with technologies like React, Next.js, and Angular. *Differences:* - Appwrite Sites runs in containerized environments with full control over server-side rendering. Firebase Hosting is optimized for static content and uses Google’s managed infrastructure. - Appwrite is open-source and self-hostable. Firebase is fully managed by Google with no self-hosting support. - Appwrite is an all-in-one solution that covers everything from hosting and authentication to databases and cloud functions in one platform. Firebase also offers these services but manages them across separate tools, requiring more integration effort. ### Conclusion Both Appwrite and Firebase are powerful platforms that simplify backend development and help teams focus on building great products rather than managing infrastructure. They share many similarities but take different approaches to solving the same challenges. Firebase offers a mature, battle-tested ecosystem that’s deeply integrated with Google Cloud. It’s a good choice for teams looking for a managed, ready-to-use solution that scales seamlessly with minimal setup. Appwrite, meanwhile, brings the benefits of open source to modern development. It provides a unified platform where you can manage authentication, databases, storage, messaging, and even hosting—all in one place. You can self-host Appwrite for full control or use Appwrite Cloud for a managed experience. This flexibility, combined with transparent pricing and a growing developer community, makes Appwrite a strong alternative for teams that want ownership and customization. Ultimately, the right choice depends on your priorities. If you want a managed, plug-and-play ecosystem with deep Google Cloud integrations, Firebase fits the bill. If you value openness, flexibility, and an all-in-one development experience that you can run anywhere, Appwrite is built for you. Here’s a table that compares both Appwrite and Firebase: | Feature | Appwrite | Firebase | | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | Deployment | Self-hosted or cloud-hosted | Cloud-hosted only | | Free plan | Yes, Free plan | Yes, Spark plan | | Paid plan | Yes, Pro plan - $25 per month | Yes, Blaze plan - Pay-as-you-go | | Open source | Yes | No | | Support | Community and email | Community, Support Portal, and help center | | Functions marketplace | Has a marketplace featuring a variety of function templates and integrations such as Discord bots, payments with Stripe, ChatGPT API, etc. | Has an extensions hub featuring pre-built functions ready to deploy. | | Messaging providers | 10 providers covering SMS, email, and push notifications | No external providers, but offers the infrastructure for FCM and In-App Messaging | ### Resources If you want to learn more about Appwrite, you can find more resources below. - [Appwrite documentation](/docs) - Visit Appwrite’s docs to learn how to get started and more about Appwrite’s functionality. - [Appwrite GitHub repository](https://github.com/appwrite/appwrite/stargazers)Explore the code and architecture and see how you can contribute to Appwrite. - [Appwrite Discord](https://appwrite.io/discord) - Join Appwrite’s Discord server, be part of a vibrant community, and get support. - [Appwrite YouTube](https://www.youtube.com/@Appwrite) --- ## Messaging: Open-source alternative to Firebase Cloud Messaging https://appwrite.io/blog/post/open-source-firebase-alternative-messaging-fcm With Firebase Cloud Messaging (FCM) officially deprecated in June 2024, developers are now faced with a crucial decision: migrate to FCM’s newer API or explore open-source alternatives. In this blog, we’ll compare Firebase Cloud Messaging with Appwrite’s Messaging service, an open-source alternative to push notifications, to help you understand the key differences and advantages so you can choose the best solution for your project. ### FCM deprecation and what it means As of June 2024, Firebase officially deprecated the legacy Firebase Cloud Messaging (FCM) service. This means developers using the older FCM APIs will need to make changes to their infrastructure. To continue sending messages, you’ll need to migrate your custom code to work with the [newer FCM HTTP v1 APIs](https://firebase.google.com/docs/cloud-messaging/migrate-v1) or shift to a different solution for sending push notifications. Otherwise, you risk losing the ability to send or receive any push notifications in your apps. While FCM remains functional under the HTTP v1 API, this change may lead you to consider alternatives. ### Overview of Firebase Cloud Messaging FCM, part of Google’s Firebase suite, has been widely used for sending push notifications to Android, iOS, and web applications. It enables developers to engage with users by sending targeted, real-time messages and updates. However, with FCM being deprecated in June 2024, developers must migrate to new FCM HTTP v1 APIs or explore alternative solutions. #### FCM key features: - **Cross-platform messaging**: Support for Android, iOS, and web apps. - **Push notifications**: Send notifications to users even when apps are not actively in use. - **Real-time messaging**: Deliver messages instantly or based on conditions. - **Topic-based messaging**: Send messages to user groups based on defined topics. - **Analytics integration**: Track engagement, open rates, and more through Firebase Analytics. While FCM was a popular choice due to its deep integration with Firebase and Google Cloud services, its reliance on the broader Google ecosystem limited flexibility for some developers, especially those looking for more customizable or privacy-friendly solutions. ### Messaging: an open-source alternative Appwrite is an open-source, all-in-one cloud development platform. Use built-in backend infrastructure and web hosting, all from a single place. You can build your entire backend within minutes and deploy effortlessly, adding Authentication, Databases, Functions, Storage, Messaging, and more to your projects using the frameworks and languages of your choice. Appwrite’s [Messaging](https://appwrite.io/products/messaging) service is built to address the real-time communication needs of modern applications in a fully secure and open-source way. #### Messaging key features: - **Cross-platform messaging**: Like FCM, Messaging supports push notifications for Android, iOS, and web platforms, as well as SMS and email. - **Server-side SDKs**: Provides flexibility for integrating notifications directly into your backend services. - **Secure and private**: Appwrite is [fully GDPR, CCPA, and HIPAA-compliant](https://appwrite.io/docs/advanced/security), ensuring the secure storage of your and your users’ data. - **Targeting, scheduling**, **and previewing:** You can draft, preview and schedule your message directly from the console, ensuring you’re targeting the right users at the right time. - **Integrations:** Appwrite provides a variety of [third-party integrations](https://appwrite.io/integrations#messaging) to up your messaging game. - **Community and support**: As an open-source platform, Appwrite benefits from a growing developer community and comprehensive documentation. ### Comparing FCM and Appwrite’s Messaging service #### Ease of setup **FCM:** Firebase offers an easy-to-use interface that is well-suited for developers already working within the Google ecosystem. FCM integrates smoothly with mobile apps via the Firebase SDK and benefits from the Google Cloud infrastructure. **Messaging:** Appwrite Messaging, part of the broader Appwrite ecosystem, offers a [user-friendly setup](https://appwrite.io/docs/products/messaging). You can develop in the language and framework of your choice, eliminating any vendor lock-in. This flexibility means you can integrate messaging into your apps while retaining complete control over your infrastructure and data. Whether you choose to self-host or use a cloud setup, Appwrite empowers you to customize the environment without being tied to a single platform. #### Push notifications **FCM:** FCM is tightly integrated with Google's infrastructure, making it the go-to option for Android developers. Its native support in Android allows for easy setup without complex configurations, providing a seamless experience for push notification delivery. Optimized for performance, FCM ensures efficient notifications with minimal impact on battery life and device resources. **Messaging:** While Appwrite Messaging can't fully replace FCM on Android due to Google's native integration, it provides a highly customizable push messaging solution for developers looking to maintain full control over their infrastructure, data, and security. Additionally, Appwrite supports platforms beyond Android and iOS, making it suitable for broader use cases and custom implementations, particularly for teams looking to self-host or avoid dependency on proprietary cloud services. #### Data privacy and security **FCM:** With FCM, data is managed through Google Cloud, which may raise concerns for teams working with sensitive data or those requiring specific compliance with data privacy regulations. Google provides strong security features but operates under its terms and conditions. **Messaging:** Appwrite is fully compliant with [GDPR](https://appwrite.io/blog/post/announcing-appwrite-is-gdpr-compliant), [CCPA](https://appwrite.io/blog/post/announcing-appwrite-is-ccpa-compliant), and [HIPAA](https://appwrite.io/blog/post/announcing-appwrite-is-hipaa-compliant) regulations. It also offers encryption and built-in security mechanisms to protect your messaging infrastructure. #### Pricing **FCM:** Firebase offers a generous free tier with FCM, and additional costs are often covered under the broader Firebase service pricing model. However, as your app scales, these costs can add up depending on your use of other Firebase services. **Messaging:** Messaging is currently in beta and is free to use for both self-hosted and cloud solutions, but we will release competitive pricing in Q4 of 2024. Keep an eye out on our [pricing page](https://appwrite.io/pricing). ##### Developer community and support **FCM:** Firebase has the benefit of a large, established community and extensive documentation. **Messaging:** Appwrite maintains an active and growing open-source community. With frequent contributions, updates, and a supportive user base, developers can expect responsive help and continued innovation. ### FCM vs. Messaging | **Feature** | **FCM** | **Messaging** | | --- | --- | --- | | **Open-source** | No | Yes | | **Self-hosting options** | No | Yes | | **Platform dependency** | Google Cloud dependent | Platform independent, open-source | | **Data privacy** | Managed under Google’s terms and conditions | GDPR, CCPA and HIPAA compliant | | **Message scheduling** | Yes | Yes | | **Message preview** | Yes | Yes | | **Targeting and topics** | Yes | Yes | | **Messaging channels** | Push notifications for iOS, Android and web | Push notifications, email and SMS, with extensive third-party integrations | | **SDK Support** | Extensive SDKs for Android, iOS, web | SDKs for multiple platforms (Android, iOS, web, etc.) | | **Cost** | Free tier with paid options for large scale | Free in beta, pricing to be announced | | **Community and support** | Large community managed by Google | Discord community and contributors | ### Migrating from FCM With the deprecation of FCM in June 2024, developers are faced with the choice of migrating to FCM HTTP v1 API or seeking alternatives like Appwrite. If you’re looking to maintain flexibility and data control in your push notifications, Appwrite’s Messaging service offers a powerful, scalable alternative. ### Conclusion Firebase Cloud Messaging remains a powerful option, especially within the Google ecosystem. But in a world where data privacy and control are becoming increasingly important, Appwrite provides an opportunity to build a messaging infrastructure that’s both open and secure. - [More on Messaging](https://appwrite.io/products/messaging) - [Messaging docs](https://appwrite.io/docs/products/messaging) - [Push notifications best practices](https://appwrite.io/blog/post/push-notifications-best-practices) - [Messaging explained](https://appwrite.io/blog/post/messaging-explained) --- ## Appwrite Sites vs Netlify: Choosing the right web hosting platform https://appwrite.io/blog/post/open-source-netlify-alternative Deploying frontend applications has gotten easier over the past decade, but also more complex behind the scenes. Most apps today need more than just HTML, CSS, and a CDN. They need authentication, secure APIs, environment configs, storage access, and sometimes server-side rendering. Netlify helped kick off a modern approach to hosting, especially for static sites and JAMstack-style architectures. But if you're building an app that also depends on a backend, managing that split becomes a real part of your workflow. With [**Appwrite Sites**](products/sites), you get an alternative that treats frontend deployment as part of a full-stack environment. Instead of connecting your frontend to a separate backend via API tokens and CORS rules, Sites lives in the same platform as your database, storage, authentication, and functions. In this article, we'll break down how **Appwrite Sites** and **Netlify** compare, from framework support and SSR, to environment variables, deployment flows, and the developer experience of hosting modern apps. ### What is Appwrite Sites? [**Appwrite Sites**](/products/sites) is a deployment and hosting platform within the Appwrite ecosystem. It supports both **static sites** and **server-side rendered applications**, with built-in Git integration, preview deployments, custom domain support, and container-based SSR. Because it's part of the same platform as Appwrite's backend services, like auth, database, storage, and cloud functions, you don't need to stitch together separate providers or manage secrets manually. Your frontend and backend live in the same project, with a shared security model and automatically scoped environment variables. Sites supports a wide range of frameworks, from Next.js and Nuxt to Astro, SvelteKit, Angular, Remix, and Flutter Web. For static apps, you can deploy via Git or manually upload a `.tar.gz`. For SSR, Sites runs your app in a container with runtime access to secrets and Appwrite SDKs. ### Netlify and the JAMstack model Netlify pioneered the JAMstack movement, where apps are decoupled into static frontends + serverless functions + external APIs. That model works especially well for sites that are mostly static, with light dynamic behavior: marketing pages, documentation, or e-commerce frontends backed by third-party services. Netlify offers automatic builds from Git, preview URLs, branch-based environments, edge functions, form handling, and even built-in identity support (though limited in scope). It also includes a CLI and plugin ecosystem for customizing builds. But while it's flexible, it's not tightly integrated. You still need to bring your own database, authentication, file storage, and backend logic, and manage how your frontend connects to all of it. If you're building a simple frontend, Netlify is fast and effective. But for applications where frontend and backend logic are deeply connected, managing those boundaries becomes the challenge. ### Deployment workflows #### Netlify Netlify uses Git-based deployment as its primary model. You connect your GitHub, GitLab, or Bitbucket repository, and Netlify auto-detects your framework. Every push to a branch triggers a build, with a unique deployment URL. - Branch-based previews are automatically generated - Build settings can be customized via the UI or `netlify.toml` - Environment variables can be scoped to Production, Preview, and Dev - Rollbacks are supported - There's no drag-and-drop UI deployment option, manual deploys must go through the CLI Netlify does provide build plugins, custom functions, and API routing, but you'll need to wire those up separately if you're using a custom backend. #### Appwrite Sites Appwrite Sites also supports Git-based deployment with preview environments: - You connect a GitHub repo to your Appwrite project - Framework detection sets defaults for build, install, and output - Pushes to the production branch deploy automatically - Every commit, branch, or pull request generates a unique URL for preview - All builds include downloadable artifacts - **Manual deployments** are supported via `.tar.gz` or drag-and-drop in the Appwrite Console Rollbacks are instant and available through the UI. Because the deployment lives inside your Appwrite project, it inherits access to your backend services automatically, without needing to configure tokens, keys, or external origins. This is especially helpful for teams working in mixed Git workflows, or solo developers who want a quick manual deploy option without setting up CI. ### Framework support Netlify supports most popular frontend frameworks, including Astro, Eleventy, SvelteKit, Nuxt, Next.js, and Angular. Most are treated as static frameworks unless you configure SSR explicitly using Netlify Functions or Edge Functions. SSR support is more limited. While you can run serverless functions alongside your frontend, the integration varies depending on your framework. Next.js and Astro get the most complete support. Appwrite Sites supports a similar set of frameworks, but adds **SSR support via containers**, which allows full runtime access to environment variables and Appwrite services: - **Next.js** – SSR and static - **Nuxt** – SSR and static - **SvelteKit** – SSR and static - **Angular** – SSR and static - **Astro** – SSR and static - **Remix** – SSR and static - **Flutter Web** – static only - **Vanilla JS, Lynx, Analog, etc.** – static Each framework can be customized with its own build and install commands. If your framework isn't listed, you can use “Other” and specify custom config. If you're using SSR with anything beyond Next.js or Astro, Appwrite Sites provides broader and more consistent support out of the box. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Start for free" point2="Open source" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/" /%} ### Hosting options: Static vs SSR Both platforms support static and server-side rendered hosting, but their architectures differ significantly. #### Netlify - **Static**: Content is pre-rendered and served from a global CDN - **SSR**: Pages are rendered using serverless or edge functions - **Timeouts**: Default timeout is 10s, up to 26s for Pro plans - **Limitations**: Memory and cold starts depend on function tier and region - **Environment access**: Functions get runtime env vars, but static content does not Netlify's SSR works well for lightweight APIs and dynamic content, but lacks full access to services unless you add integrations or connect to external providers. #### Appwrite Sites - **Static**: CDN-hosted HTML/CSS/JS, with build-time env vars - **SSR**: Full server-side rendering in isolated containers - **Timeouts**: Configurable up to 30s per request - **Environment access**: Both build-time and runtime variables are supported - **Logging**: Console output from SSR code is captured in logs The container-based SSR model means you can use Appwrite's SDK at runtime, securely read secrets, and interact directly with databases, functions, or storage, all from your rendering logic. If your SSR routes require access to auth tokens, protected storage, or custom logic, Appwrite's model gives you more control than Netlify's function-based execution. ### Environment variables and secrets Netlify uses a three-scope model, Production, Preview, Development, with env vars scoped accordingly. You define them in the dashboard or via the CLI, and they're injected at build or function runtime. Appwrite Sites provides: - Project-wide variables shared across services - Site-specific variables - Secret variables (hidden after creation) - Built-in Appwrite variables (project ID, endpoint, region, etc.) - Automatic API key injection at build and runtime (for SSR) You can define variables once in your Appwrite project and access them across deployments, functions, and services, without needing to sync values manually. For example, instead of creating a token in Netlify and passing it to your frontend, Appwrite Sites automatically injects a scoped API key with access to your project, removing the need to manually rotate secrets or configure CORS. ### Custom domains, CORS, and trust boundaries Both platforms allow custom domains and HTTPS via auto-generated certificates. Netlify: - Supports custom domains via DNS config - Wildcard subdomains and previews (e.g. `.netlify.app`) - Requires manual CORS configuration when connecting to external APIs Appwrite Sites: - Supports custom domains via CNAME or NS records - Auto-generated domains for every branch and commit - Built-in CORS trust if your backend is hosted on Appwrite Since Appwrite Sites and your backend live in the same project, deployed frontends are automatically recognized and trusted. You don't have to manually list frontend origins or manage preflight responses, Appwrite knows your Sites deployment is part of the same scope. This eliminates one of the most common pitfalls in multi-platform deployments: CORS misconfigurations. ### Logging and observability Netlify provides basic logging: - Build logs for every deployment - Function logs for serverless execution - Analytics for traffic and latency (paid plans) Appwrite Sites offers: - Build logs - Request logs (status code, path, duration, headers) - Console output for SSR apps - Log retention (24h on Free, 7d on Pro) - Optional log disabling (for performance-sensitive routes) Appwrite's SSR logs include server-side console output, which is helpful when debugging SSR failures, auth edge cases, or unexpected rendering issues, without needing to integrate a third-party logging tool. ### Security and isolation Both platforms enforce HTTPS and offer secret management. Appwrite adds additional safeguards: - Isolated containers for SSR deployments - Secrets not visible after creation - Automatic API key injection scoped to the current site - Project-based access controls - Preview deployment restrictions (e.g. team-only) Since everything runs inside your Appwrite project, the surface area for misconfigurations is smaller. You're not exposing separate public APIs or managing external API keys, access happens within a single trusted environment. ### Appwrite Sites vs Netlify: feature comparison | Feature | Appwrite Sites | Netlify | | --- | --- | --- | | Static hosting | Yes | Yes | | Server-side rendering | Yes (containers) | Yes (functions) | | Git-based deployment | Yes | Yes | | Manual deployment (UI) | Yes | No | | Preview deployments | Yes (branch, PR, commit) | Yes | | Custom domains | Yes (CNAME + NS) | Yes | | Environment variables | Yes (build + runtime, project-wide) | Yes (scoped by env) | | Secrets support | Yes (Option to hide after creation) | Yes | | Automatic API key injection | Yes | No | | Auth, DB, storage integration | Yes (native) | No (external required) | | SSR console logs | Yes | Limited | | CORS for Appwrite backend | Yes (auto-trusted) | No (manual config) | | Timeout configuration | Yes (up to 30s, self-configurable) | Yes (10s default, requires support team on paid plans) | ### Final thoughts Netlify is an excellent platform for static frontends and lightweight JAMstack sites. If you're building a marketing page, blog, or frontend that only talks to third-party APIs, it still delivers a clean and fast workflow. But if your application needs more than that, auth flows, custom APIs, server-side logic, secure data access, etc., **Appwrite Sites** provides an integrated alternative. Instead of wiring together services across platforms, you keep your frontend and backend in one place, with fewer moving parts and tighter defaults. Appwrite Sites is built for teams that want their frontends to do more, and for developers who want full control without unnecessary complexity. ### More resources - [Appwrite Sites docs](/docs/products/sites) - [Appwrite compared to Vercel](/blog/post/open-source-vercel-alternative) - [Appwrite Sites product tour](https://youtu.be/VtDe6hDw91k) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) - [Appwrite Discord server](/discord) --- ## 10 open-source alternatives to popular software for startups https://appwrite.io/blog/post/open-source-startup-tools Time and time again, we've seen that startups that change and adapt to market needs are the ones who win big. However, with startups using traditional software products, it becomes increasingly difficult to pivot. High costs, vendor lock-in, limited control, and flexibility can hold them back to do so. That's why open-source, self-hostable software products are becoming the go-to choice for many startups and enterprises as they offer the control, flexibility, and scalability that traditional software solutions simply can't match. In this blog, we'll learn about open-source, it's benefits and 10 open-source product alternatives for your startup. ### What is open-source? In simple words, open-source software is made available with a license that allows anyone to view, modify, and in some cases also distribute its source code. Unlike traditional software, which is owned and controlled by a company, open-source products gives you full access to the code, so you can customize the software to meet your specific needs. It’s more than just access to the code. We’ve seen too many cases where startups got burned by closed platforms. A founder recently shared on [LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7331362060750032896/) how their client lost $65,000 and their entire EdTech platform overnight. Just because the platform they were using filed for bankruptcy without warning. Fired all 1,500 employees. Locked every customer out. No access to code. No way to export data. No one to even reach out to. That’s what happens when your infrastructure is not in your control. This is why open-source matters. Because with open-source, you’re not dependent on a single company’s fate. You can self-host. You can migrate. You can fork the code and keep building. ### Benefits of open-source software While the huge benefit of an [open-source](https://opensource.org/) product is the ability to customize it according to your needs, there are many other benefits to consider, like: #### Cost savings Open-source products generally offer budget-friendly options for businesses of all sizes. Many open-source projects are free to get started and you can pay as you scale or need additional support, which could be a great option for budget-tight teams. #### Community support Open-source projects generally have a large and active community, with members who are always ready to help. This makes it easy to troubleshoot and get help when needed. #### No vendor lock-in With an open-source product, you can have more control over the tech stack and reduce the risk of being trapped by a vendor's decisions. So you don't have to get dependent on a single vendor and migrate whenever needed. #### More control over your data Most open-source projects have self-hostable versions that allow you to host the software on your own servers, giving you full control over your data and how it's stored. ### 10 open-source product alternatives #### 1. [n8n](https://n8n.io/) ![n8n](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/n8n.png) **n8n** is an open-source alternative to **Zapier**. It allows users to automate workflows by connecting various apps and services, similar to Zapier, but with the added advantage of being fully customizable. #### 2. [Appsmith](https://www.appsmith.com/) ![Appsmith](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/appsmith.png) Appsmith is an open-source alternative to Retool. It enables you to build custom internal tools quickly by connecting to databases, APIs, and other services. It provides a drag-and-drop interface to design apps, making it easy to create dashboards and workflows. #### 3. [Documenso](https://documenso.com/) ![Documenso](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/documenso.png) Documenso is an open-source alternative to Docusign. It is a document management platform designed to help businesses organize, store, and collaborate on documents. It offers features like document indexing, searching, and version control, allowing teams to manage their files efficiently. #### 4. [Dub.co](https://dub.co/) ![Dub.co](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/dub.co.png) Dub.co is an open-source alternative to Bitly. It is a link management platform designed for modern marketing teams. It allows users to create branded short links using custom domains, track clicks with detailed analytics, and manage marketing campaigns efficiently. #### 5. [AppFlowy](https://appflowy.com/) ![AppFlowy](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/AppFlowy.png) AppFlowy is an open-source alternative to Notion. It provides a flexible workspace for note-taking, project management, and collaboration with features like customizable templates, task tracking, and databases. #### 6. [Webstudio](https://webstudio.is/) ![Webstudio](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Webstudio.png) Webstudio is an open-source alternative to Webflow. An open-source platform for building and managing websites with a focus on simplicity and flexibility. It provides an intuitive drag-and-drop interface, allowing users to design websites without coding. #### 7. [Typesense](https://typesense.org/) ![Typesense](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Typesense.png) Typesense is an open-source alternative to Algolia. A fast and relevant search engine built for developers. It allows you to easily integrate full-text search functionality into your applications with features like typo tolerance, filtering, faceting, and real-time indexing. #### 8. [PostHog](https://posthog.com/) ![PostHog](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/PostHog.png) PostHog is an open-source alternative to Mixpanel. A product analytics platform designed to help businesses track user behavior and make data-driven decisions. It provides teams with tools that allow them to gain insights into how users interact with their products. #### 9. [Sentry](https://sentry.io/welcome/) ![Sentry](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Sentry.png) Sentry is an open-source alternative to Raygun. An error tracking and monitoring platform that helps developers identify and fix issues in their applications in real-time. It provides detailed error reports making it easier to diagnose and resolve bugs quickly. #### 10. [Appwrite](https://appwrite.io/) ![Appwrite](/images/blog/10-open-source-alternatives-to-popular-software-for-startups/Appwrite.png) Appwrite is an open-source alternative to Firebase, Vercel and Auth0. An all-in-one, cloud development platform that provides you with built-in backend infrastructure and hosting. It includes everything from auth, databases, and storage to serverless functions and real-time APIs, so you can focus on building, not wiring things together. ### Conclusion In conclusion, open-source products gives your startup the flexibility, control, and cost savings you need to scale and innovate. Choose an open-source tech stack for your startup that offers a smarter way to build and grow without the limitations of traditional software. --- ## Appwrite Sites vs Vercel: Choosing the right web hosting platform https://appwrite.io/blog/post/open-source-vercel-alternative Updated on October 6, 2025 Deploying modern web applications should be fast, flexible, and reliable. As developers, we've come to expect instant previews, custom domain support, seamless environment configuration, automatic HTTPS, and the ability to deploy everything from static pages to full server-rendered apps. Vercel has been a default choice for frontend teams building with Next.js. But with the launch of **Appwrite Sites**, you now have a new, integrated way to deploy web apps, especially when you want your front end to live alongside your backend, database, authentication, and storage. In this post, we'll take a look at how Appwrite Sites compares to Vercel. We'll explore everything from framework support to deployment workflows, logging, performance, and how each platform fits into a larger developer workflow. ### What is Appwrite Sites? **[Appwrite Sites](/products/sites)** is Appwrite's new deployment and hosting platform, designed to support both **static** (including single-page applications) and **server-side rendered (SSR)** web applications. It's tightly integrated into the Appwrite ecosystem, so if you're already using Appwrite for authentication, database, storage, or functions, you can now deploy your front end in the same place, with automatic access to shared environment variables and services. Whether you're building with Vite, Next.js, Nuxt, SvelteKit, Astro, or something simpler like Vanilla JS or even Flutter Web and [Lynx](/blog/post/bytedance-lynx-vs-react-native), Appwrite Sites provides a unified deployment workflow for both static and dynamic applications. ### Integrated, full-stack hosting with Sites One of the biggest differences between Appwrite Sites and Vercel is how they fit into your application architecture. **Vercel** focuses primarily on frontend deployment. It excels at handling static assets and serverless-rendered content, especially from Next.js. It also supports other frontend frameworks like Astro, SvelteKit, Nuxt, and Remix, although it's deeply optimized for Next.js and includes native support for features like ISR (Incremental Static Regeneration) and Edge Middleware. **Appwrite Sites**, on the other hand, is part of a **complete development platform**. You're not just deploying your frontend. You're deploying it in the same environment that already knows about your database collections, users, sessions, cloud functions, storage buckets, and APIs. That means the same platform handles everything from user auth to file uploads to server-side logic, and your frontend can interact with it all without any manual configuration. This level of integration also means less risk of misconfiguration when connecting your services. Your environment variables, domain rules, CORS policies, and secrets are all managed within one project scope, reducing the friction between your frontend and backend. ### Deployment workflows Both platforms support Git-based workflows, which means you can deploy by connecting a GitHub repo and pushing changes to a selected branch. Each deployment also generates **automatic screenshots**, giving you a quick visual snapshot of the deployed result. #### Deploying on Vercel - You connect your GitHub, GitLab, or Bitbucket repo. - Vercel auto-detects your framework and sets up sensible defaults. - Every push to your production branch triggers a build and deployment. - Pull requests automatically create preview deployments with unique URLs. - Each deployment includes a UI to inspect build logs, preview the deployment and view associated metadata. - You can redeploy any previous commit and view full deployment history. - There's no manual UI deployment support, although, there’s a CLI option. #### Deploying on Appwrite Sites - You connect a GitHub repo to your Appwrite project. - Appwrite auto-detects your framework and applies default build, install, and output settings. - You choose a production branch. - Each commit triggers a build and deploy. - You can preview pull requests, commits, and even branches, each with their own URLs. - Manual deployment is supported, with a simple drag-and-drop interface in the Console. This lets you deploy without needing Git or the CLi. - Rollbacks are instant. You can restore a previous deployment with one click. - Deployment output is available as a downloadable folder you can inspect locally. Manual deployment with Appwrite Sites offers an extra layer of flexibility, especially for small and simple static sites, which makes it easy to get started or quickly publish content without relying on Git or command-line tools. ### Framework support Vercel is best known for its deep Next.js integration, but it also supports other frameworks like Astro, SvelteKit, Nuxt, Angular, and Remix. However, its serverless and edge rendering infrastructure is most optimized for Next.js. Features like API Routes, Middleware, and ISR work out of the box with Next.js and require extra configuration or aren't supported at the same level with other frameworks. Appwrite Sites offers SSR and static support for a wide range of frameworks, including: - Next.js - Nuxt - SvelteKit - Angular - Astro - Remix - Flutter Web (static) - Lynx - Analog - Vanilla JS and other static site generators Each framework comes with its own build recommendations, and Appwrite Sites allows you to customize build and install commands, as well as define the output directory for compatibility. Any static framework works well with Appwrite out of the box. For popular tools like Next.js, Nuxt, and SvelteKit, Appwrite offers presets that auto-configure your build and output settings. If your framework isn’t listed, you can choose “Other” and manually set the commands and output directory. Most static and SPA setups work seamlessly. The only exception is SSR support for certain metaframeworks like Lit, which aren’t yet supported in SSR mode, but they still work fine as static sites. If you're using a framework outside of Next.js, Appwrite Sites provides a more consistent experience across the board. This is also an advantage for teams that use different frameworks for different parts of their stack. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Start for free" point2="Open source" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/" /%} ### Hosting options: Static vs SSR Appwrite Sites supports both **Static/SPA** hosting and **Server-Side Rendering (SSR)**, much like Vercel. But there are a few differences in how they operate. #### Static hosting On both platforms, static content is pre-rendered at build time and served via a CDN. This gives you fast cold starts and simple deploys. Appwrite Sites provides build-time environment variables and supports any static framework or tool. #### Server-side rendering With SSR, both platforms render pages on demand. Vercel runs SSR via serverless functions or edge functions depending on your configuration and framework. Its default serverless runtime supports dynamic SSR, and Next.js projects get special handling for API routes, dynamic rendering modes, and edge cache rules. Appwrite Sites runs SSR apps in **isolated containers**, which means: - You get full runtime access to environment variables - Console output from server-side code is captured in logs - The SSR runtime can access Appwrite services directly That container-based model gives you more flexibility, especially if you're doing server-side API calls, interacting with Appwrite's SDK, or running custom logic during rendering. Appwrite Cloud takes care of all the serverless functionality with [optimized CDNs](/docs/products/network/cdn), [edge capabilities](/docs/products/network/edges), and autoscaling built in and fully abstracted through the [Appwrite Network](/docs/products/network), so you can focus on your application code rather than infrastructure management. ### Environment variables Both platforms offer strong environment variable support, with scope control and secrets handling. #### Environment variables on Vercel - You define environment variables per environment: Development, Preview, and Production. - You can override variables per branch. - Secrets are encrypted and only available at build or runtime (depending on the variable). - You can manage them from the dashboard or CLI. #### Environment variables on Appwrite Sites - You define project-wide and site-specific variables. - Variables can be exposed at build time, runtime, or both. - Appwrite injects **built-in environment variables** for your site, project, region, version, framework, and more. - You can define **secret** variables that are hidden after creation and not exposed via Console or API. These built-in variables make it much easier to integrate your front end with the rest of your Appwrite services. You don't have to manually pass your API endpoint or project ID because it's already available. Appwrite also provides a **dynamic API key automatically**, scoped and injected for both the build process and runtime (for SSR). This makes it easier to create or configure collections at build time, or securely perform server-to-server operations during SSR. If you were deploying on Vercel, you’d need to manually generate an Appwrite API key, store it as a secret, and rotate it regularly to maintain security. ### Domains, CORS, and routing Both Vercel and Appwrite Sites allow you to connect custom domains and use automatically generated subdomains for previews. Vercel provides: - Auto-generated domains per project and deployment. - Custom domain support with DNS configuration. - Wildcard and branch subdomains. - HTTPS with automatic renewal via Let's Encrypt. Appwrite Sites offers: - Auto-generated domain. - Custom domain support via CNAME records or full **nameserver (NS) configuration** - Branch and commit-specific URLs. - HTTPS enforcement and TLS certificates out of the box. When you host both frontend and backend on Appwrite, your domain is trusted automatically. This eliminates the need to manually add frontend origins to your CORS settings because Appwrite already knows your Sites deployment is part of your project. This simplifies development and removes a common pain point: CORS errors. It’s also important for preview deployments. Vercel assigns preview URLs under a shared namespace (e.g. *.vercel.app). If you try to allow previews with a wildcard like *.vercel.app, you’re not just allowing your previews, you’re allowing every Vercel preview domain to access your backend. That introduces unnecessary risk. Appwrite avoids this entirely. Since previews are scoped to your project, they’re trusted automatically, giving you tighter control without needing to expose your backend to external domains. ### Logging and monitoring Both platforms offer ways to monitor your applications and track issues in production, with some key differences in their approaches. Vercel provides: - Build logs and deployment history. - Function execution logs (for serverless and edge functions). - Analytics for traffic and performance (on paid plans). Appwrite Sites includes: - **Request logs**: status codes, duration, methods, headers, paths. - **Console output**: SSR logs from `console.log()` and `console.error()`. - **Log retention**: 24 hours on the free plan, up to 7 days on Pro. - **Optional log disabling**: useful for privacy and performance-sensitive SSR routes. These logs give you immediate visibility into how your app behaves in production and will help with troubleshooting issues when needed. ### Performance and timeouts Vercel uses edge functions and CDN caching for content delivery. Static content is served from edge locations, while serverless functions run in regional data centers. Performance varies by plan tier and deployment type. Appwrite Sites uses a global CDN for static content, and container-based SSR for dynamic apps. You can configure timeouts (up to 30 seconds per request), and performance depends on your framework and hosting mode. Static hosting delivers near-instant responses through CDN caching, while SSR requires container initialization on cold starts but provides consistent performance afterward. ### Security and isolation Both platforms enforce HTTPS and protect secret environment variables. Appwrite Sites also ensures: - Container isolation per deployment - TLS enforcement for all domains - Secret environment variables hidden after creation - Preview deployments can be restricted to team members This is important for deploying apps that handle sensitive data or use server-side logic tied to specific users or accounts. ### When to choose Appwrite Sites If you're already building your backend with Appwrite, or want a single platform for your full-stack app, Appwrite Sites is a natural fit. You get integrated deployments, native access to your backend, and support for a wider range of frameworks with both static and SSR modes. It's also a good choice if you: - Need SSR for frameworks beyond Next.js - Want to deploy front ends tightly coupled to backend services - Prefer to manage authentication, database, storage, and hosting in one place - Want rollback support, deployment visibility, and runtime logs built-in ### Final thoughts Both Appwrite Sites and Vercel are great platforms for deploying modern web applications. But they're built for slightly different workflows. Vercel is an excellent frontend-focused host, especially for teams that are all-in on Next.js, or for static sites that don’t need a backend. Appwrite Sites, meanwhile, offers a deeply integrated full-stack environment, where your front end, backend, and services all live in the same project. If you want to simplify your stack and keep everything in one place, from database to auth to hosting, Appwrite Sites offers a compelling alternative that brings clarity and flexibility to full-stack development. You can get started today by heading to your Appwrite Console and creating a new Site. Appwrite Sites has templates, GitHub integrations, and framework guides ready to go. If you have any questions, reach out to the Appwrite community on [Discord](https://appwrite.io/discord) or use the [contact form](https://appwrite.io/contact-us) on the website. ### Frequently asked questions (FAQs) **1. Is there an open-source alternative to Vercel?** Yes. Appwrite Sites is a modern, open-source alternative to Vercel. It lets you deploy static and server-side rendered apps while managing your backend, database, auth, and storage, all in one place. The key difference is control: Vercel is fully managed and frontend-focused, while Appwrite Sites gives you the same smooth deployment experience with the flexibility to self-host or use the cloud. If you want an all-in-one, open, and developer-friendly setup, Appwrite Sites is worth considering. **2. Are there platforms that host both frontend and backend together?** Yes. Tools like Appwrite combine hosting with backend services (auth, DB, functions). This helps you avoid managing CORS, API gateways, and secrets across multiple providers. **3. What’s the difference between Appwrite Sites and Vercel?** Vercel focuses on frontend hosting, especially Next.js, while Appwrite Sites hosts your entire stack (frontend + backend) in one environment. You get built-in auth, databases, and APIs, so your frontend and backend live together without extra setup. **4. Does Appwrite Sites support Git deployments and custom domains?** Yes. Appwrite Sites fully supports Git-based deployments: you can connect your GitHub repo, select a branch, and automatically deploy on every commit or pull request. It also supports custom domains, with automatic HTTPS and SSL certificates included. You can connect domains via CNAME or nameserver (NS) configuration, and even create branch-specific preview URLs for testing before production. ### More resources - [Appwrite Sites docs](/docs/products/sites) - [Announcing Appwrite Sites: the open-source Vercel alternative](announcing-appwrite-sites) - [Building with Sites templates](/blog/post/building-with-sites-templates) - [Appwrite Sites product tour](https://youtu.be/VtDe6hDw91k) - [Appwrite Sites video announcement](https://youtu.be/0cERQxFjTW4) --- ## Supporting the future of open source https://appwrite.io/blog/post/oss-journey-blog With the release of [Appwrite Pro](https://appwrite.io/blog/post/announcing-appwrite-pro), we reached another milestone. A new development in our product offering that allows you to build with more confidence. But for those of you thinking: ‘This is a paid plan, so what about us open-source maintainers?’ Well, we haven’t forgotten you. Appwrite is open-source, and we have come so far due to the love the open-source community has shown us. Therefore, we want to continue to give back to the community by allowing OSS maintainers to build with Appwrite Pro free of charge. With this OSS plan, we hope to ensure the future for many open-source maintainers, just like many of you have ensured ours. Let’s take a short walk down memory lane to learn about Appwrite’s open-source journey so far and why we owe the community our support. ##### **Falling in love with open-source** The [first ever pull request](https://github.com/appwrite/appwrite/pulls?page=105&q=is%3Apr+is%3Aclosed) for Appwrite was submitted in August 2019 by Eldad, Founder and CEO of Appwrite, and not much later, the [first community PR](https://github.com/appwrite/appwrite/pull/2) followed. Contributors kept on coming and helped with code, copy, accessibility, fixing bugs, and more. For Eldad at the time, it was remarkable to witness how devs would help out and expect nothing in return. They wanted to use Appwrite as their backend, so they helped improve Appwrite for themselves and also ensured others would not have to face the challenges of building a complex backend. During this period, Eldad fell in love with open-source and the community around it. ![Eldad's first ever PR for Appwrite](/images/blog/first-pr1.png) Not much later, Appwrite's [open-source version](https://betterprogramming.pub/introducing-appwrite-an-open-source-backend-server-for-mobile-web-developers-4be70731575d) was launched. The launch went viral, our Hacker News post was trending, and our GitHub Stars charts went up with over 1,500 new stargazers in the first month after release. The love was evident from both sides and together with many contributors, Eldad worked tirelessly as a solo maintainer to build out the product envisioned, ensuring many other developers would not face the same struggles when building complex software. ![First ever community contribution for Appwrite](/images/blog/community-pr1.png) Today, Appwrite has over [38K stars](https://github.com/appwrite/appwrite), 800 contributors, 3.5K forks, and thousands of contributions made, all with the help, support, and love of the open-source community. ![The number of contributions over the years](/images/blog/contributions-2023.png) ##### **Pre-monetization as a solo maintainer** After three months of getting serious traction, Eldad decided to become a full-time open-source maintainer, leaving a full-time job. After 12 months, with no monetization of Appwrite yet possible, the real struggle started. The financial burden as a solo maintainer was immense. Not only do you have to cover the costs of building your project, such as infrastructure, development tools, and hosting, but you also feel the financial burden of day-to-day life. After many considerations to fight the financial pressure, Appwrite got an amazing investment offer from the outstanding individuals at Ibex and Seedcamp, who offered to support the vision. Six months later, they were followed by an investment from Bessemer (Auth0, Cloudinary) and Flybridge (Firebase, MongoDB) that led our 8.5m seed round. The timing could not have been better, and Appwrite was able to thrive with new financial stability. ##### **Giving back to the community** This financial power meant many things, such as Appwrite could build a team, invest in a better product offering, community swag, and start an OSS fund. And all of it with one thing in mind, giving back to the community. Helping those who have helped us. ##### **Hiring from the OSS community** The first thing Eldad did with the funding was build out the team. And what better place to find the right people than in the open-source community? We hired some of the first and top Appwrite contributors. [Christy](https://github.com/christyjacob4), [Torsten](https://github.com/TorstenDittmann/TorstenDittmann), [Damodar](https://github.com/lohanidamodar), and Brandon. They formed Appwrite’s founding engineering team. Damodar even had a [YouTube channel](https://www.youtube.com/@appwriters) that focussed on Appwrite and Flutter, just to show off the support we got from an early stage. ![The evolution of Torsten Dittman's contributions to Appwrite](/images/blog/Torsten-GitHub-profile.png) As we grew, so did our team, and we added many more engineers to the team. Many of them came [from the community.](https://appwrite.io/community) Some contributed to code, some started helping others in the community, and some created content. To this day, we still hire from the community, as we stand by open source and its contributors. It is embedded in our DNA. ##### **Appwrite Swag Store** Another way we ensure funds from the community go back to the community is by giving back the money we earn on swags. For every shirt, hoodie, bag, or sock we sell from the [Appwrite store](https://appwrite.store/), we give back the profit made. In general, we believe swag should be there for the community, not to line our pockets. ##### *OSS Fund* We always knew that if we're ever fortunate enough to be in a position to give back funds to the community, we'd never think twice about it. And with our Series A, we were in that position. In 2022 we announced the Appwrite OSS Fund, a $50,000 fund to support open-source projects. At the time, Eldad put the purpose of the fund into words, and we couldn’t phrase it any better: *‘In the journey of turning your dream project into a reality, all that you need to have is the vision to build an awesome open-source project. The idea behind the fund is to support developers like you and the open-source project you are building.’* This fund has now come to an end, but we successfully supported twenty OSS maintainers, such as [LinkFree](https://github.com/EddieHubCommunity/BioDrop) (now BioDrop), [Vale](https://github.com/errata-ai/vale), [Rally](https://github.com/lukevella/rallly), and [Strawberry](https://github.com/strawberry-graphql/strawberry/). ##### **Pro Plan for free** Now that we have released Appwrite Pro, we can also release our new initiative to give back to the OSS community, the Appwrite OSS Program. OSS maintainers can apply for the OSS Program and use Appwrite Pro resources free of charge. This plan is important to us as we strongly believe in helping OSS maintainers, and we are humbled to have the opportunity and privilege to contribute to the growth of developers. We know very well where we came from, and Appwrite has been built with and by the open-source community. We’re excited to continue our support and to see OSS maintainers reach out to build with Appwrite Pro. In case you are such a maintainer, read our [announcement blog](https://appwrite.io/blog/post/announcing-the-appwrite-oss-program) to learn more. --- ## How password hashing algorithms keep your data safe https://appwrite.io/blog/post/password-hashing-algorithms In today's digital world, keeping sensitive information like passwords secure is extremely important. Password hashing algorithms are essential for protecting user credentials and ensuring authentication systems are reliable. In this blog, we'll break down how password hashing algorithms work, highlight their key features, and review some of the most popular and secure algorithms. This will help you make informed choices when setting up password storage and verification in your applications. #### What is a password hashing algorithm? **Password hashing algorithms** are specialized mathematical functions that transform plain text passwords into unique, fixed-size outputs, known as hashes, which are then stored in databases. Through the use of techniques such as salting, adjustable work factors, and memory hardness, modern password hashing algorithms are designed to prevent attacks like rainbow tables and data breaches. Password hashing involves using a hash function to combine a password with a unique value called a salt. This process is repeated multiple times based on a set difficulty level. Sometimes, additional memory usage is included to make the process even more complex and secure. This function would operate as follows: `hash_result = HashFunction(Iterate(Combine(password, salt), work_factor), memory_hardness)` Where: - **`Combine(password, salt)`**: Combines the password and salt, possibly using concatenation or a more complex operation. - **`Iterate(data, n)`**: Repeatedly applies a hash function 'n' times to 'data' to increase computational complexity. - **`HashFunction(data, memory_hardness)`**: Applies the primary hash function to the data and optionally incorporates memory hardness. The result is a fixed-size hash that is unique, deterministic, and resistant to reverse engineering, ensuring the secure storage and verification of passwords. For example, inputting a string `loremipsum` into a hashing function that uses the SHA-256 algorithm would output `5245a52778d684fa698f69861fb2e058b308f6a74fed5bf2fe77d97bad5e071c`. ##### Characteristics of password hashing algorithms Password hashing algorithms have certain characteristics: - **One-way function** Password hashing algorithms should be one-way functions, so it's nearly impossible to reverse-engineer the original password from the hash. This prevents attackers from getting user passwords from stored hashes. - **Deterministic** A password hashing algorithm must always produce the same hash output for a given input to ensure consistency and reliability. - **Fixed-size output** Password hashing algorithms must produce a fixed-size output (hash) regardless of the input size. This is necessary when verifying the password inputted by comparing it with its hash. - **Slow computation** Unlike general hashing algorithms, which prioritize fast computation, password hashing algorithms should be intentionally slow to compute. This characteristic makes it more time-consuming and resource-intensive for attackers to perform brute-force attacks or attempt to guess passwords using a large number of inputs. - **Avalanche effect** A small change in the input should cause a big change in the hash output, making the new output look unrelated to the old one. This makes it hard for attackers to guess the input from the output or find two inputs that create the same hash (collision). For example, the SHA-256 hash for `eight` is `c195d2d8756234367242ba7616c5c60369bc25ced2dcb5b92808d31b58ef217a`, but for `right` is `27042f4e6eca7d0b2a7ee4026df2ecfa51d3339e6d122aa099118ecd8563bad9`, despite having only one different character. - **Pseudorandomness** The output of a password hashing algorithm should look random and evenly spread out, so attackers can't find patterns or guess relationships between the inputs and their hash outputs. - **Resistance to side-channel attacks** Password hashing algorithms should be designed to resist side-channel attacks. An example of this would timing attacks, where an attacker attempts to gain information about the password or hash by analyzing the time taken to compute the hash. - **Adjustable work factor** A good password hashing algorithm should let you adjust the work factor, which means increasing the complexity over time as hardware gets better. This keeps the password hashing process secure and too resource-intensive for dictionary attacks. - **Memory hardness** Some modern password hashing algorithms are designed to be memory-hard, meaning that they require a significant amount of memory to compute the hash. This characteristic makes it more difficult for attackers to perform parallel attacks using specialized hardware, such as GPUs or ASICs, which have limited memory resources. - **Wide adoption and peer review** A reliable password hashing algorithm should have a proven track record, be widely adopted, and have undergone extensive peer review and analysis by the cryptographic community. This ensures that the algorithm has been tested for vulnerabilities and is considered secure for password storage and verification. #### Examples of password-hashing algorithms Here are some modern password-hashing algorithms and their concise descriptions: - **Bcrypt** Bcrypt is a widely used password hashing algorithm based on the Blowfish cipher. It incorporates a salt and an adjustable work factor to slow down the hashing process, making brute-force attacks more time-consuming. Bcrypt is designed to be resistant to side-channel attacks and is considered secure for password storage. - **Scrypt** Scrypt is a memory-hard password hashing algorithm that requires a significant amount of memory to compute the hash, making it more difficult for attackers to perform parallel attacks using specialized hardware. It also supports adjustable work factors and salt usage. Scrypt was specifically designed to protect against hardware-based attacks, such as those using GPUs or ASICs. - **Argon2** Argon2 is a modern, memory-hard password hashing algorithm that won the Password Hashing Competition in 2015. It offers adjustable work factors for both time (computation) and memory usage, providing a balance between security and performance. Argon2 supports salting and has three main variants: - `Argon2i`: optimized for resistance to side-channel attacks - `Argon2d`: optimized for resistance to time-memory trade-off (TMTO) attacks - `Argon2id`: a hybrid version of both of the above - **PBKDF2 (Password-Based Key Derivation Function 2)** PBKDF2 is a widely-used password hashing algorithm that iteratively applies a pseudorandom function, such as HMAC, to the input password and salt. It supports an adjustable work factor, increasing the number of iterations to make the hashing process slower and more resistant to attacks. While PBKDF2 is considered secure, it is not memory-hard and may be more susceptible to hardware-based attacks compared to `scrypt` or `Argon2`. These modern password hashing algorithms are designed to provide increased security for password storage and verification by incorporating features like salting, adjustable work factors, memory hardness, and resistance to various types of attacks. #### Password hashing and Appwrite Appwrite Authentication also leverages password hashing algorithms to allow developers to secure their users’ passwords via password hashing algorithms. Appwrite uses the `Argon2id` algorithm to hash the password when a user creates an account from a client-side application. Appwrite’s SDKs offer a simple abstraction for the Appwrite Accounts API to let developers implement this, like the following example: ```client-web import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const user = await account.create({ userId: ID.unique(), email: 'email@example.com', password: 'password' }); ``` When a developer implements account creation on a server-side application, however, the Appwrite Users API allows them to input a password that has been hashed using any of the following hashing algorithms: - Argon2 - Bcrypt - MD5 - Scrypt - Scrypt Modified - PHPass - SHA An additional side-benefit this offers is that it allows a developer to migrate users from other platforms, such as Firebase, Supabase, and Nhost, using [Appwrite Migrations](https://appwrite.io/docs/advanced/migrations). #### Recap In summary, password hashing algorithms are essential tools for securing sensitive user data, particularly passwords, in modern applications. By understanding these algorithms, developers can make informed decisions when implementing password storage and verification systems. and ensure the security and integrity of their users' data. If you want to implement an authentication system that leverages the power of hashing algorithms simply and quickly, try [Appwrite](https://appwrite.io). --- ## Password protection for developers: best practices https://appwrite.io/blog/post/password-protection Today, we're more connected online than ever before. The internet has made things like shopping, banking, and communicating much easier. This convenience does come at a cost, however. Every important activity we perform on the internet is associated with a digital identity, and this identity is only as secure as we make it. Unfortunately, as seen across multiple incidents in recent history, not everyone’s digital identity on the internet has been maintained securely. The primary culprit in these incidents, such as the [LinkedIn Data Breach](https://www.cbsnews.com/news/linkedin-2012-data-breach-hack-much-worse-than-we-thought-passwords-emails/) in 2012, the [Yahoo Data Breach](https://www.nytimes.com/2017/10/03/technology/yahoo-hack-3-billion-users.html) in 2012-13, or the [GoDaddy Data Breach](https://techcrunch.com/2021/11/22/godaddy-breach-million-accounts/) in 2021, is weak passwords. As software developers, it is essential to remember that our applications’ data is only as secure as the people who use them. Identity verification is crucial, which brings us to the importance of using passwords to keep data and resources safe in our software, or as we call it, "password protection." In this blog, let’s learn why strong password protection is essential and what best practices we can keep in mind while implementing the same. #### Why strong password protection is necessary to implement There are many reasons why we as developers must implement password protection in our software: - **Safeguard user data.** Developers must protect sensitive user information like personal data and financial details. Proper password protection keeps unauthorized people from accessing their online accounts, helping maintain user trust and privacy. - **Protect application integrity.** Weak password protection can lead to unauthorized access, resulting in data and security breaches, application functionality tampering, and other malicious activities. By implementing good password protection and password management, developers can maintain the integrity of their applications and minimize the risk of cyberattacks. - **Prevent unauthorized access.** A strong password protection system stops unauthorized users from accessing restricted parts of an application, like admin panels or user accounts. This ensures only authorized users can access and change data, reducing the risk of data leaks and attacks. - **Reduce support and incident response costs.** A secure password protection system reduces support requests and issues from compromised accounts. This saves time and resources, letting developers focus on improving the application and adding new features. - **Compliance with regulations** Various data protection regulations and standards, such as [GDPR](https://appwrite.io/blog/post/announcing-appwrite-is-gdpr-compliant), [HIPAA](https://appwrite.io/blog/post/what-is-hipaa-compliant), and PCI DSS, require developers to implement secure authentication mechanisms, including strong password protection. Failing to comply with these regulations can lead to legal penalties, fines, and reputational damage. - **Preserve brand reputation** Data breaches and security incidents severely impact a company’s reputation, leading to a loss of user trust and potential financial losses. Developers can help protect their company’s brand and maintain customer confidence by implementing proper password protection measures. #### Best practices to implement password protection Now that we have discussed the importance of password protection, let’s learn about some best practices to remember as you implement password protection policies for your software: - **Enforce strong password policies** Encourage users to create strong, unique passwords by enforcing password requirements such as keeping minimum length, having a mix of uppercase and lowercase letters, numbers, and special characters, and removing personal information such as their name, date of birth, or email address. You can use libraries like `zxcvbn` to estimate password strength and provide suggestions for improvement. - **Maintain password dictionary** Utilize a password dictionary that contains commonly used weak passwords to check for when any user sets their password. This prevents any user from choosing a password that can be easily cracked in scenarios such as brute-force attacks. - **Enforce maximum password age policy and password history** Enforce a maximum password age to ensure all users periodically update their passwords. Maintaining password history prevents compromised passwords from being used to attack a system or application beyond a limited time frame. - **Implement two-factor authentication (2FA)** Add an extra layer of security by requiring users to provide a second form of verification, such as a one-time password sent via email or SMS to their mobile device generated by an authenticator app like Google Authenticator or Twilio Authy. - **Secure password resets** Implement password reset functionality using a secure token that expires after a short period. Send the token via a secure communication channel like email or SMS, and require users to enter the token with their new password to ensure the individual is authorized to update the password. - **Hash and salt passwords** Always store hashed and salted passwords instead of plain text. Hashing is a one-way function that transforms a password into a fixed-length string of characters, while salting adds a random value to the password before hashing to prevent precomputed attacks like rainbow tables. Use modern hashing algorithms like `bcrypt`, `scrypt`, or `Argon2`, which are designed to be slow and computationally expensive, making brute-force attacks more difficult. - **Use rate limiting and account lockout mechanisms** Prevent brute-force attacks by temporarily locking accounts or requiring users to complete a captcha after a certain number of failed login attempts. Implement rate limiting to limit the number of authentication attempts within a specific time frame. This will also give your team time to investigate and prevent attacks before damage is caused. - **Monitor and audit** Regularly monitor and audit your authentication system for suspicious activity and maintain logs to help with incident response and forensic analysis. - **Educate your users** Please help your users understand why setting strong passwords is necessary and how password managers like LastPass, 1Password, or Bitwarden can help them generate and manage their passwords. #### Moving Forward Security is a shared responsibility between developers and users. Collaborating with them in this process is essential to implement better password protection and keep your applications secure. As a developer, by the way, if you would like to try an authentication solution that implements better password protection policies out-of-the-box, look at [Appwrite](https://appwrite.io/docs/products/auth). With various features such as password hashing, session limits, a strong permissions system, password history, a password dictionary, and personal data checks in passwords, [Appwrite Authentication](https://appwrite.io/docs/products/auth) offers an easy-to-implement yet robust authentication system for you to build with. --- ## How to implement 2FA in your applications https://appwrite.io/blog/post/password-protection-2fa Updated on October 6, 2025 With digital security taking more and more importance in our day-to-day lives, relying solely on passwords for user authentication is increasingly recognized as inadequate and the necessity for Two-Factor Authentication (2FA) has become dire for all of us. This blog aims to guide readers through the importance and process of integrating 2FA into their applications, enhancing security, and protecting user data effectively. ### Understanding 2FA 2FA adds an extra layer of security to the traditional login process by requiring users to provide two different types of information before gaining access to an account. This method significantly enhances security by combining something the user knows (like a password) with something the user has (such as a mobile device for receiving SMS codes or an authenticator app) or is (biometrics, for example). The vulnerabilities of single-factor authentication, primarily consisting of password-only systems, lie in their susceptibility to phishing, social engineering, and brute force attacks. 2FA addresses these vulnerabilities by making unauthorized access considerably more challenging for attackers. There are several types of 2FA methods, including: - **SMS-based verification:** Sending a code via text message. - **Authenticator apps:** Using apps like Google Authenticator or Authy to generate time-based one-time passwords (TOTPs). - **Email verification:** Sending a code or link to the user's registered email address. - **Hardware tokens:** Devices that generate a code or use a biometric factor (like a fingerprint). - **Push notifications:** Sending a login approval request to a user's device. ### How 2FA is implemented Implementing 2FA involves several key steps: **1. Select 2FA methods** Choose one or more 2FA methods suitable for your user base, such as SMS verification, authenticator apps, email codes, hardware tokens, or push notifications. **2. User enrollment process** Allow users to opt-in for 2FA through their account settings. Depending on the method, this might include verifying a phone number, installing an authenticator app, or connecting a hardware token. Verify the 2FA method by sending a code or request through the chosen method to ensure it’s set up correctly. **3. Integrate 2FA into the authentication flow** Adjust the authentication flow to incorporate a second step for 2FA. After the user enters their username and password (first factor), prompt them for the second factor. Develop the backend logic to verify the second factor. This includes generating and validating one-time codes or handling responses from push notifications. **4. Ensure secure implementation** Ensure that all communication, especially involving the transmission of 2FA codes or tokens, is encrypted using secure protocols like TLS. Implement security measures like rate limiting and temporary lockouts to prevent brute-force attacks. **5. Provide fallback and recovery options** Offer backup codes during 2FA setup for emergency access and establish a secure process for account recovery in case of lost access to the 2FA method. ### Best practices for implementing 2FA Effective implementation of 2FA enhances security measures for applications, protecting both user data and access. However, to ensure the successful deployment and user adoption of 2FA, it's important to follow best practices that balance security, usability, and accessibility. Here are some key best practices for implementing 2FA: - **User education and communication**: Educate users on the importance and benefits of 2FA to encourage adoption. Clear communication about how 2FA works and why it's being implemented can help reduce resistance and increase user compliance. - **Choose appropriate 2FA methods**: Offer multiple 2FA options to accommodate different user preferences and circumstances. Include methods like SMS verification, authenticator apps, email verification, and hardware tokens. Consider the security and accessibility of each method; for example, authenticator apps are more secure than SMS due to potential vulnerabilities in telecommunication infrastructure. - **Make 2FA enrollment simple**: Simplify the process of enrolling in 2FA. Provide clear, step-by-step instructions and support to guide users through setup. Consider using QR codes for easy pairing with authenticator apps. - **Fallback options and account recovery**: Ensure there are secure fallback options for users who lose access to their second factor, such as backup codes, secondary email verification, or a manual reset process through customer support. Implement a secure, user-friendly account recovery process to maintain access without compromising security. - **Regularly update security measures**: Keep your 2FA implementation up to date with the latest security standards and protocols. Regularly review and update your 2FA processes to address new vulnerabilities and threats. - **Ensure privacy and data protection**: Protect user privacy and the security of 2FA data. Encrypt sensitive data in transit and at rest, and ensure that any third-party services used for 2FA comply with data protection regulations. - **Accessibility and inclusivity**: Make 2FA accessible to all users, including those with disabilities. Ensure that 2FA interfaces are compatible with screen readers and other assistive technologies and provide alternative authentication methods for users who may face challenges with standard 2FA methods. - **User feedback loop**: Create a feedback loop to gather user input on the 2FA process. Use this feedback to make continuous improvements, addressing any usability or technical issues that arise. - **Compliance and legal considerations**: Ensure that your 2FA implementation complies with relevant laws, regulations, and industry standards, such as GDPR, HIPAA, or PCI DSS, depending on your application's focus area. ### Implementing 2FA in your apps with Appwrite With the release of Appwrite 1.5, we have introduced the ability to implement [2FA](/docs/products/auth/mfa) via Appwrite Authentication. Currently, we offer three methods for implementing the second factor: - **Authenticator Apps (TOTP):** Use common authenticator apps like Google Authenticator or Twilio Authy for authentication. - **Emails:** A verified email can be used for authentication, providing a 6-digit code to the user. - **SMS:** A verified phone number can be used for authentication, providing a 6-digit code to the user. ### Frequently asked questions (FAQs) **1. What is Two-Factor Authentication (2FA) and why is it important?** 2FA adds an extra layer of security to your login process by requiring something you know (like a password) and something you have (like a phone or app code). It makes it much harder for attackers to gain access, even if they steal your password. **2. What types of 2FA can I use in my app?** Common methods include SMS codes, email codes, authenticator apps (like Google Authenticator), hardware tokens, and push notifications. Each method adds a second layer of protection against unauthorized logins. **3. Is SMS-based 2FA secure enough?** While SMS-based 2FA improves security over password-only logins, it’s not the most secure option due to risks like SIM swapping. Authenticator apps or hardware tokens are more secure because they don’t rely on telecom networks. **4. How can I add 2FA to my app with Appwrite?** With Appwrite, you can enable 2FA directly through Appwrite Authentication. It supports TOTP apps, email, and SMS verification. You can easily configure these options via the Appwrite Console or API. **5. What should users do if they lose access to their 2FA method?** Always provide backup options—like recovery codes, secondary email, or phone verification. This ensures users can safely regain access without compromising security. ### Resources Visit our documentation to learn more about Appwrite, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our source code. - [Docs](/docs/products/auth/mfa) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Build a chatbot with GPT-4o and Appwrite Functions https://appwrite.io/blog/post/personal-chatbot-gpt-4o Recently, at the OpenAI Spring Update, OpenAI CTO Mira Murati announced the launch of their new flagship model, **GPT-4o**. GPT-4o happens to be OpenAI's fastest and most affordable model so far, which led us to wonder if we could use it to build our very own chatbot. Therefore, in this blog, we will learn how to use Appwrite Functions and the OpenAI GPT-4o API to build a personal chatbot. #### Setting up the OpenAI Platform To use the OpenAI GPT-4o API, we must first create an API key on their platform. To do that, we must first create an account on the [OpenAI platform](https://platform.openai.com/). Once the account is set up and a project is created, we can visit their [API keys](https://platform.openai.com/account/api-keys) page and create an API key. Ensure you copy and save this key in a safe place, as the OpenAI platform will not let you view the key after it is created. ![Create an OpenAI API Key](/images/blog/personal-chatbot-gpt-4o/openai.png) > Note: To use the GPT-4o API, your account must be upgraded to Usage tier 1 (you must purchase at least $5 worth of credits). To learn more, visit their [Usage tiers documentation](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-one). #### Initializing the Appwrite Function Now that we have our OpenAI API Key, let's prepare the function on [Appwrite](https://cloud.appwrite.io/). Go to your Appwrite project and visit the Functions page. From there, we will create a function using the Node.js starter template. ![Create a new Appwrite Function](/images/blog/personal-chatbot-gpt-4o/functions.png) Once the function is ready, we must visit the Settings tab on the Function page and add the following environment variables: - `OPENAI_API_KEY`: API Key from our OpenAI account - `OPENAI_MAX_TOKENS`: Maximum number of tokens that the OpenAI response should contain (we'll set this as `512`) Once that is done, visit the function's GitHub repository and clone the project. #### Developing the project Once your function's GitHub repository is ready, clone it to your local device and enter the directory. You will notice a directory structure as follows: ``` . ├── src/ │ └── main.js ├── README.md ├── package-lock.json └── package.json ``` ##### Preparing the UI To get our chatbot up and running, we must first develop a UI you can interact with. We will create a folder `static` at the root level of our project directory and add a file `index.html`. We will then add the following HTML to this file: ```html Prompt ChatGPT demo

    Prompt ChatGPT demo

    Use this page to test your implementation with OpenAI GPT-4o API. Enter text and receive the model output as a response.

    ``` ##### Preparing our utility functions To simplify the function logic, we create additional utility functions to - return the contents of a file in the static folder as a string (to send our UI to the client browser) - throw an error if any of the keys are missing from the object (to check for missing environment variables) For that, we shall enter the `src` folder and create a file `utils.js` with the following code: ```js import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); /** * Returns the contents of a file in the static folder * @param {string} fileName * @returns {string} Contents of static/{fileName} */ export function getStaticFile(fileName) { return fs.readFileSync(path.join(staticFolder, fileName)).toString(); } /** * Throws an error if any of the keys are missing from the object * @param {*} obj * @param {string[]} keys * @throws {Error} */ export function throwIfMissing(obj, keys) { const missing = []; for (let key of keys) { if (!(key in obj) || !obj[key]) { missing.push(key); } } if (missing.length > 0) { throw new Error(`Missing required fields: ${missing.join(', ')}`); } } ``` ##### Creating the function logic Now that our chatbot UI and utility functions are ready, we can develop our final function logic. For that, we must first install the `openai` NPM package. Open your terminal in the project directory and run the following command: ```bash npm i openai ``` After that is done, we must visit the `src/main.js` file and replace the existing code with the following: ```jsx import OpenAI from 'openai'; import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res }) => { throwIfMissing(process.env, ['OPENAI_API_KEY']); if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } try { throwIfMissing(req.body, ['prompt']); } catch (err) { return res.json({ ok: false, error: err.message }, 400); } const openai = new OpenAI(); try { const response = await openai.chat.completions.create({ model: 'gpt-4o', max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: req.body.prompt }], }); const completion = response.choices[0].message.content; return res.json({ ok: true, completion }, 200); } catch (err) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } }; ``` This function will return our chatbot UI on any `GET` request to the function and will use the prompt sent in the request body and use the OpenAI GPT-4o API to generate and return a response otherwise. At this point, our project directory structure should look as follows: ``` . ├── src/ │ ├── main.js │ └── utils.js ├── static/ │ └── index.html ├── README.md ├── package-lock.json └── package.json ``` ##### Testing the function Once you've completed all the aforementioned steps, you can push the code to our GitHub repository, at which point Appwrite Cloud will automatically deploy the changes to your function. ![Appwrite Function deployments](/images/blog/personal-chatbot-gpt-4o/deployment.png) You can then go ahead and test your function by opening the function domain in your browser. Here is what an example of this looks like: [apwr.dev/gpt-4o-demo](https://apwr.dev/gpt-4o-demo) ![Final project UI](/images/blog/personal-chatbot-gpt-4o/final.png) #### Next steps And with that, you have successfully deployed your personal chatbot powered by GPT-4o and Appwrite Functions. If you liked this project or want to investigate the full project code, visit our [GitHub repository](https://github.com/appwrite-community/gpt-4o-function). Additionally, if you would like to learn more about Appwrite Functions, here are some resources: - [Appwrite Functions docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## How Appwrite Databases can replace your PlanetScale database https://appwrite.io/blog/post/planetscale-databases-alternative On March 6th, 2024, PlanetScale announced their plan to sunset their free tier, impacting developers and their projects worldwide. Many devs are now forced to migrate away and find a new database solution. As an open-source backend-as-a-service platform, Appwrite can be the solution to finding a PlanetScale alternative. In this article, we compare PlanetScale Databases to Appwrite Databases so you can understand whether Appwrite’s free-tier Databases are a feasible option. ### How Appwrite can help When comparing PlanetScale with Appwrite, it's essential to understand that they serve somewhat different purposes. PlanetScale is known to be a highly scalable, MySQL-compatible database service, focusing on providing a robust solution for scaling relational databases horizontally. Appwrite, on the other hand, offers a more comprehensive set of products for your backend, including Databases, Authentication, Functions, Storage, and Messaging. If PlanetScale is a great screwdriver set, Appwrite is a Swiss Army Knife for the developer. #### An all-in-one package Appwrite is an open-source, all-in-one cloud development platform. Use built-in backend infrastructure and web hosting, all from a single place. Here are the core products Appwrite offers: - **Authentication:** Manage user registration, login, and account functionalities. It supports multiple sign-in methods as well as two-factor authentication. - **Database:** Create and manage databases to store and query your application's data. - **Storage:** Securely store and manage files used within your application. Appwrite also allows for file previews and image manipulations. - **Functions:** Execute custom code within a secure environment to implement specific application logic without managing servers. - **Messaging:** Communicate with your users through push notifications, emails, and SMS text messages. - **Realtime:** Enable real-time updates and synchronization within your application for features like chat or collaborative editing. [Signing up for Appwrite Cloud](https://cloud.appwrite.io/register) or self-hosting it on your server doesn't just give you access to one of these, but all of these. Let’s delve further into some of our developer experience and more database-oriented benefits. #### API-first approach A major difference that Appwrite has from other database-as-a-service offerings like PlanetScale is that instead of directly querying the underlying database, we offer APIs to interact with the product. This approach offers several advantages: - **Simpler interactions with data:** Developers can perform CRUD actions easily through straightforward HTTP requests. Knowledge of SQL isn’t mandatory anymore. - **Works with any language**: Any programming language or platform capable of making HTTP requests can consume Appwrite Databases, making it highly versatile. - **Additional security**: Integrated authentication and access control ensure that data is only accessible to authorized users, keeping your application secure. - **Easier education:** Detailed API documentation and SDKs for various languages offer a smooth developer experience, making it easy to get started and efficient to use. Leveraging an API-first approach, we have built a lot of abstractions to interact with the underlying MariaDB database in Appwrite and not just to perform CRUD actions on the tables but also to handle the creation and management of databases, tables, columns, indexes, and relationships. This experience extends beyond Appwrite Databases to all other Appwrite products. #### SDKs and developer tooling To decrease the friction in building for all developers and complement our API-first approach, Appwrite offers over 10 SDKs across languages such as JavaScript, Python, Dart, and Kotlin, as well as a CLI that works on Linux, MacOS, and Windows-based systems. - **Simplicity:** SDKs eliminate the need for developers to write code from scratch to interact with Appwrite's API, saving them time and effort. - **Consistency and reliability**: SDKs and the CLI ensure that applications use Appwrite’s API correctly and efficiently while retaining type safety, reducing the risk of errors. - **Enhanced productivity:** By automating and simplifying tasks, SDKs and CLI help developers accomplish more in less time, boosting productivity. They make integrating Appwrite’s features, test applications, and deploy updates easier. This way, any developer transitioning to Appwrite can get started with our stack quickly and easily. #### Self-hosting Appwrite Since Appwrite is open-source, we have simplified the process to self-host it. This has several benefits: - **Control**: You gain complete authority over your server environment and configurations. - **Privacy and security**: You can keep all data in-house for enhanced security and compliance with data protection laws. - **Cost efficiency**: You might potentially lower expenses over time by optimizing resources and avoiding third-party fees. - **Reduced latency:** Hosting closer to your clients may help improve application performance. - **Avoid vendor lock-in**: You're not tied to the services, pricing, and terms of a specific cloud provider. Currently, Appwrite uses Docker Compose to deliver all the necessary services and components necessary for the platform. We provide a single installation command to set up an Appwrite instance on any system with Docker. ```bash 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:1.8.1 ``` We have a dedicated [self-hosting guide](/docs/advanced/self-hosting) in our docs for more info. #### Managed databases on Appwrite Cloud With Appwrite Cloud, our Database offering is fully managed. This means that our team handles any administrative tasks associated with running the underlying database. This carries several key benefits: - **Automated backups**: Regular snapshots of the database are taken automatically to ensure data can be restored in case of loss. - **Scaling**: Resources (CPU, memory, storage) are adjusted by our team according to demand, often with minimal or no downtime. - **Monitoring and alerts**: There is continuous monitoring of database performance and health, with alerts for any issues, which are team takes care of. - **Maintenance and updates**: We handle updates to the database software and operating system, applying patches and security fixes as needed. - **Enhanced security:** We maintain robust security measures to protect your data. One of the biggest advantages of Appwrite Cloud is that these benefits are not just restricted to paid customers but also available to consumers of our free tier. #### Pricing plans With Appwrite Cloud, we have mindfully designed our pricing plans to be accessible to most developers. Right now, we offer two [pricing plans on Appwrite Cloud](/pricing). - **Free plan** - A free tier for every developer working on a side project or SaaS product that can thrive on our Free plan limits. - Free of cost - **Pro plan** - A plan that supports your ambitions to scale your production project and allows you to grow. - Comes with flexible budget control tools to ensure you never get a surprise bill. - $15, per month, per member. ![Appwrite pricing overview](/images/blog/planetscale-databases-alternative/pricing.png) For databases, here are the differences between both plans: | Category | Free plan | Pro plan | | ------------------- | ------------------- | ------------------- | | Databases | 1 per project | Unlimited | | Documents | Unlimited | Unlimited | | Reads & Writes | Unlimited | Unlimited | | Dedicated databases | No | Yes | You can also look at PlanetScale [pricing](https://planetscale.com/pricing) to understand how Appwrite compares. ### Moving forward There is no denying that PlanetScale has done a fantastic job developing a scalable and robust database offering. They are a massive inspiration in the database space and the larger developer tooling ecosystem. That being said, there are gaps in the developer community that PlanetScale chooses not to prioritize due to their business growth plans, which we can help with. Free users have formed an influential part of the Appwrite community and have made a difference not just as consumers but also as builders, public speakers, contributors, etc., who have constantly criticized and shared feedback, added ideas and suggestions, and made Appwrite a much better platform. We hope we will see more such developers give us a shot in the days to come. Learn more about Appwrite by visiting our [docs](/docs) and joining our [Discord community](https://appwrite.io/discord). --- ## Build and deploy a personal portfolio on Appwrite Sites https://appwrite.io/blog/post/portfolio-template-sites Today, a personal portfolio website is no less than real estate on the internet for developers. It's your digital home address, where people can discover who you are, what you do, how they can contact you, and so much more in between. However, many of us don't have our portfolio websites ready despite their importance because, even with modern vibe coding tools, a clean and straightforward portfolio is much harder to design than expected. That's why, as part of the Sites templates, Appwrite offers a portfolio template that you can deploy in just a few short steps. ### Overview of the portfolio template The Appwrite portfolio template is a personal portfolio app for developers. Built with Next.js and styled with Tailwind CSS, it features several pages: - Landing page with an about section and project listing - Individual project pages - Contact me page with an email form (currently just logs form data on the console) ![Deployed site](/images/blog/portfolio-template-sites/deployed.png) ### Deploy the portfolio template on Appwrite Firstly, you must head to Appwrite Cloud and [create an account](https://cloud.appwrite.io/console/register) if you haven't already (or [self-host Appwrite 1.7](https://appwrite.io/docs/advanced/self-hosting)). Next, create your first project, which will lead you to the project overview page. ![Get started](/images/blog/portfolio-template-sites/get-started.png) Head to the **Sites** page from the left sidebar, click on the **Create site** button, and select the **Clone a template** option. This will take you to the Appwrite Sites templates listing, where you should select **Portfolio** under the **Use case** category on the left sidebar. This will show you numerous templates, some developed by our team and some by our partners, from which you must click on the `Portfolio template` option. ![Site templates](/images/blog/portfolio-template-sites/templates.png) After selecting the template, connect a GitHub repository now (you can do this later, too). Leave the production branch and root directory as is, update the domain name if you want, and click the **Deploy** button. You can watch the deployment logs as Appwrite builds your site. ![Deployment logs](/images/blog/portfolio-template-sites/deployment-logs.png) After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. You can view the site by clicking the **Visit site** button, or view the site configuration (deployments, logs, domains, usage, and settings) by clicking the **Go to dashboard** button. ![Congratulations](/images/blog/portfolio-template-sites/congrats.png) ### Making changes and deploying them to the site To learn how to make changes in the portfolio site, let's update the contact form action to email you whenever someone submits a message. For this demo, we shall use Resend, a popular API service for sending emails. First, create an account on [Resend](https://resend.com/), then go to the **API Keys** tab in the left sidebar to create a new API key. Save this API key for later usage. ![Resend API key](/images/blog/portfolio-template-sites/resend-api-key.png) > Note: While this isn't mandatory for the demo, for production apps, you should add and verify a [domain](https://resend.com/docs/dashboard/domains/introduction) to send emails via Resend. Next, you must update the environment variables of our Appwrite Site. Head back to your Appwrite project, visit **Sites** from the left sidebar, and click on your portfolio site. Head to the **Settings** tab of your site, scroll down to the **Environment variables** section, and create the following environment variables: - `RESEND_API_KEY`: The Resend API key you created earlier - `EMAIL_ADDRESS`: The email address you want to receive contact form messages on ![Site environment variables](/images/blog/portfolio-template-sites/env-vars.png) Lastly, clone the repository Appwrite created for your portfolio site. Enter the directory, and install Resend's Node.js library by running the following command: ```js npm install resend ``` Then, head to the `src/actions/contact.ts` file and update it to the following: ```js 'use server' import { Resend } from "resend" export const submitContactForm = async (formData: FormData) => { try { const rawFormData = { name: formData.get('name'), email: formData.get('email'), subject: formData.get('subject'), message: formData.get('message'), } console.log(rawFormData); const resendApiKey = process.env.RESEND_API_KEY; const emailAddress = process.env.EMAIL_ADDRESS; if (!resendApiKey || !emailAddress) { throw new Error('Missing environment variables for Resend API key or email address'); } const resend = new Resend(resendApiKey); await resend.emails.send({ from: 'Acme ', // You can switch this out with an email of your domain once added to Resend to: [emailAddress], subject: `New message from ${rawFormData.name}`, text: `Name: ${rawFormData.name}\n\nEmail: ${rawFormData.email}\n\nSubject: ${rawFormData.subject}\n\nMessage: ${rawFormData.message}` }); } catch (error) { console.error('Error sending email:', error); throw new Error('Failed to send contact form. Please try again later.'); } } ``` You can now commit these changes and push them to GitHub, automatically deploying the updated site to Appwrite. ### Frequently asked questions (FAQs) **1. What’s the easiest way to host my developer portfolio for free?** Appwrite Sites is one of the simplest and developer-friendly ways to host a portfolio for free. It gives you instant static site hosting, GitHub integration for automatic deployment, and built-in SSL, all without needing to manage servers or set up custom CI/CD. **2. Can I connect my custom domain to Appwrite Sites?** Yes. Once your portfolio is deployed, you can go to your Site’s Settings → Domains in Appwrite and add your custom domain (like yourname.dev). Appwrite automatically provisions SSL certificates, so your site stays secure and uses HTTPS by default. **3. Can I update my portfolio automatically whenever I push to GitHub?** Yes. Once your GitHub repo is linked, every git push to your production branch triggers an automatic rebuild and deployment on Appwrite. It’s similar to Vercel or Netlify, but fully integrated into the Appwrite ecosystem. **4. What’s a good alternative to Vercel or Netlify for hosting personal projects?** If you’re looking for an open-source, privacy-friendly alternative, Appwrite Sites is a solid choice. It offers the same ease of use, Git-based deployments, custom domains, and build logs, while also giving you access to Appwrite’s full backend stack, including authentication, databases, storage, and serverless functions. Perfect if you plan to grow your personal project into something bigger later. **5. How do I add analytics to my portfolio site?** You can integrate analytics tools directly into your portfolio’s code. Since Appwrite Sites supports frameworks like Next.js or React, simply paste your tracking snippet (from tools like Plausible, Umami, or Google Analytics) inside your app’s layout or component. Once deployed, Appwrite will serve your site with those scripts included, allowing you to track visits and engagement automatically. ### Next steps And with that, the personal portfolio template is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. For more information about Appwrite Sites: - [Appwrite Sites product docs](/docs/products/sites) - [Quick start to deploy web app](/docs/products/sites/quick-start) - [Deploy Appwrite Site templates](/docs/products/sites/templates) - [Appwrite Discord server](/discord) --- ## The cost of convenience: preventing password sharing https://appwrite.io/blog/post/preventing-password-sharing Over the last few years, password sharing has emerged as a notable challenge for developers and companies, intertwining concerns of security, privacy, and revenue. Although seemingly benign, this practice has significant implications, especially for platforms with seat-based pricing models. Netflix made waves in 2023 with its [ban on account sharing](https://www.techradar.com/news/netflix-password-sharing), spotlighting the potential revenue loss businesses face when users circumvent subscription models designed to reflect actual usage. Beyond financial repercussions, the critical importance of addressing password sharing simply cannot go unaddressed today. ### Understanding the risks of password sharing Despite its commonality in both personal and professional contexts, password sharing poses significant risks for businesses across various sectors. This practice can undermine the security infrastructure, compromise sensitive data, and even affect the financial health of an organization. Here are the key risks associated with password sharing for businesses: - **Security breaches**: Increases vulnerability to unauthorized access and cyberattacks. - **Data leaks**: Raises the likelihood of unintentional exposure of sensitive information. - **Compliance violations**: Can lead to breaches of regulatory standards (e.g., GDPR, HIPAA), resulting in fines and sanctions. - **Diminished accountability**: Makes it difficult to trace activities and attribute actions back to individual users accurately. - **Operational disruptions**: Causes inefficiencies and potential system lockouts affecting productivity in certain scenario (for instance, if shared accounts are locked out due to suspicious activities). - **Increased IT support costs**: Requires additional resources to manage and resolve access issues. - **Erosion of trust**: Undermines customer and partner confidence if security is compromised. - **Loss of intellectual property**: Enhances the risk of proprietary information theft. ### Best practices for secure authentication To prevent password sharing and enhance security, businesses and software development teams can adopt various authentication best practices. These methods secure access to systems and data and encourage individual accountability and compliance with regulatory standards. Here are some of the most effective authentication best practices: - **Enforce multi-factor authentication:** Use MFA to require multiple verification factors, making unauthorized access more difficult. - **Passkeys and passwordless authentication:** Adopt passkeys or passwordless methods that rely on cryptographic keys or biometrics, eliminating the need for passwords and reducing phishing risks. - **Time-based One-Time Passwords (TOTP):** Deploy TOTPs that expire quickly to minimize the risk of password theft. - **Enable role or policy-based access control:** Apply RBAC or PBAC to ensure users access only what they need for their roles, minimizing unnecessary access and reducing the incentive for password sharing. - **Password policies:** Enforce regular password changes and complexity requirements judiciously to discourage password sharing without burdening users. - **Education and awareness:** Foster security awareness through education on the importance of secure authentication practices and the risks of password sharing. ### Technological solutions to prevent password sharing To prevent password sharing and enhance security, several tools and technologies are available that help organizations control access and monitor usage. These include: #### 1. Authentication systems - **Multi-factor authentication (MFA)**: Tools like Duo Security, Authy, and Google Authenticator add an extra layer of security by requiring additional verification beyond just a password. We at Appwrite have also recently announced our own 2FA solution for consumers of our Authentication product, which you can read about in our [product announcement](https://appwrite.io/blog/post/announcing-two-factor-authentication). - **Single Sign-On (SSO) providers**: Solutions like Okta, Azure Active Directory, and OneLogin allow users to access multiple applications with a single set of credentials, reducing the temptation to share passwords. #### 2. Passwordless authentication technologies - **Biometric authentication systems**: These systems use fingerprints, facial recognition, or iris scans for secure access, available through smartphones and specialized hardware. - **FIDO2 security keys**: Devices such as YubiKey or Titan Security Key support passwordless login, offering a physical hardware-based second factor. #### 3. Access management tools - **Privileged Access Management (PAM) solutions**: CyberArk, BeyondTrust, and Thycotic secure, control, and monitor access to critical assets and infrastructure, limiting the need for shared passwords. - **Role-Based Access Control (RBAC) Systems**: Built into many cloud platforms (AWS IAM, Azure Role-Based Access Control) and enterprise systems to ensure users have access only to what they need based on their role. #### 4. User and entity behavior analytics - **Behavioral analytics tools**: Solutions like Exabeam and Splunk use analytics to detect abnormal behavior that might indicate shared passwords or other security risks. #### 5. Educational and policy management tools - **Security awareness training platforms**: KnowBe4, Proofpoint, and Mimecast offer training to educate employees about the risks of password sharing and promote secure practices. - **Policy management software**: Tools for creating, managing, and enforcing security policies can help organizations maintain standards that discourage password sharing. ### Developing a culture of security among developers Cultivating a culture of security within software development teams is crucial today. This entails a collective commitment to upholding security protocols, including the prevention of password sharing. By embedding security as a core value, organizations can foster an environment where best practices are not only understood but embraced. This cultural shift is instrumental in ensuring that security considerations are integrated into every aspect of the development process, thereby safeguarding against potential vulnerabilities. As the software development community moves forward, the collective effort to combat password sharing will undoubtedly contribute to a more secure environment for all. --- ## April product update: The Appwrite Network, FlutterFlow auth library, and RxDB integration https://appwrite.io/blog/post/product-update-april-2025 Welcome back to the April product update. Last month, we dropped some big updates: - The Appwrite Network is now live - Appwrite authentication kit library for FlutterFlow - RxDB Integration is here So let’s dive in. ### New cloud regions ![The Appwrite Network](/images/blog/product-update-april-2025/the-appwrite-network.png) In April, we launched the Appwrite Network, our vision for a globally distributed cloud regions and edge locations. With the launch of two new cloud regions (New York City and Sydney) and more to come soon, you can choose where your data sits and optimise it for reduced latency and improved performance. So you can now create your projects in: - New York City (NYC) - Sydney (SYD) - Frankfurt (FRA) [Read the announcement](http://appwrite.io/blog/post/the-appwrite-network) ### Appwrite authentication kit for FlutterFlow ![Authentication kit for FlutterFlow](/images/blog/product-update-april-2025/FlutterFlow.png) Did you know Appwrite now has an authentication kit library for FlutterFlow? Which means now you don’t have to build authentication from scratch for your FlutterFlow apps. Instead, you can simply use the kit to implement secure user authentication with just a few clicks. Check out our full documentation for how to get it up and running. [Visit documentation](https://appwrite.io/integrations/flutterflow-auth-kit) ### RxDB replication plugin ![RxDB replication plugin](/images/blog/product-update-april-2025/RxDB.png) Offline support for applications has been the second most requested feature on our GitHub repository. Therefore, we announced a direct integration of Appwrite databases with RxDB, a local-first NoSQL database designed for JavaScript apps. So now it's easier than ever to build real-world apps that stay in sync online and offline. [Visit documentation](https://appwrite.io/docs/products/databases/offline) ### Big announcement coming soon ![PH coming soon](/images/blog/product-update-april-2025/PH-comingsoon.png) We teased it last time, and now it's almost here. We’ve got a big announcement very soon. No spoilers (yet), but all we can say is that this announcement will change the way you build with Appwrite. We’ve been heads down, building something big, and we’re just as excited as you are to bring it to life. So visit our ‘Coming Soon’ page and click **Notify Me** to get an email when we go live. [Go to product hunt](https://www.producthunt.com/products/appwrite) ### Monthly Community Recognition ![Community recognitions](/images/blog/product-update-april-2025/community-recognition.png) This month, we’re excited to feature **Astro Rentals** as part of our Monthly Community recognitions. **Astro Rentals** application is an Airbnb-style website clone built using Astro JS, Clerk, and Appwrite. A big shout out to [PetiPois](https://github.com/petipois). Check out her website, Astro Rentals, [here](https://astrorentals.netlify.app/). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [Announcing the Appwrite Network](https://appwrite.io/blog/post/the-appwrite-network) - [How to change regions in Appwrite Cloud using migrations](https://appwrite.io/blog/post/change-regions-with-migrations) - [Why multi-cloud is taking over](https://appwrite.io/blog/post/why-multi-cloud-is-taking-over) - [How to reduce cloud latency](https://appwrite.io/blog/post/how-to-reduce-cloud-latency) - [20 security best practices for vibe coding](https://appwrite.io/blog/post/vibe-coding-security-best-practices) **Build** - [Build an offline-first journal app with RxDB and Appwrite](https://appwrite.io/blog/post/offline-first-journal) **Watch** - [New regions available on the Appwrite network](https://youtu.be/hx6RZka3OGk?si=g709ZjTzwuUVuB4f) - [Change cloud regions with migrations](https://www.youtube.com/watch?v=Ya2R6dD_xxE) - [Build and deploy a Full Stack React admin dashboard for a travel agency](https://youtu.be/xZ1ba-RLrjo?si=n994ESOxq1qf1YbJ) ### What’s to come That’s a wrap for April. A big announcement is just around the corner. Stay tuned! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## August product update: Init | 1.6 release to Cloud and Self-hosted https://appwrite.io/blog/post/product-update-august Great news for all of you who cannot wait to get started with all the newly announced products and features: 1.6 is now available on Cloud and Self-hosted. Go to the Init page to find all relevant content and documentation. Or keep reading to learn more about what has been released. [Visit Init](https://appwrite.io/init) ![1.6 release](/images/blog/init-recap-august/localdev.png) ### Local Development On day 0, we released Local Development, which allows you to develop and test functions on your local machine. Running functions locally has many benefits. Local development allows faster debugging, a quicker feedback loop, and a better overall experience. When running your function locally during development, you are more likely to test it every step of the way instead of shipping multiple big changes all at once. Watch the product tour for more, or visit our [documentation](https://appwrite.io/docs/products/functions/develop-locally) to get started. [Watch product tour](https://youtu.be/GMwrHds4Oa8) ![1.6 release](/images/blog/init-recap-august/newcli.png) ### New Appwrite CLI A good, easy-to-use CLI makes all the difference in your experience, so we're really excited to introduce a new version of the Appwrite CLI. It allows you to test your functions locally, easily migrate databases, and more. The new CLI’s main features are: - **Local development**: Test your Appwrite function locally - **Data sync**: Push and pull data between Appwrite and your CLI in a non-destructive way. - **Headless login**: Login to your Appwrite account in a non-interactive manner for CI/CD pipelines Besides those features, this release includes many bug fixes and optimizations. Watch the product tour for more, or visit our [documentation](https://appwrite.io/docs/tooling/command-line/installation) to get started. [Watch product tour](https://youtu.be/nlzFl3AHlog) ![1.6 release](/images/blog/init-recap-august/functionseco.png) ### Functions ecosystem Appwrite loves Functions, so it's with great pleasure that we're introducing a host of new updates to the Functions ecosystem. - Faster Functions cold-starts - Dynamic API keys - Delayed executions - Binary executions - Execution and deployment filtering Functions are now faster, smoother, better, and more secure. Watch the product tour for more. [Watch product tour](https://youtu.be/G2UBTOBumII) ![1.6 release](/images/blog/init-recap-august/go.png) ### Go support We've added a Go SDK and Function runtime to allow you to take advantage of Go’s speed to handle complex tasks like calculations, statistics, or file transformations more efficiently. The new runtime allowed for up to 3x faster cold starts and a base execution time of less than 1 millisecond. That means your functions will start running much quicker, and once they're up, they'll fly. #### What can you build with Go? Are you excited to start building with Go? Here are a few use cases to get you started: - **Create APIs:** Build RESTful APIs quickly and efficiently. - **Process data:** Handle complex data transformations and analysis. - **Image processing:** Resize, crop, or filter images on the fly. - **File conversion:** Convert files between different formats. - **Machine learning:** Deploy machine learning models for real-time predictions. [Read announcement](https://appwrite.io/blog/post/announcing-go-support) ![1.6 release](/images/blog/init-recap-august/mock.png) ### Mock numbers & Session alerts We've listened to your feedback and are introducing two new features to simplify phone authentication testing and bolster account security. Mock numbers allow you to set up a list of phone numbers with a predefined, static verification code, which is perfect for app reviews and testing. Session alerts will help your users maintain their account security. [Read announcement](https://appwrite.io/blog/post/announcing-mock-numbers-session-alerts) ### Community recognitions This month, we’re excited to feature Ayush Kumar as part of our Monthly Community Recognitions. The Next.js boilerplate with Appwrite integration helps you launch your SAAS faster with **+10 pre-built** features and several components. Visit https://spiralsaas.dev/ to learn more. If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources - [Read](https://appwrite.io/blog/post/functions-local-development-guide): Local serverless function development with the new Appwrite CLI - [Read](https://appwrite.io/blog/post/serverless-functions-best-practices): Serverless Functions 101: Best practices - [Read](https://appwrite.io/blog/post/ci-cd-examples-in-appwrite): CI/CD examples in Appwrite CLI - [Read](https://appwrite.io/blog/post/appwrite-vs-firebase-vs-supabase-functions-comparison): Comparing serverless functions: Appwrite vs. Supabase vs. Firebase - [Read](https://appwrite.io/blog/post/how-to-leverage-dynamic-api-keys-for-better-security): How to leverage Appwrite Dynamic Keys for enhanced security - [Read](https://appwrite.io/blog/post/3-things-you-can-build-with-go-runtime): 3 things you can build with the Go runtime - [Read](https://appwrite.io/blog/post/what-is-golang): Why should you use Golang in your app? - [Read](https://appwrite.io/blog/post/mock-numbers-best-practices): Mock numbers for phone sign-in: Use cases and best practices - [Read](https://appwrite.io/blog/post/designing-init-event-logo): Designing Init: Event logo and theme - [Read](https://appwrite.io/blog/post/open-source-contributors-16): Celebrating the open source contributors for Appwrite 1.6 - [Read](https://appwrite.io/blog/post/swift-101-build-a-library-with-swift-package-manager): Swift 101: how to build a library with Swift Package Manager - [Read](https://appwrite.io/blog/post/best-pagination-technique): Best database pagination technique - [Read](https://appwrite.io/blog/post/image-classification): A technical deep dive into image classification - [Read](https://appwrite.io/blog/post/how-to-back-up-your-appwrite-data): How to back up and restore your Appwrite data - [Watch](https://www.youtube.com/watch?v=ENnG7GusuO4): Here's how you do auth with 100% SSR in Next.js ### What's to come Thank you all for making Init another week to remember. Here is a sneak peek of what we will be doing in the next weeks: - We are adding new regions and backups to Cloud - A new Student Program in collaboration with GitHub - The Scale plan will be released soon with supporting features Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## August product update: Cloud GA, new TablesDB UI & Sites Hackathon https://appwrite.io/blog/post/product-update-august-2025 Welcome back to the August Product Update. This month, we’ve got some significant updates, from Appwrite Cloud, which is now generally available, and a brand new look for Appwrite Databases. On top of it, Appwrite’s very first Sites Hackathon is also now live. So, here’s what to expect: - Appwrite Cloud GA - Sites Hackathon - New Cloud region - New Database UI - Terminology update - And more! Let’s dive in. ### Appwrite Cloud is now generally available ![Appwrite Cloud GA](/images/blog/product-update-august-2025/ga.png) After more than 26 months of Appwrite Cloud, we removed the beta tag. Appwrite is now officially generally available (GA). With Appwrite Cloud now powering over 300,000 projects, we’re proud and excited to announce that we have transitioned to General Availability. [Read the announcement](https://appwrite.io/cloud-ga) ### Appwrite Sites Hackathon ![Appwrite Sites Hackathon](/images/blog/product-update-august-2025/hackathon.png) After receiving multiple requests from the community, the **Appwrite Sites Hackathon** is finally here and underway. You have until **September 12** to showcase your creativity and compete for great prizes, including the exclusive Appwriter Keyboard and other prizes. [Register now](https://hackathon.appwrite.network/) ### New Cloud region ![New Cloud region](/images/blog/product-update-august-2025/new-region.png) We’re happy to share that the new cloud region, San Francisco (SFO), is now available on all Free, Pro, Scale, and Enterprise plans. You can create a new project or migrate your existing project to the SFO region. Want to switch to a new region? Here are some resources to help you: - Video: [Changing regions with migrations](https://www.youtube.com/watch?v=Ya2R6dD_xxE) - Blog: [How to change regions in Appwrite Cloud using migrations](/blog/post/change-regions-with-migrations) ### New TablesDB API with updated terminology ![New TablesDB API with updated terminology](/images/blog/product-update-august-2025/terminology.png) Appwrite Databases now feels more relational. We’ve updated the terminology to match the relational model that developers are most familiar with. Here’s what’s changed: - Collections are now Tables - Documents are now Rows - Attributes are now Columns To support the terminology change, we’ve added the TablesDB API. A new API layer that helps you work with relational database concepts like tables, columns, and rows, without requiring any changes to your existing apps. The old document-based methods are now deprecated and will no longer receive major upgrades. They will continue to receive security patches and essential maintenance, and we are ensuring backwards compatibility with those methods for the API, so there’s no need to migrate immediately if a specific issue arises. However, we recommend adopting the new TablesDB API for future projects to take advantage of ongoing improvements and new capabilities. [Learn more](/blog/post/announcing-appwrite-databases-new-ui) ### TablesDB UI ![TablesDB UI](/images/blog/product-update-august-2025/new-ui.png) We introduced a completely new TablesDB UI for Appwrite Databases. This new interface lets you experience a spreadsheet‑like editing workflow inside the Console. You can now: - Edit records inline - Perform bulk actions - Navigate with arrow keys and shortcuts [Learn more](/blog/post/announcing-appwrite-databases-new-ui) ### Atomic numeric operations ![Atomic numeric operations](/images/blog/product-update-august-2025/atomic.png) This new database feature optimizes your app’s performance and saves you time. You can safely update numeric fields (likes, retries, stock, or scores) directly on the server, without conflicts or race conditions. No need to fetch the full document just for a single increment/decrement operation. [Learn more](/blog/post/announcing-atomic-numeric-operations) ### Timestamp Overrides ![Timestamp Overrides](/images/blog/product-update-august-2025/timestamp.png) With the help of timestamp overrides, you can now retain original timestamps during imports to keep audits, analytics, and user timelines accurate. Whether importing a few thousand records or years of historical data, this feature allows you to control timestamps when needed and let Appwrite handle them when you don’t. [Learn more](/blog/post/announcing-timestamp-overrides) ### Community recognitions ![Community recognitions](/images/blog/product-update-august-2025/community.png) We’re excited to feature [WriteWise](https://writewise-os.streamlit.app/) as part of our Monthly Community recognitions this month. WriteWise is an AI RAG (Retrieval-Augmented Generation) system that can browse through Appwrite docs and provide code examples or explanations based on your questions. A big shout-out to osasgentech. If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [Exploring AI and vibe coding: Insights from the Appwrite developer community](/blog/post/ai-vibe-coding-insights) - [Simplifying social media management with automation and AI with StoreAlert and Appwrite](/blog/post/customer-story-socialaize) - [Announcing an improved Appwrite Databases. A completely new look, feel, and experience](/blog/post/announcing-appwrite-databases-new-ui) - [Announcing Opt-in relationship loading: Granular control for smarter data fetching](/blog/post/announcing-opt-in-relationship-loading) - [Deploy a Next.js app to Appwrite Sites](/blog/post/deploy-nextjs-app-to-appwrite-sites) **Watch** - [Build a full stack food delivery app in React Native](https://www.youtube.com/watch?v=LKrX390fJMw) - [Importing CSV files into an Appwrite database](https://youtu.be/7UfWaHNghas?si=ggKDtrhclOQXllS2) - [Bulk operations with Appwrites database](https://youtu.be/lliJLad3e9s?si=mdV8QIplgdxPOFKY) - [Type generation for Appwrite DB Schema](https://youtu.be/ip2CXH2KRl8?si=9EoOCraYd-hOCYoX) - [Announcing Database Upserts](https://youtu.be/Pt9ZttS3KiA?si=TWlRi5qFzZbHbzAP) - [Auto-increment support for numeric sequencing](https://youtu.be/yy-L6QmEWe8?si=BjXzFna1C54IDXZd) ### What’s to come That’s a wrap. But the excitement doesn’t stop here. Next month, we’re bringing: - A new Database integration - Plenty of useful Database features - A new open-source initiative by the Appwrite team! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## Product update December 2024: Highlights of the year https://appwrite.io/blog/post/product-update-december-2024 2024 has come to an end, and we want to thank all of you for making it an amazing year for Appwrite! We have seen hundreds of thousands of you join the platform, and even more projects are being ignited every day. In this product update, we take a moment to look back at everything we’ve released to make building with Appwrite accessible and enjoyable for all of you. From launching the highly requested features to launching new programs, here is Appwrite in numbers: - 1 product - 16 features - 10 runtimes - 3 SDKs - 28 integrations - 2 programs ![Products & features](/images/blog/product-update-december-2024/products-features.png) ### Products and features highlights 2024 In 2024, we added many products and features that many of you needed for your projects. Some of them essentials, like Messaging, and some of them part of the future hype, like AI integrations. But, whatever we released, we wanted to give you the tools to build the products you envisioned. Here is a recap of the top products of 2024. #### Messaging - Communicate with your users directly from the Appwrite console Adding Messaging to Appwrite was a big game changer in 2024. Not only does it make us the only open-source BaaS with this product, but it also was a product that many of you directly asked for. Just connect an integrator such as Sendgrid or Twilio to Appwrite, and you can start sending messages within minutes. In 2025, we plan to add push notifications that will give you more flexibility. #### MFA - Extra layer of security Who doesn’t love a security-tight application? With MFA, we introduced TOTP, emails, and SMS methods to add an extra security step for your users. To lead by example, we also added it to the Appwrite Console. Be sure to turn it on! #### Appwrite AI - Build AI-powered applications In May, we announced multiple AI Integrations to help developers build AI apps. With pre-built functions and an enhanced knowledge base, you can easily add features like natural language processing (NLP), image analysis, and audio processing to your projects. Integrate with popular AI tools like Pinecone, OpenAI and Perplexity, among others to supercharge your projects with advanced AI features. Check out all the cool AI projects built with Appwrite [here](https://builtwith.appwrite.io/search/?useCase=ai) #### New Appwrite CLI - Speed up your development process A CLI can be your best friend if done right. The [new version](https://appwrite.io/blog/post/introducing-new-appwrite-cli) was a more than welcome addition and has made developing applications with Appwrite a lot more enjoyable. No more overwriting or deleting existing collection data during deployments. With the new Appwrite CLI, you can test functions locally and push or pull data between Appwrite and your CLI seamlessly without affecting existing data. #### Improved SSR support - Implement SSR authentication with ease Lack of access to session secrets made SSR complex, leaving devs to rely on hacky and undocumented methods. With SSR support, Appwrite now securely manages session secrets server-side, making [SSR implementation](https://appwrite.io/blog/post/introducing-support-for-server-side-rendering) simple. #### Database backups - Secure your databases Data is your most valuable asset, and safeguarding it is incredibly important. That’s why we introduced [Database Backups](https://appwrite.io/blog/post/introducing-database-backups), which allows you to take a backup of your database. These backups are hot and encrypted, ensuring maximum security with no downtime. #### Roles - Improve your team collaboration and security In September, we launched the much-requested [Roles](https://appwrite.io/blog/post/announcing-roles-for-enhanced-collaboration-and-security) feature, making it easy for larger teams to manage access. Alongside the "Owner" role, new options include Developer, Editor, Analyst, and Billing. Custom roles are on the way, giving you even more control. Stay tuned. ![Integrations catalog](/images/blog/product-update-december-2024/integrations.png) ### Integrations catalog We launched the Integrations Catalog with a ton of popular integrations like Stripe, OpenAI, Huggingface, GitHub, and more. These integrations let you add payments, AI capabilities, and other cool things, taking your projects to the next level. We will continue to update the catalog with more integrations. You can also apply to become a [Technology partner](https://appwrite.io/integrations/technology-partner) and get your integration listed in the catalog. Check out the catalog [here](https://appwrite.io/integrations). ![Runtimes](/images/blog/product-update-december-2024/runtimes-sdk.png) ### Runtimes We support an extensive list of runtimes and continuously add new ones to meet your unique tech preferences. In 2024, we updated our runtime ecosystem to include support for: - Bun 1.0.29 - Node 22 - Ruby 3.3 - Deno 2.0 - PHP 8.3 - Python 3.12 - Kotlin 1.9 - Java 18 - Swift 5.9 - Dart 3.3 Check out the docs [here](https://appwrite.io/blog/post/announcing-more-and-updated-runtimes) and start building. ### SDKs Making Appwrite language agnostic is a high priority. So this year, we worked diligently to improve our SDKs and add new ones. Check all our announcements: - [React Native](https://appwrite.io/blog/post/introducing-appwrite-react-native-sdk) - [Deno 2.0](https://appwrite.io/blog/post/deno-2-appwrite-functions) - [Go](https://appwrite.io/blog/post/announcing-go-support) - [Enum SDK support](https://appwrite.io/blog/post/introducing-enum-sdk-support) - [New SDK versions released for Web, Flutter, and Android](https://appwrite.io/changelog/entry/2024-04-25-2) - [New SDK versions released](https://appwrite.io/changelog/entry/2024-02-15) ![Programs](/images/blog/product-update-december-2024/programs.png) ### Programs We’re committed to helping developers build the future, which is why we introduced two programs for founders who can use some help reducing their costs and students who want to get acquainted with the newest technologies. #### Appwrite Startup Program We launched the Appwrite Startup Program to support startups with a reliable backend, enabling them to scale faster and build smarter. Backed by Techstars and multiple venture studios, many innovative startups are already part of the program. Get $20k worth of cloud credits for 12 months to scale your startup. [Learn more](https://appwrite.io/startups). #### Appwrite Education Program In 2024, we also launched Appwrite Education Program for students in partnership with our friends at Github, this makes us the only BaaS platform to offer student discounts. We already have thousands of students building cool projects and learning along the way. What’s holding you back? [Join the program](https://appwrite.io/education) and start building today! ![Community recognitions](/images/blog/product-update-december-2024/community-recognitions.png) ### Community recognitions This month, we’re excited to feature Moneybinds - Money Tracker Web App by [Sanjiv](https://x.com/sanjivkjais) as part of our Monthly Community Recognitions. Moneybinds helps you track every penny of your finances without the complexity of linking your bank accounts or digital wallets. A big shout-out to Sanjiv: Check out our Moneybinds [here](https://moneybinds.com/). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [A modern developer’s guide to user authentication](https://appwrite.io/blog/post/guide-to-user-authentication) - [10 startup ideas for developers](https://appwrite.io/blog/post/startups-ideas-for-developers-2024) **Build** - [3 things you can build with GO runtime](https://appwrite.io/blog/post/3-things-you-can-build-with-go-runtime) - [Building the giveaway app](https://appwrite.io/blog/post/building-init-giveaway-app) **Watch** - [Build a full stack project with React and Appwrite](https://www.youtube.com/watch?v=zWF7O7aHQW4) - [Build and Deploy a Full Stack Social Media App](https://www.youtube.com/watch?v=_W3R2VwRyF4&t=3076s) ### What's to come As we look to 2025, we are grateful to our community and are focused on improving Appwrite to help developers build solutions that solve real problems. Here’s to another year of making software development accessible and enjoyable for all developers. Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## Product update February 2025: Appwrite 1.6.1, usage metrics improvements. https://appwrite.io/blog/post/product-update-feb-2025 Welcome back to the February product update. From shipping Appwrite 1.6.1 to upgrading our usage metrics system, we have some interesting things to share with you. Let’s dive in! ![The Appwrite 1.6.1](/images/blog/product-update-feb-2025/new-version.png) ### Appwrite 1.6.1 is now available We released Appwrite 1.6.1, packed with features, fixes and improvements to your Appwrite self-hosted instance. Some of the notable updates include: - Improved compatibility for webp requests. - Additional function runtime versions such as Node 22, Dart 3.5, and Bun 1.1. - A real-time heartbeat to keep real-time connections alive. These updates focus on performance, stability, and expanded functionality. [View the changelog](https://appwrite.io/changelog/entry/2025-02-06) ### Usage metrics system improvements We’ve improved the usage metric system to include OPTIONS requests and 4XX responses, which were previously omitted. So now you can have a clearer traffic overview, making it easy to track requests, identify issues, and optimize performance. [Learn more](https://appwrite.io/docs/advanced/platform/pro#check-resource-usage) ![Community recognitions](/images/blog/product-update-feb-2025/community.png) ### Community recognitions We’re excited to feature Anvos by ReveredInsan this month as part of our Monthly Community Recognitions. Anvos enables AI agents to interact with your platform through direct workflow access, eliminating unreliable UI automation. A big shout-out to [ReveredInsan](https://x.com/kanlanc). Check out their product, Anvos, [here](https://www.anvos.io/). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [Customer story: StoreAlert](https://appwrite.io/blog/post/customer-story-storealert) - [Setting up route protection in React Native](https://appwrite.io/blog/post/setting-up-route-protection-in-react-native) - [Self-hosting Appwrite with Coolify](https://appwrite.io/blog/post/self-hosting-appwrite-with-coolify) **Build** - [React Native Authentication with Expo Router](https://www.youtube.com/watch?v=JUU_XzdbzN0) - [Add phone Authentication to any app](https://www.youtube.com/watch?v=5YRct08TI8A) - [Adding magic link auth shouldn’t be hard](https://www.youtube.com/watch?v=mqgNmx9YE5w) - [Building a Full-Stack SuperBowl Web App with Vanilla JS and Appwrite](https://medium.com/@bgw26/building-a-full-stack-superbowl-web-app-with-vanilla-js-and-appwrite-authentication-storage-and-4a36ea642d38) **Watch** - [React Native crash course](https://youtu.be/bCpFbERgj7s?feature=shared) ### What's to come That’s a wrap! February was a short month, but we will be back with a lot of great updates in the coming weeks! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## Product update January 2025: Partners program, Scale plan and more https://appwrite.io/blog/post/product-update-jan-2025 2025 is off to a strong start for Appwrite. Here’s what happened in January: - A new program for Appwrite Partners - Launch of the Scale plan: Empowering Growing Companies - More powerful computing options - Improved push notifications Let’s dive in. ![The Appwrite Partners program](/images/blog/product-update-jan-2025/partner-image.png) ### The Appwrite Partners program We’ve launched a new program for agencies, development studios, system integrators, and freelancers to help you build innovative solutions for your clients using Appwrite. Partner with Appwrite and get exclusive benefits like: - Co-marketing: Dedicated partner catalog on our website, content collaborations, featured in our product update, and many more opportunities. - Support: Get access to the Appwrite engineering team to help you build the best custom products for your clients. - In-depth training: We provide in-depth training and workshops to help you master Appwrite for your clients. There are many more benefits that can help you deliver powerful solutions to clients, increase revenue, and expand your reach. Become a partner and grow your business. [Apply now](https://appwrite.io/partners) ![The Appwrite Scale plan](/images/blog/product-update-jan-2025/scale-image.png) ### The Appwrite Partners program This month, we also launched the Scale plan, a new offering to empower larger teams and create new opportunities for developers. Specially designed for agencies, startups, scale-ups, and SMBs needing priority support, enhanced flexibility, and advanced compliance to confidently scale their operations. Check out how you can use the Scale plan to take your projects to the next level! [Learn more](https://appwrite.io/blog/post/scale-plan-now-available) ### Product updates ![Push Notfications](/images/blog/product-update-jan-2025/push-notifications-image.png) #### Push Notifications: New delivery options Push Notifications just got a major upgrade this month. Now, you can run background updates silently, send critical alerts that bypass Do Not Disturb, and do other useful things, all designed to help you deliver personalized and impactful notifications. [Read the announcement](https://appwrite.io/blog/post/announcing-new-push-notifications-features) #### Billing improvements, UI fixes, and performance updates We believe little things make big things happen. That’s why we rolled out small yet impactful updates, including a clearer billing system, UI improvements, and fixes for collections and mobile experiences, all aimed at improving your overall Appwrite experience. [Go to changelog](https://appwrite.io/changelog/entry/2025-01-03) ![New compute capabilities for Appwrite Functions](/images/blog/product-update-jan-2025/compute-image.png) #### Introducing new compute capabilities for Appwrite Functions We've just added multiple new premium compute options to Appwrite Cloud. You can now choose up to 4 CPU cores and 4GB of memory to juice your Functions or customize the runtime specs according to your requirements. [Read the announcement](https://appwrite.io/blog/post/introducing-new-compute-capabilities-appwrite-functions) #### New Function runtimes and GitHub identity improvements We always keep on adding support to new runtimes. This month, we added PHP 8.3, Ruby 3.3, and Python 3.12 to your functions. Plus, we resolved Github identity conflicts to ensure smooth functionality. [Go to changelog](https://appwrite.io/changelog/entry/2025-01-14) #### Database improvements and fixes From improved handling of database indexes and attributes to enhanced error handling, we’ve released several database updates to improve your database performance and reliability. [Go to changelog](https://appwrite.io/changelog/entry/2025-01-17) ![Community recognitions](/images/blog/product-update-jan-2025/community-image.png) ### Community recognitions This month, we’re excited to feature Praxis by ReveredInsan as part of our Monthly Community Recognitions. Praxis matches your profile with recently funded startups, giving you access to active job openings and helping you land your dream role. A big shout-out to ReveredInsan. Check out Praxis [here](https://www.joinpraxis.org/) If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [5 VS Code extensions that replace entire development tools](https://appwrite.io/blog/post/5-vs-code-extensions-that-replace-entire-dev-tools) - [Budget caps: How to stop unexpected cloud bills before they happen](https://appwrite.io/blog/post/budget-caps-stop-unexpected-cloud-bills) - [How Appwrite streamlines database operations using hooks](https://appwrite.io/blog/post/hooks-appwrite-databases) **Build** - [Building a full-stack app with Svelte and Appwrite](https://appwrite.io/blog/post/build-fullstack-svelte-appwrite) - [Building custom authentication flows with Appwrite](https://appwrite.io/blog/post/building-custom-auth-flows) - [Email your users using Resend and Appwrite Messaging](https://appwrite.io/blog/post/integrate-resend-smtp) **Watch** - [Build an app and master React in 2 hours](https://www.youtube.com/watch?v=dCLhUialKPQ&t) - [React Native Appwrite SDK - Getting started](https://www.youtube.com/watch?v=YSUmzHH_OMg) - [Setup custom SMTP for your Appwrite self-hosted instance](https://www.youtube.com/watch?v=gUWP9J2Hp0k) ### What's to come That’s a wrap for January. Here is what you can expect in the next weeks: - New Cloud regions - Init announcement - More feature updates and improvements Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## July product update: New Database features, Sites live, and Console improvements https://appwrite.io/blog/post/product-update-july-2025 Welcome back to the July Product Update. This month, we have made many, many great releases! Here’s what to expect: * Appwrite Sites is now available to all * Many new Databases features * Console improvements * Improved notification previews Let’s dive in. ### Appwrite Sites live for all ![Sites live for all](/images/changelog/2025-07-24.png) Here’s an update many of you’ve been waiting for: Appwrite Sites is now live for all! You can now host your applications directly from the Appwrite console, making Appwrite an all-in-one development platform to develop, deploy, and scale your apps. [Read the announcement](/blog/post/announcing-appwrite-sites) ### CSV import ![CSV imports](/images/blog/announcing-csv-imports/cover.png) We started July with CSV import. Built on top of Appwrite's migration APIs, this Databases feature makes it easy to bring in large datasets, seed collections, or migrate structured data using only a CSV file. [Learn more](/blog/post/announcing-csv-imports) ### Bulk API ![Bulk API](/images/blog/announcing-bulk-api/cover.png) You can now handle heavy data workloads with greater ease. With Bulk API, you can create, update, delete, or upsert multiple documents in a single API call. This is especially useful for managing large datasets or syncing data between systems without multiple API calls. [Learn more](/blog/post/announcing-bulk-api) ### Database Upsert ![Database Upsert](/images/blog/announcing-database-upsert/cover.png) Database Upsert lets you create or update a document with a single API call. If the document exists, it’s updated; if not, it’s created. This removes the need for manual existence checks, reduces extra requests, and simplifies your logic. [Learn more](/blog/post/announcing-database-upsert) ### Encrypted string attribute support ![Encrypted ](/images/blog/announcing-encrypted-string-attributes/cover.png) We’ve added an extra layer of security to Appwrite Databases and Storage. This feature allows you to encrypt string attributes at rest directly from the Appwrite console, without needing to implement manual encryption. [Learn more](/blog/post/announcing-encrypted-string-attributes) ### Auto increment support ![Auto-increment](/images/blog/announcing-auto-increment-support/cover.png) Appwrite Databases now support auto-increment fields. A `$sequence` column is automatically managed in your collection, increasing with each new document. This helps keep your data ordered without requiring manual handling. [Learn more](/blog/post/announcing-auto-increment-support) ### Console and deployment improvements ![Console](/images/blog/product-update-july-2025/console.png) We've shipped several improvements to fix broken flows and clean up the developer experience across Sites, Functions, and the Appwrite Console. A few of them include: - Site screenshots now use the Storage API preview route - The Git deployment modal now auto-fills your production branch - Domain validation status now updates in real-time And many more. View the full changelog for more details [Go to Changelog](/changelog/entry/2025-07-29) ### Smarter notifications ![OTP](/images/blog/product-update-july-2025/otp.png) We’ve just added new previews for all Appwrite OTP emails. End users can now view the code/OTP without opening the message. Here’s what improved: - Key info is shown at the top - Notifications are cleaner and easier to read - Clearer copy makes actions quicker and reduces confusion [Learn more](/docs/products/messaging) ### Community recognitions ![community.png](/images/blog/product-update-july-2025/community.png) This month, we’re excited to feature [**Idea Tracker**](https://idea-tracker-v2.appwrite.network/) as part of our Monthly Community recognitions. **Idea Tracker** is an application that helps you save and track project ideas you plan to build. A big shout-out to [Abhi Varde](https://x.com/varde_abhi). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [Building with Appwrite Sites templates](/blog/post/building-with-sites-templates) - [Using $sequence to track document order in Appwrite](/blog/post/track-document-order-with-sequence) - [Secure sensitive database fields with encrypted string attributes](/blog/post/encrypted-attributes-for-sensitive-fields) **Watch** - [Build a full stack food delivery app in React Native](https://www.youtube.com/watch?v=LKrX390fJMw) - [Importing CSV files into an Appwrite database](https://youtu.be/7UfWaHNghas?si=ggKDtrhclOQXllS2) - [Bulk operations with Appwrites database](https://youtu.be/lliJLad3e9s?si=mdV8QIplgdxPOFKY) - [Type generation for Appwrite DB Schema](https://youtu.be/ip2CXH2KRl8?si=9EoOCraYd-hOCYoX) - [Announcing Database Upserts](https://youtu.be/Pt9ZttS3KiA?si=TWlRi5qFzZbHbzAP) - [Auto-increment support for numeric sequencing](https://youtu.be/yy-L6QmEWe8?si=BjXzFna1C54IDXZd) ### What’s to come That’s a wrap for July. Next month, we will have more database updates, including good news for developers working with SQL, and some very big news! Stay tuned! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## June product update: Public roadmap | The Appwriter | Messaging https://appwrite.io/blog/post/product-update-june It’s the first week of a new month, which means it’s time to reflect on all the wonderful things that happened in June. Read on to learn more about our public roadmap, Appwriters out in the wild, and dev tools to boost your productivity. ### Public roadmap ![Appwrite's public roadmap](/images/blog/product-update-june/public-roadmap.png) This month, we launched the new public roadmap on GitHub. It is part of our commitment to transparency with the Appwrite community. It enables: - Increased transparency in our product priorities - Direct feedback route for the community - Further clarity in timelines to set expectations The roadmap is designed to help us increase collaboration among the core team, contributors, maintainers, and Appwrite developers. [Read the announcement here.](https://appwrite.io/blog/post/public-roadmap-announcement) ### The Appwriter is out! Appwrite community members have started to receive their Appwriters, and the reviews are in! People love it. ![Appwriter testimonials](/images/blog/product-update-june/appwriter-testimonials.png) We’re very proud of the overwhelmingly positive feedback from the community. Our team has worked tirelessly for the past six months to ensure that every detail, from the packaging to every key press, is perfect. The Appwriter will be available for pre-order on the Appwrite store today! [Visit our store](https://apwr.dev/store) to place your pre-order. Does this mean we’re also a hardware company now? ### Appwrite Messaging ![Messaging page](/images/blog/product-update-june/messaging-page.png) Appwrite’s newest product Messaging now has its own product page (the first of its kind!). Messaging allows you to set up email, SMS and push notifications to your users from a single API. You can find more in-depth information on how messaging works and even play around with the interactive animations. [Visit the brand-new page to find out more.](https://appwrite.io/products/messaging) ### 42K stars on GitHub [This is a huge moment for the community!](https://x.com/appwrite/status/1805564529083924701) Thank you to everyone for supporting us in our journey. Onwards and upwards! ### Product updates Big shoutout to [Manjunath0408](https://github.com/Manjunath0408) and [Darshan](https://github.com/ItzNotABug) for their contributions! #### New releases - [[Console]](https://github.com/appwrite/console/pull/942) Added new filtering method `contains` , and enum and array filtering - [[Mobile version]](https://github.com/appwrite/console/pull/1122) File extensions are now scrollable on mobile by [Manjunath0408](https://github.com/Manjunath0408) - [[Mobile version]](https://github.com/appwrite/console/pull/1126) Fixed the mobile footer - [[Mobile version]](https://github.com/appwrite/console/pull/1107) Fixed mobile paddings danger cards - [[Mobile version]](https://github.com/appwrite/console/pull/1110) Improved mobile table responsiveness - [[Messaging]](https://github.com/appwrite/console/pull/1123) Added Messaging support to the events builder modal by [Darshan](https://github.com/ItzNotABug) #### Bug fixes - Improved Console loading bar - [Throw an error if invalid domain is added to a function](https://github.com/appwrite/console/pull/1115) by [Manjunath0408](https://github.com/Manjunath0408) - [Fix for inconsistent build logs](https://github.com/appwrite/appwrite/issues/6237) by [Darshan](https://github.com/ItzNotABug) - [Fix for migration of collections, attributes and indexes of Databases](https://github.com/appwrite/appwrite/issues/8044) ### Open source community recognitions This month, we’re excited to feature Raman as part of our Monthly Community Recognitions. Raman created a video using **Appwrite Cloud and Flutter** showcasing how to work with Authentication. He also has over 2,000 messages on our Discord community server, and we want to recognize him for his commitment to the community. [Watch the video.](https://www.youtube.com/watch?v=jLVWZauHnjE) If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources - [Watch](https://www.youtube.com/watch?v=OFrMWvgsZ94): Add custom user data with Preferences - [Watch:](https://www.youtube.com/watch?v=smnxWspZMNY) Build a theme switcher in React in 5 minutes - [Watch](https://x.com/appwrite/status/1799141497973186760): Unlock granular permissions in your apps with Appwrite Labels - [Watch](https://youtube.com/shorts/glbo5lsWSsI?feature=shared): 4 VS Code themes you should try - [Read](https://appwrite.io/blog/post/developer-tools-appwrite): 12 tools to supercharge your Appwrite project - [Read](https://appwrite.io/blog/post/baas-vs-custom-backend): BaaS vs. Custom Backend: making the right choice as a freelancer - [Read](https://appwrite.io/blog/post/integrate-custom-auth-sveltekit): Integrate any external authentication solution into your Appwrite project ### Open source and community updates - [Repository:](https://github.com/2002Bishwajeet/serverless-functions) OGP Extractor is a serverless API that extracts open graph data, used to generate link previews by Bishwajeet Parhi - [Repository:](https://pub.dev/packages/app_realtime_ext) Up-to-date Appwrite Flutter SDK by Muslimin Ontong ### What’s to come If you’ve checked out our public roadmap, you might have seen what we’ve been working on. But here is a quick sneak peek into what you can expect from us in the coming weeks: - Appwrite’s newest version 1.6 soon to be released - An updated version of [Pink design](https://pink.appwrite.io/) is in the works - A new release means a new Init… keep an eye out for the announcement! Follow us on [socials](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## March product update: Product Hunt launch, Appwrite MCP server, and faster backups. https://appwrite.io/blog/post/product-update-march-2025 Welcome back to the March product update. Here’s what happened the past month: - Launched the Appwrite MCP server - Key optimizations to Appwrite cloud backups - Update to budget limits in Appwrite Cloud Plus, we've got something exciting to share with you. Let’s dive in. ![Product Hunt announcement](/images/blog/product-update-march-2025/ph.png) ### Big announcement coming soon We’ve got a major announcement on the way. This time, we’re bringing the heat to Product Hunt. The Appwrite community is the first to know! Visit our ‘Coming Soon’ page and click Notify Me to get an email when we go live. We can't wait to share what we’ve been working on. [Go to Product Hunt](https://www.producthunt.com/products/appwrite) ![Appwrite MCP server](/images/blog/product-update-march-2025/mcp.png) ### Appwrite MCP server Did you know that Appwrite has launched its own MCP server? This means you can now let AI agents like Cursor or Windsurf analyze database records, access the User API, or perform actions directly on your Appwrite project. Not sure where to start? No worries, we’ve got you covered. - [Appwrite MCP documentation](https://appwrite.io/docs/tooling/mcp) - [Appwrite MCP server practical applications](https://www.youtube.com/watch?v=83rNS6W2Nu8) - [Learn more about MCP and how it works under the hood](https://appwrite.io/blog/post/what-is-mcp) The Appwrite MCP server opens up infinite possibilities, and this is just the beginning. We can't wait to see how you’ll use it in your projects. ![Cloud improvements](/images/blog/product-update-march-2025/cloud-backups.png) ### Appwrite Cloud backups are now faster Appwrite Cloud backups are now significantly faster. We’ve made key optimizations to our infrastructure, reducing backup times by up to 7x. With quicker data recovery and improved reliability, you can spend less time waiting and more time building. [Learn more](https://appwrite.io/changelog/entry/2025-03-27) ### Update to budget limits in Appwrite Cloud We’ve improved budget limits in Appwrite Cloud to give you better control over your organization’s scaling. Now, setting the budget to 0 means your organization will not exceed its allocated resources, while null will explicitly disable the budget. This update ensures clearer budget management and better control over scaling. [Learn more](https://appwrite.io/changelog/entry/2025-03-19) ![Community recognitions](/images/blog/product-update-march-2025/community.png) ### Community recognitions This month, we’re excited to feature Geohash as part of our Monthly Community recognitions. The Geohash application demonstrates how to implement geolocation-based user search with Appwrite. A big shout-out to [Clément](https://github.com/clementg13). Check out their product, Geohash, [here](https://github.com/clementg13/Appwrite-Nearby-GeoLocation). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources **Read** - [Lynx by ByteDance vs React Native](https://appwrite.io/blog/post/bytedance-lynx-vs-react-native) - [What exactly is MCP, and why is it trending?](https://appwrite.io/blog/post/what-is-mcp) - [Image transformation with Appwrite Storage](https://appwrite.io/blog/post/image-transformation-with-appwrite-storage) - [Building a backendless application with Angular and Appwrite](https://angular.love/building-a-backendless-application-with-angular-appwrite) **Watch** - [Appwrite vs Supabase performance results](https://youtu.be/zWLfLxq2Ws4?si=7NaoZPb-JbDpNVfG) - [Appwrite MCP server practical examples](https://youtu.be/83rNS6W2Nu8?si=gJUMP4VisK_kG0nE) - [Appwrite MCP server setup with Cursor](https://www.youtube.com/watch?v=Qzikjm5H7wM) ### What's to come That’s a wrap for March. We have a lot to come in the coming weeks, starting with new Cloud regions! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## November product update: Database AI suggestions and ElevenLabs template https://appwrite.io/blog/post/product-update-november-2025 Welcome back to the November product update. This month is different. If you caught our Open Letter, you already know where we're heading: Appwrite is no longer just a backend. With Appwrite Sites, we're taking a real step toward the most complete cloud platform to date. And on top of that, we've shipped a stack of upgrades across Databases, plus a new ElevenLabs template to add voice to your apps in minutes. So, here's what to expect: - DB operators - Database AI suggestions - Skipping total counts for faster list queries - Disable image transformations for buckets - Next.js standalone builds support Let's dive in. ![An open letter to Appwrite Community](/images/blog/product-update-november-2025/open-letter.png) ### An open letter to Appwrite Community This month, we shared something bigger than a feature release: our vision for where Appwrite is heading. In our **Open Letter**, Eldad breaks down how Appwrite is evolving from a backend platform into a full **developers' cloud** that supports the entire product journey: imagine → build → deploy → observe → protect. If you want the full story behind why Sites matters and where the platform is going next, you should definitely give it a read. [Read the full open letter](/blog/post/the-developers-cloud) ![Database AI suggestions](/images/blog/product-update-november-2025/ai-suggestions.png) ### Database AI suggestions Starting with an empty table can be slow, repetitive, and time-consuming. With Database AI suggestions, you get the option to use suggested columns and indexes the moment you name your table. That means: - No more blank schemas - Appwrite suggests sensible columns you can use. - Faster setup - Go from table name to schema in seconds. - You're in control - Review, tweak, or skip the suggestions. [Read the announcement](/blog/post/announcing-database-ai-suggestions) ![DB operators](/images/blog/product-update-november-2025/db-operators.png) ### DB operators Now you don't have to fetch an entire row just to bump a number or tweak a tag list. With DB Operators, you can now send inline, atomic update instructions directly in your Update or Upsert call. No race conditions. No bulky payloads. Now live on Appwrite Cloud. [Read the announcement](/blog/post/announcing-db-operators) ![ElevenLabs Text-to-Speech template](/images/blog/product-update-november-2025/elevenlabs.png) ### ElevenLabs Text-to-Speech template You can now add a real, human-like AI voice to your apps using Appwrite Sites with our new ElevenLabs integration. And the best part? We've shipped a ready-to-use Appwrite Sites template built with ElevenLabs Text-to-Speech API. [Check out the integration](/integrations/ai-elevenlabs-text-to-speech) ![Skip total counts](/images/blog/product-update-november-2025/skip-totals.png) ### Skip total counts for faster list queries A new way to optimize your Appwrite Databases. You can now skip total counting on list endpoints with a simple `total=false` flag and get faster, lighter list responses without the extra database work. [Learn more from the documentation](/docs/products/databases/pagination#skip-totals) ![Next.js standalone builds](/images/blog/product-update-november-2025/nextjs.png) ### Next.js standalone builds are now supported on Appwrite Sites You can now deploy Next.js apps built in [standalone mode](https://nextjs.org/docs/app/api-reference/config/next-config-js/output) on Appwrite Sites, with full support for Next.js 16. In standalone mode, the build process creates a smaller package that includes only the files your app needs to run in production, leaving out development tools and unused parts of the framework. [Read the announcement](/blog/post/nextjs-standalone-support-in-appwrite-sites) ### Disable image transformations for buckets Image transformations allow actions like resizing, cropping, and format conversion through the Appwrite Storage API. With this new update, you have full control to turn off these operations when they're not needed, reducing the chance of unintentional processing or costs. ![Community recognitions](/images/blog/product-update-november-2025/community.png) ### Community recognitions We're excited to feature [Appwrite ORM](https://appwrite-orm.online/) as part of our Monthly Community recognitions this month. A powerful TypeScript ORM that adds type safety, automatic migrations, and smooth developer workflows on top of Appwrite. A big shout-out to [Ori Raisfeld](https://www.linkedin.com/in/ori-raisfeld-422392264/). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. ### Engineering resources Read - [Introducing the developers' cloud](/blog/post/the-developers-cloud) - [Announcing Database AI suggestions: from table name to schema in one click](/blog/post/announcing-database-ai-suggestions) - [What's new in Node.js v25.2: Web Storage, V8 14.1, permissions and more](/blog/post/nodejs-v25-whats-new) - [Remix 3: what's changing and why it matters](/blog/post/remix-3-whats-changing-and-why-it-matters) - [Next.js standalone builds now supported on Appwrite Sites](/blog/post/nextjs-standalone-support-in-appwrite-sites) - [Announcing DB operators: Update multiple fields without fetching the entire row](/blog/post/announcing-db-operators) ### Conclusion That's a wrap. We've been building up the hype for a while now, and it's almost here. If you're into vibe coding or building AI applications, then we've got some massive news for you in the coming weeks. Stay tuned! Follow us on [X](https://x.com/appwrite) and check our [Changelog](/changelog) regularly, as we will release more information in the coming weeks. --- ## October product update: Unlimited Sites, Transactions API, and 1.8 to Self-hosted https://appwrite.io/blog/post/product-update-october-2025 Welcome back to the October product update. This month, we released the most requested Database feature, along with Appwrite 1.8x support for Self-hosted. Best of all, you can now spin up unlimited Sites on the free plan. So, here's what to expect: - Transactions API - New cloud regions - TanStack Start support - Next.js 16 support Let's dive in. ![Unlimited sites](/images/blog/product-update-october-2025/sites.png) ### Unlimited Sites on the Free plan Appwrite Sites began in early access with a single goal: to make deploying modern web projects as seamless as building them. To ensure performance and reliability, we initially limited deployments to one site per project while we refined the experience. Now that the platform has matured, that limit's gone. With the Free plan, you can now deploy an unlimited number of sites per project, giving you the freedom to build, test, and launch without constraints. [Read the announcement](/blog/post/unlimited-appwrite-sites-free-plan) ![Transactions API](/images/blog/product-update-october-2025/transactions.png) ### Transactions API This was one of the most requested features by the community, and it's finally live on both Appwrite Cloud and Self-hosted instances. With Transactions API, you can stage multiple operations across one or more tables, and commit them only when you're ready. - If every operation checks out, Appwrite commits them in one atomic action. - If anything fails, whether due to permissions, conflicts, validations, or other reasons, Appwrite automatically rolls everything back. [Read the announcement](/blog/post/announcing-transactions-api) ![Self hosted](/images/blog/product-update-october-2025/sh.png) ### 1.8 Self-hosted support Appwrite 1.8 is now live for Self-hosted instances. Appwrite has always been built with openness and flexibility in mind. Every release we ship is meant to work just as well for teams running their own infrastructure as it does on the cloud. Version 1.8 continues that commitment, giving self-hosted users access to: - New Databases experience - Powerful Databases features - Features to simplify and streamline migrations [Read the announcement](/blog/post/appwrite-1-8-0-self-hosted-release) ![TanStack Start](/images/blog/product-update-october-2025/tanstack.png) ### TanStack Start support for Appwrite Sites TanStack Start is making all the noise, and for good reason. It's simple, type-safe, and built for devs who want control. You can now run TanStack Start with full Server-Side Rendering (SSR) directly on Appwrite Cloud. Zero configuration needed. [Read the announcement](/blog/post/tanstack-start-support-in-appwrite-sites) ![New cloud](/images/blog/product-update-october-2025/cloud.png) ### New Cloud regions We've added two new regions to Appwrite Cloud. The Singapore (SGP) and Toronto (TOR) Cloud regions are now live and ready for your projects. This means you can: - Create new projects directly. - Or migrate your existing projects to take advantage of the new locations. [Learn more](/docs/products/network/regions) ![Next.js](/images/blog/product-update-october-2025/nextjs.png) ### Next.js 16 SSR support Next.js 16 brings one of the most polished releases the framework has seen in a while. Instead of chasing big rewrites, this version focuses on the fundamentals, faster builds, predictable caching, smarter routing, and better developer visibility. And if you're deploying on Appwrite Sites, you can start using Next.js 16. The latest release is fully supported, so you can build, test, and host your Next.js projects seamlessly on Appwrite's open-source infrastructure. [Visit docs](/docs/products/sites/quick-start/nextjs) ![Community](/images/blog/product-update-october-2025/community.png) ### Community recognitions We're excited to feature [CappyChat](https://cappychat.com/) in our Monthly Community recognitions this month. CappyChat is an advanced AI chat platform offering intelligent conversations, live coding, image generation, and real-time web search. All in a fast, customizable interface. A big shout-out to [Ayush Sharma](https://x.com/CyberBoyAyush). If you'd like to participate in next month's Community Recognitions, join our Discord server to showcase your project. ### Engineering resources Read - [Claude Code tips and best practices](/blog/post/claude-code-tips-tricks) - [7 reasons to not think twice before migrating from Vercel to Appwrite Sites](/blog/post/migrate-from-vercel-to-appwrite-sites) - [Choosing the right AI database for your application in 2025](/blog/post/choosing-the-right-ai-database) - [Why developers are leaving Next.js for TanStack Start, and loving it](/blog/post/why-developers-leaving-nextjs-tanstack-start) - [Best Postman alternative options developers actually enjoy using](/blog/post/best-postman-alternative-options) - [Appwrite 1.8.0: The most powerful self-hosted release yet](/blog/post/appwrite-1-8-0-self-hosted-release) - [From student to developer - How open source can launch your career](/blog/post/from-student-to-developer-how-open-source-can-launch-your-career) - [The future of coding: Cursor, AI, and the rise of backend automation with Appwrite](/blog/post/the-future-of-coding-cursor-ai-and-the-rise-of-backend-automation-with-appwrite) Watch - [Keep data consistent with database transactions](https://www.youtube.com/watch?v=SSNtchzwqIg) ### What's to come That's a wrap. If you're into building AI applications, then we've got some massive news for you in the coming weeks. Here's a quick sneak peek: - More Databases features - Exciting new integrations - And something big for those who love AI Builders Follow us on X and check our Changelog regularly, as we will release more information in the coming weeks. --- ## September product update: New Roles | Hackathon | CCPA | 1.6 for self-hosted https://appwrite.io/blog/post/product-update-september Right after Init, we hit the ground running with a busy month. We introduced the new **Roles** feature, announced **CCPA compliance**, and kicked off the **Appwrite** **Hacktoberfest Hackathon.** Let’s dive into it! ![New roles announcement](/images/blog/product-update-september/3.png) ### New roles We’ve introduced Roles, a feature designed to bring granular permissions to the Appwrite Console, available for both the Pro and Scale plans. Alongside the existing owner role, you can now assign these new roles to your team members: - Developer - Editor - Analyst - Billing To celebrate the new feature, you can invite members with the new roles to your Pro teams for free until the end of the year. [Read the annoucement.](https://appwrite.io/blog/post/announcing-roles-for-enhanced-collaboration-and-security) ![Hacktoberfest Hackathon](/images/blog/product-update-september/4.png) ### Hacktoberfest Hackathon We’ve heard your requests, and we're excited to deliver — Appwrite is organizing its very first Hacktoberfest Hackathon this October! Here’s what you need to know: - Dates: October 1st — October 31st. - Teams up to **4 members** or solo. - **Brand-new** projects only. - 1st place will receive **the Appwriter**, 2nd and 3rd — the **Appwrite swag kit**. Ready to join? Registration is open now until Oct 31. [Register here.](https://apwr.dev/htf24-hackathon) ![1.6 for self-hosted](/images/blog/product-update-september/1.png) ### 1.6 for self-hosted Appwrite 1.6 is now LIVE for self-hosted instances! Everything we launched during Init is now ready for your self-hosted instance, including: - Local Development - New Appwrite CLI - Binary executions - Dynamic keys - Delayed executions - Go SDK & Runtime - Session alerts [Check out the release notes for more details.](https://github.com/appwrite/appwrite/releases/tag/1.6.0) ![CCPA announcement](/images/blog/product-update-september/2.png) ### CCPA Appwrite is now fully CCPA-compliant, bringing you even more security and peace of mind when building your apps. The [California Consumer Privacy Act (CCPA)](https://oag.ca.gov/privacy/ccpa) is a landmark data privacy law that enhances consumer rights for residents of California. [Read the announcement.](https://appwrite.io/blog/post/announcing-appwrite-is-ccpa-compliant) --- ### Product updates #### 1.5.11 Security patch A security issue was discovered in open-runtimes/executor that caused error stack traces to be included in build and execution logs, potentially exposing sensitive information. For Appwrite Cloud users, we have deployed a security patch and rotated affected keys to prevent misuse, and we've confirmed that there was no exploitation of this issue, so no action is needed on your part. If you are using a self-hosted Appwrite instance, we strongly recommend upgrading to version 1.5.11 to implement the security patch and prevent any further exposure. This upgrade is straightforward and requires no migrations or additional steps. [Check out the changelog for more details.](https://appwrite.io/changelog/entry/2024-09-17) #### Updating collection attribute names and sizes You can now update attribute names and sizes directly from your Cloud Console. This update provides better flexibility in managing your data models, allowing you to adjust attributes without needing to recreate them or handle complicated migrations. This is just one in a series of updates we plan to release for Appwrite databases over the next few weeks. --- ### Community recognitions This month, we’re excited to feature Paralino by [Zelimir](https://x.com/heyzlmr) as part of our Monthly Community Recognitions. Paralino is a privacy focused, end-to-end encrypted location-sharing and family safety app. The first version is built for Android (iOS version coming soon) and is powered fully by Appwrite. Check it out and download the [application on Google Play](https://play.google.com/store/apps/details?id=app.paralino.android&pli=1). If you'd like to participate in next month's Community Recognitions, [join our Discord server](https://appwrite.io/discord) to showcase your project. --- ### Engineering resources - [Watch](https://www.youtube.com/watch?v=HGlBpna17LQ): Appwrite Database Tutorial from NetNinja - [Read](https://appwrite.io/blog/post/how-to-optimize-your-appwrite-project): How to optimize your Appwrite project for cost and performance - [Read](https://appwrite.io/blog/post/gdpr-compliance-mobile-apps-alternative-firebase): GDPR compliance for mobile apps: a developer’s guide - [Read](https://appwrite.io/blog/post/best-ios-android-app-development-platforms): 7 best app development frameworks for iOS and Android - [Read](https://appwrite.io/blog/post/build-a-chat-app-with-appwrite-and-gemini): Building a chat app with Appwrite and Google Gemini - [Read](https://appwrite.io/blog/post/hacktoberfest-ideas-2024): Get inspired for Hacktoberfest 2024 with these ideas - [Read](https://appwrite.io/blog/post/ccpa-vs-gdpr): CCPA vs GDPR: Understanding the differences and implications - [Read](https://appwrite.io/blog/post/appwrite-realtime-for-flutter): Get started with Appwrite Realtime for Flutter - [Repository:](https://github.com/PinguApps/AppwriteSdk) Appwrite SDK for .NET by [Pingu](https://github.com/pingu2k4) --- ### What’s to come October will be jam-packed with announcements and new features, so stay tuned! - New Cloud and Database features are to be revealed soon! - A certain Backups teaser made waves on our [Discord channel](https://appwrite.io/discord)… - And we filmed some exciting new stuff while at our Camp in Barcelona! Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## Appwrite September product update: Docs MCP server, new Database features, and queries https://appwrite.io/blog/post/product-update-september-2025 Welcome back to the September Product Update. This month, we have some serious Database upgrades with a new set of queries and features. Also, get ready for one more exciting hackathon news. So, here’s what to expect: - New set of queries - Appwrite Docs MCP server - API for Spatial columns - Hacktoberfest Hackathon - And more! Let’s dive in! ### Appwrite's Hacktoberfest Hackathon ![Hacktoberfest Hackathon](/images/blog/product-update-september-2025/hackathon.png) It's that time of year again—Hacktoberfest has started! It's the biggest celebration of open-source technology, and we're excited to be back with yet another month-long hackathon. We invite you to join the fun and build something amazing and open-source. [Join now](https://hacktoberfest.appwrite.network/) ### New set of queries ![New queries](/images/blog/product-update-september-2025/queries.png) Appwrite Databases now have smarter queries. We recently added two sets of new queries that let you handle time-based filtering and exclusions directly in your queries. [Time Helper queries](/docs/products/databases/queries) `createdBefore`, `createdAfter`, `updatedBefore`, `updatedAfter` [Inversion queries](/docs/products/databases/queries) `notSearch`, `notContains`, `notBetween`, `notStartsWith`, `notEndsWith` Together, these new queries make your code cleaner, reduce the need for workarounds, and give you more control over your data. ### Appwrite Docs MCP server ![MCP server](/images/blog/product-update-september-2025/mcp.png) We also launched a dedicated MCP server for Appwrite docs that enables AI assistants to access and search through Appwrite's documentation directly. That means, AI assistants can now: - Search through Appwrite documentation - Retrieve specific guides and references - Generate code snippets using the latest APIs - Provide contextual help for Appwrite features [Visit docs](/docs/tooling/mcp/docs) ### API for Spatial columns ![API for Spatial columns](/images/blog/product-update-september-2025/spatial-columns.png) You can now build scalable geo workflows right out of the box. With this new API, you can store points, lines, and polygons, add spatial indexes, and run geo queries all natively in Appwrite. What’s new: - Point column - Line column - Polygon column - Spatial index - 12 new operators With these new capabilities, it’s simple to model and query location-based data. [Read the announcement to learn more](/blog/post/announcing-spatial-columns) ### Turbopack support for Appwrite Sites ![Turbopack support](/images/blog/product-update-september-2025/turbopack.png) Appwrite Sites now supports Next.js applications built with Turbopack, with faster builds and better compatibility for your deployments. With native support, your builds run without modification, deploy faster, and stay consistent between local and production. This update strengthens compatibility between Appwrite Sites and the modern Next.js toolchain. [Read the announcement to learn more](/blog/post/turbopack-support-appwrite-sites) ### Performance improvements for Functions and static sites We've shipped significant performance improvements across functions and static sites. Function cold starts are now 58% faster in the Frankfurt (FRA) region with more consistent start times across executions. Static sites are now served directly through our edge proxies, eliminating the need for dedicated runtimes. This means: - No more cold starts for static sites - Automatic caching for all static site assets - Over 99% of requests served locally from 120+ global PoPs ### Community recognitions We’re excited to feature [MediaKit](https://usemediakit.app/) in our Monthly Community recognitions this month. MediaKit is a privacy-first, browser-based toolkit launched with a powerful Image Compressor. Appwrite handled the entire auth flow, including sign-ups, email verification, password recovery, and secure login. A big shout-out to [Gurjeet](https://www.linkedin.com/in/gurjeet-singh-virdee-25a476199/). If you'd like to participate in next month's Community Recognitions, join our Discord server to showcase your project. ### Engineering resources **Read** - [Make the best use of Appwrite’s MCP server](/blog/post/make-best-use-appwrite-mcp) - [Setting up “Sign in with Google” in your app](/blog/post/setting-up-google-signin) - [Client vs Server Components in React](/blog/post/client-vs-server-components-react) - [Implementing Google OAuth with Expo Router](/blog/post/google-oauth-expo) - [Comparing the best vibe coding tools](/blog/post/comparing-vibe-coding-tools) - [Build a “delivery store locator” using Spatial Columns in Appwrite Databases](/blog/post/build-delivery-store-locator-spatial-columns) - [How Radar scaled media curation while saving $1M with Appwrite Cloud](/blog/post/customer-story-radar) **Watch** - [We made relationship queries flexible](https://youtu.be/PFQv5Gvg2Io?si=AlxmWfTxj-gfRod7) - [We launched an MCP server for our documentation](https://youtu.be/tIUGYvirdS4?si=p68ElsbciNRQG7ug) - [Collect payments using Stripe](https://www.youtube.com/watch?v=BXsVXkemjv0) - [Build a store locator using Spatial Columns in Appwrite Database](https://youtu.be/eAc9mqW6yEU?si=mR_qMAYsGTBvEgfs) ### What’s to come That’s a wrap. This month, we have a lot more exciting news! If you're a big fan of self-hosting, or vibe coding...well, it's going to be a great few weeks for you! Plus, we have more database and region updates pending: - New databases partner - Multiple database features - Brand new cloud region Follow us on [X](https://x.com/appwrite) and check our [Changelog](https://appwrite.io/changelog) regularly, as we will release more information in the coming weeks. --- ## Appwrite Cloud is now in public beta https://appwrite.io/blog/post/public-beta We're thrilled to announce a major step forward for Appwrite Cloud as we transition from our private beta to the public beta today! We're eager to hear your feedback, learn from your experiences, and continually improve Appwrite Cloud to better serve your needs. We're thrilled to announce a major step forward for Appwrite Cloud as we transition from our private beta to the public beta today! We're eager to hear your feedback, learn from your experiences, and continually improve Appwrite Cloud to better serve your needs. **TL;DR Sign up at [cloud.appwrite.io/register](https://cloud.appwrite.io/register)** ### Why Appwrite Cloud? At Appwrite, we are committed to the open-source community and believe that developers should have access to powerful tools and services that are intuitive, developer-centric and affordable. With Appwrite Cloud, we are extending our commitment to provide developers with the best possible experience by offering a fully managed backend solution that simplifies infrastructure management and helps them focus on what they do best - building great applications! ### What was the private beta? Over the last couple of months, we conducted a successful private beta that allowed us to get Appwrite Cloud in the hands of selected members of the community. This exercise allowed us to learn a lot about usage patterns, infrastructure performance, early feedback and lots more. We conducted cloud interviews with members of the initial cohort to understand their use cases and identify the most important features for them. These conversations have been invaluable in shaping the direction of Appwrite Cloud, allowing us to prioritize features and improvements. We also conducted a pricing survey with participants to better understand developer demographics, their needs, their budgets and expectations. This information was instrumental in helping us fine-tune our pricing strategy and deliver the best value for our developers. ### What's new in the public beta? With the launch of the public beta, we're opening up access to the Appwrite Cloud to a broader audience, allowing more developers to explore and test our platform. The public beta retains all the features and benefits of the private beta while incorporating valuable feedback from our early adopters. Additionally, we'll continue working closely with the Appwrite community to refine the platform and introduce new features based on user feedback. We also encourage you to join our Discord community where you can get help with anything Appwrite related! ### What’s next? We have a lot of things planned in the upcoming months while we await Cloud to become publicly available, and we’d love to share some highlights with you. #### Cloud interviews and surveys We’re conducting a series of interviews & surveys with participants of the Public Beta to get more insights into their requirements. During these interviews, we focus on understanding their background, their reasons for using a backend server like Appwrite, their use cases, their experience so far, along with their expectations and shortcomings of the platform. These interviews have provided key insights that have prompted us to steer in the right direction. If you’re part of the Beta and would like to participate in these interviews, please reach out to me on the Appwrite Discord or my email, and we’d be glad to set up a call. #### Pricing Our goal is to make Appwrite Cloud affordable and accessible to all developers. We’ve had some really great feedback from our initial pricing survey and are really close to sharing our model with you all. We will continue working together with the Appwrite community to refine this pricing model. Rest assured, there will be a generous free tier for all the hobby projects you’ve been wanting to create! #### Self-hosted edition A lot of us may have questions about the open source version of Appwrite and its future. We can proudly say, we will continue to stand among the handful of companies that have been open-source first. Our vibrant community is a testament that, for the longest time, we have prioritized the open source version, and we will continue to do so. In fact, all major features will roll out to the open source version even before they hit the cloud! Appwrite will forever remain open source! #### Multi-region support During the Beta, we’re operating with Frankfurt as our primary region. This is a careful decision to ensure we are geographically centrally located. As we get closer to general availability, we will focus on 3 more regions, namely San Francisco, New York, and Singapore with more regions to follow. #### Compliance Compliances are frameworks that help organizations ensure they are meeting certain standards and requirements in regard to data security and privacy. For most SaaS/BaaS companies, SOC 2, HIPAA & GDPR are the essential ones. These certifications help companies demonstrate to their customers and stakeholders they are taking data security and privacy seriously, and are committed to protecting their sensitive information. These usually involve a lot of paperwork, documentation, and administrative processes. We’re in the process of ensuring we meet the highest standards for data security and privacy. #### Support for more function runtimes While in beta, Appwrite Cloud supports five serverless runtimes, namely, Node, Python, PHP, Ruby, and Dart. As we approach general availability, we will continue to add support for our entire suite of runtimes based on the requests and requirements of the beta participants. ### Coming up We’ve set multiple goals and key performance indicators to help us determine the success of this important stage before making the Appwrite Cloud generally available. Those indicators include the feedback and insights we get from beta participants, consumption metrics, and our infrastructure resilience during this time. Once those are achieved, we'll officially announce the release of the Appwrite Cloud platform and reach general availability. As always, huge thanks to the Appwrite community for your unwavering support. This milestone would not have been possible without your contributions. You're all incredible, and we'll continue working hard to deliver the development platform you deserve. --- ## Announcing Appwrite's public roadmap https://appwrite.io/blog/post/public-roadmap-announcement We are excited to announce the launch of Appwrite’s public roadmap on GitHub. The new roadmap is part of our commitment to the Appwrite community and our core values of transparency and collaboration. It was one of the most highly requested features by the Appwrite community and will serve as a tool for increased collaboration among the core team, contributors, maintainers, and Appwrite developers. By making our development plans public, we aim to provide our community with better visibility into our priorities, streamline the process for reporting bugs and requesting features, and align expectations around platform release timelines. ### Why a public roadmap? #### Increasing transparency Open communication with the community is crucial for building trust and fostering a collaborative environment. Our public roadmap will show what we are working on, what we plan to tackle next, what features or fixes are on the horizon, and what their release timelines might look like. #### Prioritizing community needs The community is the heart of Appwrite, and your feedback plays a significant role in shaping our development path. The public roadmap lets you directly influence our priorities by highlighting the most critical bugs and requested features. This guarantees that we focus on the areas that matter most to you. The Appwrite community really is the secret sauce that ensures that the Appwrite project always moves in the right direction. #### Aligning expectations We understand that knowing when new features and fixes will be available is important for planning and integrating Appwrite into your products. The public roadmap provides clear timelines and updates on our progress, helping you better plan your development cycles, communicate them to your stakeholders, and set expectations accordingly. ### How to use the roadmap #### Accessing the roadmap You can access our [public roadmap on GitHub](https://github.com/orgs/appwrite/projects). The roadmap is organized into different projects for each release, with issues or pull requests assigned to specific versions to indicate when we plan to complete the changes. For example, you can check out the changes planned for version 1.5.8 to see what’s coming up next. #### Reporting bugs and requesting features To report a bug or request a new feature, simply create an issue on our GitHub repository. When submitting an issue, please provide as much detail as possible, including steps to reproduce bugs and the specific needs for feature requests. This information helps our team to understand and prioritize the issue effectively. #### Engaging with the community The public roadmap is not just a tool for visibility but also a platform for collaboration. You can comment on existing issues, provide additional context, and even contribute to discussions around feature development. By upvoting bugs or features requests on GitHub you help us prioritize between the different issues and pull requests. Your insights and contributions are invaluable in helping us build a better Appwrite. #### Staying updated We encourage you to check the public roadmap regularly to stay updated on our progress. You can also watch the repository to receive notifications about new issues, updates, and releases. This way, you will always be in the loop about what’s happening with Appwrite. #### Flexibility and future planning While the new roadmap offers much more flexibility and visibility into our short-term and mid-term plans, it might still miss some bigger, longer-term projects or milestones that are in the early planning stages or going through an internal or external RFC (Request for Comments) process. These initiatives require more detailed planning and consultation before they can be added to the public roadmap. ### The roadmap framework To ensure our focus is always on the right areas, we have developed an internal framework to guide our roadmap decisions. This framework is based on the significance of our focus areas, allowing us to set clear priorities, measure progress, understand the impact, maintain balance, and foster collaboration within the team and the Appwrite community. ![Roadmap framework](/images/blog/public-roadmap-announcement/framework.png) By concentrating on specific aspects, we ensure our team stays aligned and works towards common goals that reflect our mission while continually improving the overall developer experience. Our roadmap framework revolves around four main pillars: 1. **Friction**: Make it easier for developers to adopt and succeed with Appwrite by streamlining our processes and improving the overall developer experience. 2. **Reliability**: Ensure Appwrite is a reliable, scalable, high-performing solution for all users. 3. **Growth**: Expand Appwrite’s product offerings, enabling more growth-led activities and catering to a broader range of use cases. 4. **Revenue**: Ensure the long-term success and sustainability of Appwrite. Business success is crucial for a better platform and ecosystem for the entire Appwrite community. These four focus areas help us prioritize, make decisions, and ensure we move toward a better future for Appwrite and the Appwrite community. ### This is a journey We look forward to taking this step towards greater transparency and collaboration, and we invite you to join us on this journey. Your feedback, ideas, and participation are crucial in making Appwrite the best it can be. Together, we can build a more robust, reliable, and feature-rich platform. The platform developers deserve to have. Thank you for being a part of the Appwrite community. We look forward to your contributions and continued support. --- ## Best practices for sending push notifications https://appwrite.io/blog/post/push-notifications-best-practices Push notifications are messages that appear on a mobile device or computer from an app or website. They serve various purposes, such as alerts, promotions, reminders, and more. They are essential for delivering real-time updates and alerts, enhancing the overall user experience. Push notifications can also be used to increase engagement and retention and even for security purposes. ### Different types of push notifications Before we dive right into the best practices of sending a push notification, let us learn about the different types and the purpose they serve: - **Standard push notifications**: These are the basic form of push notifications, often used for general purposes such as updates, news, or alerts. They typically include a message and can sometimes have a call to action. - **Rich push notifications**: These notifications are enhanced with media like images, videos, or sound. Rich push notifications are more engaging and can be used for marketing campaigns, feature highlights, or to provide a more immersive user experience. - **Interactive push notifications**: These notifications allow users to interact directly from the notification itself without opening the app. Actions can include replying to a message, liking a post, or snoozing an alarm. - **Geotargeted push notifications**: Triggered when a user enters or leaves a specific geographic location. These are particularly useful for retail apps, offering special deals when a user is near a store, or for travel apps providing location-specific information. - **Segmented notifications**: These are targeted to specific segments of your user base based on factors like user behavior, preferences, or demographics. This segmentation ensures that users receive relevant and personalized content. - **Behavioral notifications**: Triggered by specific user actions within the app, like abandoning a shopping cart or completing a level in a game. These notifications are personalized based on the user's behavior. - **Transactional notifications**: Used for sending information about transactions a user has performed, such as purchase confirmations, shipping updates, or banking alerts. - **Reminder notifications**: Sent to remind users about an upcoming event or an incomplete task or to re-engage users who haven’t used the app for a specific period. - **Silent notifications**: These do not alert the user or appear on the device's lock screen. Instead, they quietly deliver data to the app, which can trigger an app update or content refresh. - **Persistent notifications**: These remain on the user's screen until they are acted upon and are typically used for critical alerts that require immediate attention. ### Best practices for sending push notifications [Helplama](https://helpdesk.helplama.com/what-do-consumers-think-about-push-notifications/) discovered that sending 2-5 push notifications per week results in 43% of the app users disabling push notifications. On the other hand, sending 6-10 push notifications per week leads to 30% of users dropping off the app entirely. The content of the messages plays a crucial role. Here are some best practices to retain users: - **Prioritize user consent and preferences**: Always seek user consent for notifications and provide clear options for users to manage their preferences. Respecting user choices builds trust and enhances user experience. - **Personalize and segment**: Tailor notifications based on user behavior, preferences, and demographics. Segmenting your audience ensures that messages are relevant and engaging for each user group. - **Craft clear and concise messages**: Ensure that your messages are straightforward, easy to understand, and convey the core message quickly. - **Utilize rich media wisely**: Incorporate images, videos, or sounds to enrich notifications, but do so sensibly to avoid overwhelming users. - **Time notifications appropriately**: Send notifications at times when users are most likely to be receptive. Consider time zones and user routines, and avoid disruptive hours. - **Maintain a balanced frequency**: Overloading users with notifications can lead to annoyance and app uninstalls. Find a balance that keeps users informed without overwhelming them. - **Implement A/B testing**: Regularly test different versions of notifications to see what works best in terms of content, timing, and frequency. - **Incorporate a strong Call to Action (CTA)**: Encourage user engagement with a clear and compelling CTA. - **Measure and analyze performance**: Use analytics to monitor the performance of your notifications. This data is invaluable for optimizing strategy and improving engagement. - **Ensure relevance and value**: Notifications should always offer something of value to the user, whether it's informative, entertaining, or promotional. - **Adapt to platform differences**: Tailor your approach to fit the guidelines and best practices of different platforms (iOS, Android, web, etc.). - **Use notifications for important alerts only**: Reserve push notifications for important or urgent messages to maintain their effectiveness. - **Provide an easy opt-out option**: Users should be able to easily opt out of receiving notifications, which respects their preferences and improves overall satisfaction. ### Enabling push notifications for your app using Appwrite The newly released Appwrite Messaging substantially simplifies your process of sending out push notifications to your application’s users. We have added simple-to-use adapters for Firebase Cloud Messaging (FCM) and Apple Push Notification Service (APNS) to let you send push notifications to let you send push notifications to both Android and iOS users. ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key const message = await messaging.createPush({ messageId: sdk.ID.unique(), title: 'Init_ Day 0', body: 'Appwrite Messaging is here!', topics: ['init-day-0'] }); ``` ### Resources Visit our documentation to learn more about push notifications, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/messaging/send-push-notifications) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Handle race conditions when running operations in Appwrite DB https://appwrite.io/blog/post/race-conditions-db-operators A race condition is a special condition where events that are supposed to occur in sequence do not happen in sequence due to external factors, such as latency. These conditions are not ideal, as they could lead to outdated and incorrect data. Race conditions also exist in databases. If you're performing database operations through your app, it could get scary really quickly. In this article, we will examine how race conditions can infiltrate your apps and how to prevent them using the new database operators in Appwrite. ### A look at race conditions Let's take a look at an example of how race conditions can lead to outdated and invalid data. Concurrent requests can cause race conditions, where if many requests are initiated at a single point in time, some of them might fail to register because old values are fetched and operated upon multiple times due to concurrency. Here's an example of how this would typically happen in an Appwrite database. ```jsx // Get the counter value const counter = await tablesdb.getRow({ databaseId: "test-db", tableId: "counters", rowId: "first", }); // Increment the counter await tablesdb.updateRow({ databaseId: "test-db", tableId: "counters", rowId: "first", data: { value: counter.value + 1, }, }); ``` In the above code block, if multiple requests are sent simultaneously, there is a chance that the `counter` remains the same across multiple requests, missing an update or updating completely incorrectly. I have created a script to demonstrate this. If you want to check it out, I have published it on my [GitHub Repository](https://github.com/atharvadeosthale/db-operators-appwrite). Running the script on a database with a row ID `first` and default `value` being 0, I got the following output. ``` ====================================================================== 🔴 Race Condition: Unsafe Read-Modify-Write ====================================================================== 📥 Initial: 0 🚀 Running 25 concurrent increments... ────────────────────────────────────────────────────────────────────── 📈 Results ────────────────────────────────────────────────────────────────────── ✅ Expected: 25 📊 Actual: 1 💥 Lost: 24 updates 🔍 Race detected: 24 workers read value 0 simultaneously ====================================================================== ``` As you can see, when 25 requests were fired simultaneously, only one request successfully registered the correct value in the database, while the others retained the old values. This can be disastrous in data-sensitive applications where every request must account towards a change in database values. ### The solution To combat this problem, we recently announced [a set of DB operators](/docs/products/databases/operators) that you can use, which would atomically operate on the server side, ensuring that operations in all requests are captured successfully. Let's take a look at an example code snippet that shows an accurate representation of how you can increment the counter successfully at each request using DB operators. ```jsx // Increment the counter await tablesdb.updateRow({ databaseId: "test-db", tableId: "counters", rowId: "first", data: { value: Operator.increment(1), }, }); ``` As you can already tell from the above code snippet, we no longer need to fetch the value from the DB, which was the main cause of the race condition occurring. Instead, we pass in `Operator.increment(1)` as `value`. Removing the `getRow()` call also saves bandwidth in this case, and could be a substantial improvement if you do operations like these on scale. This ensures the increment logic is performed atomically on the server, instead of the application layer. Now, let's run the script again, but with the DB operator in use. ``` ====================================================================== 🟢 Safe Increment: Using Atomic Operations ====================================================================== 📥 Initial: 0 🚀 Running 25 concurrent increments... ────────────────────────────────────────────────────────────────────── 📈 Results ────────────────────────────────────────────────────────────────────── ✅ Expected: 25 📊 Actual: 25 💥 Lost: 0 updates ✅ No race condition: All increments processed atomically ====================================================================== ``` As you can see, all the concurrent requests now register the increment, and the final `value` is 25, which can be double-checked in the database. Feel free to run this test yourself to verify the outcome. ### Wrapping up While learning to code, developers often find handling increments or any such operations on the application layer much easier. However, these operations tend to break when race conditions are introduced on scale, just as we explored in this article. DB operators ensure that your operations don't break when your app reaches the masses. **More resources** - [Announcing DB operators: Update multiple fields without fetching the entire row](/blog/post/announcing-db-operators) - [Announcing Atomic numeric operations: Safe, server-side increments and decrements](/blog/post/announcing-atomic-numeric-operations) - [DB operators documentation](/docs/products/databases/operators) --- ## Setting up protected routes in React https://appwrite.io/blog/post/react-protected-routes In this tutorial, we will explore a straightforward method for implementing protected routes in a React application. The aim is to ensure that users can only access certain pages, such as home and profile, after passing an authentication check. If a user is not authenticated, they will be redirected to the login page. ### React protected routes To accomplish this, we will create a component called `ProtectedRoutes` that wraps around any routes that need protection. This setup allows us to run an authentication check before rendering these pages. Here are the steps. #### Creating the component First, create a new file named `ProtectedRoutes.jsx`. In this file, you will import `Outlet` and `Navigate` from React Router Dom. `Outlet` allows for rendering nested routes, while `Navigate` will be used to redirect our users if they are not authenticated. Below is a basic structure for the `ProtectedRoutes` component: ```jsx import { Outlet, navigate } from 'react-router-dom'; const ProtectedRoutes = () => { const user = null; // Simulate an unauthenticated user return user ? : // Redirect to login if not authenticated export default ProtectedRoutes; ``` #### Integrating the component into your app With the `ProtectedRoutes` component created, the next step is to wrap the routes we want to protect. We can nest all child routes by using the standard `` component and by passing in `` as the element into the parent route. ```jsx //App.jsx ... import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import ProtectedRoutes from './utils/ProtectedRoutes'; function App(){ return } path="/login"'/> {/* 👇 Wrap your protected routes */} }> <Route element={} path="/"/> <Route element= {} path="/profile"/> } ``` #### Understanding the flow When a user attempts to access `/home` or `/profile`, the `ProtectedRoutes` component checks if a user is authenticated. If the user exists, the corresponding component uses `Outlet` to allow routing to continue down to the nested routes. If not, the user is redirected to the login page. ### Testing your setup After completing the setup, it’s important to test the application. Try navigating to the protected routes. If authentication has not been established, you should be redirected to the login page. ### Conclusion In summary, you have implemented protected routes in your React application. By creating a dedicated component to manage authentication checks, you can ensure that only authorized users gain access to specific pages. This method provides a clear and efficient way to handle route protection in your application. Check out some more React resources below: - [Protected routes in React video tutorial](https://www.youtube.com/watch?v=pyfwQUc5Ssk) - [React quick start with Appwrite](https://appwrite.io/docs/quick-starts/react-native) - [Set up Google auth in React](https://appwrite.io/blog/post/set-up-google-auth-appwrite-react) - [Build a cross-platform application in React Native](https://appwrite.io/blog/post/building-cross-platform-applications-with-react-native) --- ## Simplify your CI pipeline and ship faster with Appwrite https://appwrite.io/blog/post/reasons-to-run-your-ci-pipeline-on-appwrite Modern applications rely heavily on CI pipelines. They’re the backbone of your delivery flow, the place where builds happen, tests run, and deployments get the final green light. But for many teams, CI still lives outside the hosting and runtime platform. That means juggling multiple services, maintaining extra integrations, and paying for compute twice. If you’re already using Appwrite Functions to run your backend workloads or Appwrite Sites to deploy your frontend, there’s no reason to keep your CI somewhere else. You can build, test, and validate your application directly on the same platform you use to run it in production. In this blog, we’ll break down **eight reasons why keeping your CI pipeline on Appwrite** can simplify your workflow, ensure production parity, protect your deployments, and save costs. All while keeping everything in one place. ### 1. A single platform simplifies everything When your build, test, and deploy steps live in different places, complexity grows fast. You end up syncing environment variables between platforms, maintaining extra credentials, and dealing with mismatched environments that behave differently at runtime. By running your builds and tests on Appwrite, your entire workflow stays in one place. You build and test on the same infrastructure that will serve your production traffic. You don’t need to mirror environments, because it’s already the exact same environment. That alignment removes a large category of CI flakiness and “it works locally but fails in prod” issues that can slow down even the best teams. ### 2. Production parity for real CI pipelines often run on abstracted or limited environments that behave differently from production. That’s a risk, because a passing test in CI doesn’t guarantee the code will behave the same way in production. With Appwrite, your tests run inside the same containerized runtime as your production workloads. For developers, this is a big deal. It means no unexpected dependency differences, no network or runtime mismatches, and no hidden constraints that appear only after deployment. What you validate during build and test is exactly what will run when you release. This improves confidence, reduces rollbacks, and helps teams ship faster. ### 3. Protecting your deployments with real tests A good CI pipeline isn’t just about building code. It’s about protecting production. Running your tests, checks, and other validation steps on Appwrite allows you to automatically gate deployments. Only healthy builds that pass your tests make it to production. You can run unit tests, integration tests, smoke tests, or more complex operational checks as part of the same process that builds your function or site. This creates a strong safety net without introducing extra infrastructure or services into the workflow. ### 4. Visibility where it matters Appwrite’s GitHub integration gives you the same level of visibility you’d expect from a dedicated CI service. You can see the status of your builds and tests directly in your pull requests, track whether a step has failed, and access logs and status information in real time. For senior developers and team leads, this provides the kind of operational clarity that’s often lost when CI runs in a separate environment. You don’t need to jump between multiple dashboards to understand what went wrong. Everything you need to debug a failed build or a flaky test lives inside the same platform that runs your app. ### 5. Real-world workflows that fit naturally Many existing CI use cases fit naturally into Appwrite without major changes. Teams can run automated build pipelines for both frontend and backend projects, execute test suites on every pull request, perform integration checks against staging environments, and automate pre-deployment smoke tests to catch regressions before they reach users. Because all of this happens in the same environment where the app will ultimately run, the feedback loop is tighter and the risk surface is smaller. ### 6. Cost efficiency and consolidation When comparing compute pricing between Appwrite and GitHub Actions, the numbers tell an important story. Appwrite charges per GB-hour, while GitHub Actions charges per minute of runner time. To make the comparison fair, we converted GitHub’s per-minute pricing into an hourly and per-GB equivalent. GitHub’s standard Linux runner is priced at $0.008 per minute, which comes out to $0.48 per hour. Each runner includes 7 GB of memory, so the effective price is roughly **$0.068 per GB-hour** ($0.48 ÷ 7 GB). Appwrite’s extra usage cost is **$0.06 per GB-hour**. On top of that, Appwrite’s Pro plan already includes **1,000 GB-hours of compute** at no extra charge, the equivalent of running 1 GB of compute continuously for over 40 days, included in the base price. | Use case | Description | Appwrite cost | GitHub Actions (Linux) | % difference | | --- | --- | --- | --- | --- | | Small job (1 GB, 1 hour) | Light workload | $0.06 | $0.069 | **+15%** | | Medium job (2 GB, 2 hours) | Build/test task | $0.24 | $0.274 | **+14%** | | Heavy job (4 GB, 4 hours) | Long-running build | $0.96 | $1.097 | **+14%** | This comparison uses GitHub’s default Linux runner as the baseline. Since GitHub charges a flat price per runner regardless of how much memory you actually use, smaller workloads effectively pay for unused capacity. Appwrite, on the other hand, charges directly based on the memory you allocate. By normalizing GitHub’s cost to a 1 GB baseline, we can compare both platforms on a like-for-like basis. The key difference isn’t just the small gap in per-GB pricing, it’s **how that pricing scales**. GitHub locks you into the full runner price, even if your job only uses a fraction of the available memory. Appwrite gives you granular control over how much memory you allocate and charges accordingly. Add to that the **1,000 GB-hours included** in Appwrite’s Pro plan, and many teams can run their workloads without paying any overage fees at all. This combination of included usage and flexible scaling is where Appwrite delivers a meaningful pricing advantage for CI/CD and serverless workloads. ### 7. Scaling when you need to Appwrite allows you to scale your test workloads just like any other function or deployment. You can increase CPU and memory resources, run multiple builds in parallel, and handle more demanding test suites without worrying about external runner limits. This flexibility makes it possible to run the same lightweight pipelines early in development and scale them seamlessly as your application and team grow. It also lets you keep operational control in-house instead of depending on third-party runner capacity or pricing tiers. ### 8. Fewer tools, stronger pipelines Every external service in your stack introduces more integrations to maintain, more secrets to manage, and more invoices to track. By using Appwrite for CI, your team can remove an entire layer of complexity without sacrificing the capabilities they rely on. You build, test, and deploy in the same environment. You protect your deployments with real tests. You get observability directly in GitHub. And you save money by consolidating your computing under a single platform. ### Consolidating your CI pipeline CI doesn’t have to live somewhere else. If you’re already using Appwrite for your serverless functions or sites, you can also use it to run your builds and tests. You gain runtime parity with production, strong deployment protection, full visibility in GitHub, and a cost model that’s built to scale without duplication. For teams that care about speed, reliability, and simplicity, keeping everything in one place isn’t just cleaner, it’s smarter. ### More resources - [Learn more about Function deployments](/docs/products/functions/deployments) - [Appwrite Sites deployments](/docs/products/sites/deployments) --- ## Remix 3: what's changing and why it matters https://appwrite.io/blog/post/remix-3-whats-changing-and-why-it-matters For a while, it seemed like Remix had reached a natural end. By late 2024, almost everything that made Remix unique, its loaders, actions, and nested routing, had been merged into React Router 7. The framework's creators, Ryan Florence and Michael Jackson, even joked that Remix 2 had become "React Router, the Framework." Many developers assumed Remix would fade away. But in May 2025, the team [announced Remix 3](https://remix.run/blog/wake-up-remix), an unexpected rebuild from the ground up, meant to rethink how web apps are built. They showed early previews at [Remix Jam 2025](https://remix.run/blog/remix-jam-2025-recap), and while the first version is targeted for early 2026, the goals are already clear. Remix 3 breaks away from React entirely and rebuilds around web standards. It's a big step. The framework now runs directly on the Fetch API (using standard `Request` and `Response` objects), instead of Node's proprietary `req` and `res` system. This means it can run on any JavaScript runtime; Node, Deno, or Bun, without adapters. It still supports the same patterns developers liked in Remix 2, but without depending on React's lifecycle or its virtual DOM. The team calls this a move toward being "closer to the web itself." ### Why the Remix team chose to rebuild in version 3 When Remix started, React was the natural foundation. Its component model, rendering flow, and community gave Remix an instant head start. But over time, that foundation began to limit what the team could do. By 2024, the React ecosystem had grown complex. Developers had to manage new patterns like Server Components, `use server` and `use client` boundaries, Suspense, and a flood of new hooks. Next.js, the most visible React framework, kept evolving quickly, but often at the cost of simplicity. Remix 2, meanwhile, had thinned out. Most of its core logic had moved into React Router, and the extra layer Remix added on top didn't feel worth maintaining. The team decided to stop chasing React's roadmap and instead design something simpler that would work the same way anywhere JavaScript runs. This rebuild also reflects a return to the web's own strengths. Browsers already know how to handle navigation, forms, and requests. The Fetch API, URL, Request, and Response objects are now available on both the server and client. Remix 3 builds directly on these features instead of wrapping them in extra abstractions. The goal is to make web development less about "framework rules" and more about the platform that's already there. ### The six principles guiding Remix 3 The team described six principles that define Remix 3's direction: 1. Model-first development. Write code that's easy for both people and tools to understand. Each route's intent should be clear about what data it loads, what actions it performs, and how it fits into the overall app. This makes it easier to maintain and to reason about, whether by a developer or an AI tool assisting with code. 2. Build on web APIs. Use standards like Fetch, Request, Response, and FormData directly. This means a Remix 3 app written for Node can also run on Deno or other JavaScript runtimes without code changes. 3. Religious about runtime. Avoid build-time "magic." Features should work at runtime, without a custom bundler or code generator. You can still use TypeScript or JSX, but what you write is close to what actually runs. 4. Avoid heavy dependencies. Remix 3 aims to depend on as little third-party code as possible. Instead of relying on frameworks like React or Express, it implements small, focused modules that the team can control and evolve. 5. Demand composition. Every piece of Remix 3 is built to work on its own. The router, server utilities, and data parsers can each be used separately or as part of the full framework. This modular philosophy is similar to [TanStack](https://tanstack.com/), where each tool is independently useful but works well together. 6. Distribute as one. Even though it's modular inside, Remix 3 will ship as a single package. Developers won't need to assemble ten different pieces; the framework will present a unified API. Together, these principles explain why Remix 3 looks so different. The team wants a system that's modular and flexible internally, but simple and predictable on the surface. ### A new architecture built on web standards Remix 3's architecture centers on the Fetch API. Every incoming request, whether from a browser, a test, or an edge worker, is a standard `Request` object. Every response the framework sends is a `Response` object. At the heart of the system is a new router called `@remix-run/fetch-router`. It's type-safe, explicit, and doesn't depend on file naming conventions. You define routes in code, often in a `routes.ts` file, where each route has a name and a path. For example: ```ts export const routes = route({ books: { index: "/books", show: "/books/:slug" } }); ``` From there, Remix can infer types and generate helpers like `routes.books.show.href({ slug: "gatsby" })`. This keeps links type-safe and prevents mistakes. It also lets the router generate correct URLs for any environment. ### The new rendering model The biggest change is that Remix 3 no longer uses React. You still write JSX, but it's handled by a custom component model built specifically for Remix. There's no virtual DOM. When something changes, you update state directly and call `this.update()` to refresh that part of the UI. Events use the browser's native event system instead of React's synthetic one. This makes the rendering model closer to plain JavaScript. Developers can see exactly what's happening without guessing at hidden framework behavior. The trade-off is that you need to be more deliberate about updates, there's no `useState` hook to trigger automatic re-renders. But it also means fewer performance surprises and much less overhead. To handle partial updates, Remix 3 introduces a feature called Frames. A Frame is a section of the page that can load and update independently. When a Frame refreshes, the server sends back a small HTML fragment, and Remix patches it into the DOM. This allows streaming updates without extra JavaScript or JSON APIs. This approach is similar to [HTMX](https://htmx.org/), where the server sends HTML instead of JSON, keeping the logic server-side while maintaining interactivity. For example, a comments section could live inside a Frame. When a user submits a new comment, the server renders updated HTML for that frame and sends it down. The rest of the page stays untouched. It feels like client-side interactivity, but it's still server-driven and progressively enhanced. ### Data loading and actions The loader and action pattern that defined Remix 2 is still here. Each route can export handlers for different HTTP methods. A `GET` handler fetches data to render; a `POST` (or `PUT`, `DELETE`) handler performs a mutation. Here's a simple example: ```ts export const handlers = { async GET({ params }) { const post = await getPost(params.slug); if (!post) return render(, { status: 404 }); return render(); }, async POST({ request, params }) { const form = await request.formData(); const comment = form.get("comment"); await saveComment(params.slug, comment); return redirect(routes.posts.show.href({ slug: params.slug })); } }; ``` In this setup, the `GET` handler runs on the server and renders HTML for the initial page. The `POST` handler processes a submitted `
    `, updates data, and redirects. Remix then re-runs the necessary loaders to refresh the UI. This is a key advantage: Remix runs JavaScript on the server but leverages existing browser capabilities on the client side. This makes apps more accessible to parsers, devices with limited JavaScript support, and browsers with JavaScript disabled. The browser handles POST and redirect using standard HTML forms, which works even without JavaScript. This pattern keeps the app predictable. Data always comes from one place, the loader for that route, and every action leads to a clear revalidation path. No client-side fetching hooks or state libraries are needed. ### Error handling Error handling in Remix 3 continues the route-based model from Remix 2. Each route can define its own error boundary so that failures stay local. If a child route fails to load or render, only that section shows an error message, while the rest of the page remains intact. When a loader throws a `Response` with an error status, Remix automatically triggers the matching error boundary. For example, if a loader throws `new Response("Not found", { status: 404 })`, Remix will show the Not Found UI for that route but keep parent layouts mounted. This approach prevents a single broken component from blanking the entire page (a common problem in many SPAs) and helps developers handle network or data errors cleanly. ### TypeScript and developer experience Remix 3 is written in TypeScript from the start, so every route, loader, and action is strongly typed. Route parameters and return types are inferred automatically. For example, if `routes.books.show` has a `:slug` parameter, TypeScript will know that `params.slug` is a string inside its handlers. You can pair this with schema validation libraries like [Zod](https://zod.dev/) to validate form data and API inputs at runtime. Because Remix avoids code generation, you don't need a separate typegen step. The framework infers everything from the code you already write. The same runtime-first philosophy also means you can run your app directly in `ts-node` during development, without waiting for a full build. The development workflow is simpler, too. The [preview demos](https://remix.run/blog/remix-jam-2025-recap) show a single `remix dev` command that starts a local server, watches files, and reloads automatically, no webpack config or complex bundler setup required. While Remix 3 is still in preview, you can follow the [official blog](https://remix.run/blog) and [GitHub repository](https://github.com/remix-run/remix) for early access and updates. ### Deployment and environment support Remix 3's platform independence is one of its biggest selling points. Since it's built on the Fetch API, the same app can run on Node, Deno, or Bun with almost no changes. The router handles incoming requests as `Request` objects and produces `Response` objects, so deployment often comes down to a few lines: ```ts import { createRequestListener } from "@remix-run/serve"; import { router } from "./router"; export default createRequestListener({ router }); ``` That pattern works in Node, [Appwrite Sites](https://appwrite.io/sites), Netlify, Vercel or on edge workers. Because the framework doesn't rely on Node-specific APIs, there's nothing to rewrite when switching platforms. This also makes Remix 3 easier to scale. You can deploy it as serverless functions, edge workers, or a long-running Node process depending on your needs. The framework doesn't force a specific host or vendor. That independence is deliberate; the team wants Remix 3 apps to run anywhere, without lock-in. ### Trade-offs and what to expect Rebuilding a framework from scratch comes with trade-offs. Dropping React means losing direct access to its huge ecosystem. Libraries like Material UI or React Query won't plug in directly. Developers will either use plain web components, small JS libraries, or future Remix-specific packages. There's also a learning curve. Without React's hooks or automatic rendering, you'll manage updates yourself. That's simpler in concept but different in habit. It's a shift from "declare and forget" to "update when needed." Still, the potential benefits are clear: less complexity, less JavaScript on the client, and better performance out of the box. The approach brings Remix closer to the strengths of the web, fast first loads, resilient forms, and consistent server logic, while keeping the conveniences of a single framework. ### Looking ahead Remix 3 is still in preview, but it's already one of the most interesting moves in web development right now. It reflects a broader shift in how developers think about the web: away from heavy client frameworks and back toward simplicity, standards, and server-driven logic. For teams using Remix 2 or React Router 7 today, nothing breaks overnight. Those tools remain stable and supported. Remix 3 is the next step, a framework rebuilt on what the web already does well. As development continues through 2025 and 2026, the framework will gain polish and documentation. When it reaches a stable release, developers can expect a system that feels lighter, easier to deploy, and closer to the way the web naturally works. Remix 3 isn't finished, but its direction is clear: a return to fundamentals, built for a web that doesn't need to be reinvented. --- ## REST vs GraphQL vs WebSockets: which is best for your app? https://appwrite.io/blog/post/rest-vs-graphql-websockets-which-is-best-for-your-app In software development, APIs are the lifeblood of communication between various components of an application. They set the rules for data exchange, ensuring smooth interaction between systems. In web and mobile development, selecting the right APIs will make or break your app's user experience, performance, and costs. At [Appwrite](https://appwrite.io/), we provide an API-agnostic approach, letting developers choose from various types of APIs that are best suited for their needs. There's no one-size-fits-all solution for backend-client communication. The following overview will help you understand each API's pros and cons so you can choose the best option for your product. ### REST and modern web communication **What is REST?** Representational State Transfer (REST) is a stateless, request-response protocol that serves as the foundation of data communication on the web. It is simple, widely understood, and supported by virtually all web clients and servers. **Technical details:** - **Methods:** GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD - **Status Codes:** 1xx (Informational), 2xx (Success), 3xx (Redirection), 4xx (Client Error), 5xx (Server Error) - check out the [full list of status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). - **Headers:** Provide metadata (e.g., Content-Type, Authorization, Cache-Control, and more) - **Body**: Contains the payload of your data ``` Client Server | | | ------ GET Request --------> | | | | <------- Response ----------- | | | ``` #### Use cases 1. **CRUD operations:** - **Products:** Blog platforms, content management systems (CMS), e-commerce sites. - **Why:** REST's architectural style is well-suited for Create, Read, Update, and Delete operations, enabling easy management of resources through standard HTTP methods (GET, POST, PUT, DELETE). 2. **Public APIs:** - **Products:** Social media platforms, payment gateways, data providers. - **Why:** RESTful APIs are easy to use and widely adopted, making them ideal for exposing functionalities and data to external developers and third-party services. 3. **IoT devices:** - **Products:** Smart home devices, wearable technology. - **Why:** REST's simplicity and statelessness make it suitable for IoT devices, which often need to send data to and receive commands from a server with minimal overhead. 4. Mobile and web applications: - **Products:** Mobile apps, single-page applications (SPAs). - **Why:** REST APIs allow mobile and web applications to communicate with back-end servers efficiently, fetching data, and performing actions without requiring heavy server-side frameworks. #### How Appwrite uses REST Appwrite uses REST as the primary API for communication between clients and the backend services using the different Appwrite SDKs. All REST API requests in Appwrite are made over HTTP, making it easy to interact with the backend services using standard HTTP methods. Due to the simple nature of HTTP and REST, this is usually the best choice for getting started with Appwrite. ### WebSockets: enabling real-time communication **What are WebSockets?** WebSockets provide a full-duplex communication channel over a single, long-lived connection. This protocol allows for real-time data exchange between the client and server, which is essential for applications requiring instant updates, such as chat apps or live notifications. **Technical details:** - **Handshake:** Starts as an HTTP connection and upgrades to WebSocket - **Communication:** Bi-directional, allowing data to be sent and received simultaneously - **Frames:** Text and binary frames for data exchange ``` Client Server | | | -- WebSocket handshake ---> | | <--- Handshake response -- | | | | <==========================> | | Bi-directional data flow | | | ``` #### Use cases 1. **Realtime applications:** - **Products:** Chat applications, live sports updates, trading platforms. - **Why:** WebSockets' bi-directional communication allows for instant data updates, making it perfect for real-time interactions. 2. **Collaborative tools:** - **Products:** Online collaborative editors, project management tools. - **Why:** WebSockets enable real-time data synchronization, allowing multiple users to collaborate seamlessly. 3. **Live feeds:** - **Products:** Social media feeds, live notifications. - **Why:** WebSockets can push updates to clients immediately, ensuring that users receive the latest information without delay. 4. **Gaming:** - **Products:** Multiplayer online games. - **Why:** WebSockets' low latency and continuous data exchange are crucial for the smooth operation of multiplayer games. #### How Appwrite uses WebSockets Appwrite leverages WebSockets to offer real-time capabilities. For example, when a database entry is updated, a WebSocket connection can notify all subscribed clients of the change in real time. This ensures that users always have the most up-to-date information without needing to refresh the page. All of the Appwrite client SDKs for Web, Flutter, iOS, or Android come with built-in abstraction for the real-time API, which allows you to easily listen to different channels and register your callbacks. ``` import { Client } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); client.subscribe('account', response => { console.log(response); }); ``` ### GraphQL: Efficient data querying **What is GraphQL?** GraphQL is a query language for APIs and a runtime for executing those queries using a defined type system for your data. It allows clients to request exactly the data they need, reducing the amount of data transferred and improving performance. **Technical details:** - **Schema:** Defines types, queries, mutations, and subscriptions - **Queries:** Precise data fetching, multiple resources in a single request - **Mutations:** For writing data - **Subscriptions:** For real-time updates ``` Client Server | | | ----- GraphQL query --------> | | { | | user(id: "1") { | | id | | name | | email | | } | | } | | | | <---- Query response -------- | | { | | data: { | | user: { | | id: "1", | | name: "John Doe", | | email: "john@example.com"| | } | | } | | } | | | ``` #### Use cases 1. **Complex data requirements:** - **Products:** Data-heavy applications like dashboards, analytics tools. - **Why:** GraphQL enables clients to fetch nested and related data in a single request, reducing the need for multiple round-trips. 2. **Microservices architectures:** - **Products:** Applications built with a microservices approach. - **Why:** GraphQL can aggregate data from multiple services, providing a unified API for the client. 3. **Mobile applications:** - **Products:** Mobile apps with varying data needs depending on user interaction. - **Why:** GraphQL allows mobile clients to request only the data they need, reducing bandwidth usage and improving performance. 4. **API flexibility:** - **Products:** Platforms offering public APIs, SaaS products. - **Why:** GraphQL's flexibility allows developers to evolve their APIs without breaking existing clients, as clients specify their data requirements explicitly. #### How Appwrite uses GraphQL Appwrite supports GraphQL as an alternative to REST for data querying. This enables developers to fetch precisely the data they need, optimizing network usage and improving application performance. GraphQL's flexibility also allows for more complex queries and interactions with the backend. You can use GraphQL to perform multiple operations in a single network request. The Appwrite GraphQL API is compatible with any standard GraphQL client available for your programming language. ```graphql query GetAccount { accountGet { _id email } } { "data": { "accountGet": { "_id": "...", "email": "..." } } } ``` ### Compare REST vs GraphQL vs WebSockets | Feature | REST | WebSockets | GraphQL | | --- | --- | --- | --- | | Communication | Request-Response | Full-Duplex | Request-Response | | Real-Time | No | Yes | Yes (with subscriptions) | | Data Transfer | Individual requests | Continuous stream | Precise queries | | Complexity | Low | High | Medium | | Latency | High (for real-time) | Low | Medium | | Use Case | CRUD operations, APIs | Live chats, notifications | Efficient data querying | ### Pros and cons | API | Pros | Cons | | --- | --- | --- | | REST | Simple, widely used | No real-time, stateless | | WebSockets | Real-time, low latency | More complex, resource-intensive | | GraphQL | Efficient data fetching, flexible queries | Complex implementation, potential over/under-fetching | ### How to choose your API? Understanding these APIs and protocols for communication is crucial for developers because they are the foundation of most web and mobile apps. Knowing the differences, pros, and cons helps you choose the best one for your needs or combine multiple types of APIs for better performance and user experience. It's important to remember that starting simple or experimenting can be the best approach. Not every product needs to be perfect from the start; great products evolve and improve over time. If you’d like to experiment with Appwrite, you can [get started here](https://appwrite.io/docs). --- ## Rethinking SaaS Authentication: Build secure, scalable experiences with Appwrite https://appwrite.io/blog/post/rethinking-saas-authentication In SaaS, authentication isn’t just a technical requirement—it's a key pillar of product quality, security, and growth. Whether you're guiding new users through onboarding, handling multi-tenant environments, or protecting APIs, how you manage identity can make or break customer trust and loyalty. In this guide, we’ll walk through the fundamentals of effective SaaS authentication, the core challenges every SaaS team faces, modern techniques that optimize both security and user experience, and how Appwrite helps you deliver it all—faster. ### What SaaS authentication really means [Authentication](/products/auth) verifies a user’s identity and grants access to data, services, or functionality. In SaaS, it must make user access effortless while ensuring security across distributed, cloud-native environments. A streamlined login flow keeps users engaged, but as identity attacks rise, strong security practices are now critical for protecting trust and ensuring your platform’s resilience. ### Key SaaS authentication challenges #### Finding the Balance: Security vs. User Friction Security measures like MFA, device verification, and session management often add steps to login flows. If not handled thoughtfully, they frustrate users. Technologies like **Single Sign-On (SSO)** help bridge this gap—offering both enhanced [security](/docs/products/auth/security) and smoother user experiences. Take a look at our [developer’s guide to user authentication](/blog/post/guide-to-user-authentication) for more in-depth insights. {% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Built-in security and compliance" point2="Multi-factor authentication" point3="Custom roles and permissions" point4="Session control and management" cta="Request a demo" url="https://appwrite.io/contact-us/enterprise" /%} #### Managing multi-tenant complexity Multi-tenancy is the backbone of SaaS, but it complicates identity management. Different tenants may need custom sign-in flows, roles, or [Identity Provider (IdP)](/blog/post/understanding-idp-vs-sp-initiated-sso) setups. Appwrite’s **Teams API** provides per-tenant identity management—supporting isolation, security, and flexibility at scale. #### Strengthening MFA without disrupting UX Multi-factor authentication is essential for SaaS security, but weak MFA implementations (like SMS OTPs) are vulnerable. Phishing-resistant methods like **TOTP apps** and **passkeys** are the new standard. With Appwrite, it's easy to enable TOTP 2FA and customize how and when users are challenged—balancing safety with usability. #### Scaling authentication seamlessly As your SaaS user base grows, authentication performance becomes mission-critical. Slow logins, session errors, or outages will frustrate users and damage your reputation. Appwrite’s architecture is designed for high scalability, keeping authentication fast and reliable, even under heavy load. #### Meeting compliance and privacy expectations Regulations like [GDPR](/docs/advanced/security/gdpr), [HIPAA](/docs/advanced/security/hipaa), and [SOC 2](/docs/advanced/security/soc2) demand strict user data protection and auditability. Appwrite’s built-in encryption, access controls, and regional hosting options make compliance simpler, helping you earn and maintain client trust. ### Proven SaaS authentication strategies #### Go passwordless Traditional passwords are painful and risky. Passwordless options like Magic URLs dramatically improve both security and user experience. Appwrite’s Magic URL feature lets users log in securely with a single click—no passwords, no friction. Learn more about [Magic URL](/docs/products/auth/magic-url). #### Offer Single Sign-On (SSO) Enterprise SaaS customers expect SSO integration with providers like Google Workspace, Okta, and Azure AD. Appwrite supports OAuth out of the box, and flexible extensions let you connect to custom or third-party IdPs easily. #### Embrace adaptive MFA Static MFA prompts can annoy users. Adaptive MFA adjusts authentication requirements based on risk signals—like device reputation or location. Appwrite enables dynamic security flows with Cloud Functions and custom verification logic. Want to add MFA to your app? We've got you covered in the [docs](/docs/products/auth/mfa). #### Prioritize secure API access APIs are core to SaaS platforms, but they're also prime targets for attacks. Secure your APIs with short-lived tokens, proper JWT signing, and strict audience validation. Appwrite’s secure session and token handling features make it easier to protect your backend services. #### Empower tenants with delegated management Your customers want control. Appwrite’s Teams API lets tenant admins manage users, permissions, and access policies themselves, reducing your operational load and improving client satisfaction. ### Choosing the right approach to SaaS authentication When building your auth stack, ask: - **Is it flexible enough?** Support for OAuth, SSO, magic links, MFA, and multi-tenant variations. - **Is it secure enough?** Encryption, secure tokens, ACLs, and strong MFA are non-negotiables. - **How fast can we ship?** Good SDKs, clear APIs, and customization options matter. - **Will it scale?** Performance under load and future-proofing are essential. - **Can we stay compliant?** Built-in privacy controls save time and headaches. Appwrite checks every box, giving you a robust, developer-friendly [authentication platform](/products/auth) without the heavyweight complexity. ### Ship SaaS authentication smarter with Appwrite Authentication isn’t just about login screens, it’s a foundation for SaaS product success. Strong auth builds trust, supports scale, and enables faster growth. With Appwrite, you can launch secure, scalable, and flexible authentication flows—and customize them to fit your users' exact needs. Ready to simplify your SaaS authentication journey? [Deploy Appwrite](https://cloud.appwrite.io/) and start building your future, today. ### Further reading - [Appwrite Authentication docs](/docs/products/auth) - [Setup magic link authentication](https://youtu.be/mqgNmx9YE5w?si=FsCD88afY4Cu_Bdl) - [Appwrite Authentication quick-start doc](/docs/products/auth/quick-start) --- ## Everything you need to know about RBAC and how to use it in Appwrite https://appwrite.io/blog/post/role-based-access-control-with-appwrite As your application grows, so does the complexity of managing who gets access to what. More users join the system, teams expand, features multiply, and suddenly you're stuck trying to [balance usability with security](/blog/post/guide-to-user-authentication). Everyone needs access to do their job, but not everyone should have access to everything. Without a clear access control model, you're either granting too much freedom or slowing things down with one-off checks and permission patches. That’s where role-based access control (RBAC) comes in. Instead of scattering access logic across the codebase, RBAC lets you define clear roles and assign permissions in a structured, scalable way. It removes ambiguity, reduces risk, and makes it easier to enforce security without blocking legitimate use. In this article, we’ll break down what RBAC actually looks like, why it’s essential for growing systems, and how you can easily implement it using Appwrite. ### What is Role-Based Access Control (RBAC)? Role-Based Access Control (RBAC) is a vital system for regulating user access within an organization. It operates by assigning permissions based on defined roles rather than individuals. This simplifies the management of user rights across systems. In RBAC, three primary components work together: roles, users, and permissions. Roles are defined by the functions individuals perform. They are associated with specific permissions, which dictate access to resources. RBAC implementation involves associating users with roles. Here, users inherit the permissions assigned to their roles, ensuring consistent and efficient access control management. This structure is scalable and adaptable to various organizational needs. Some key features of RBAC include: - **Centralized management**: Managing user access from a central point. - **Role hierarchy**: Allowing roles to be organized in a hierarchy. - **Separation of duties**: Reducing risk by distributing responsibilities. Implementing RBAC is crucial for data protection and maintaining security standards. It aligns with compliance requirements and helps safeguard sensitive data from unauthorized access. By streamlining access control, RBAC reduces administrative overhead and enhances operational efficiency. ### Why RBAC matters for data security and protection Data security and protection are at the heart of modern IT operations. Implementing RBAC plays a crucial role in this domain. RBAC ensures that only authorized users have access to specific data and resources, reducing the risk of data breaches. RBAC is particularly significant for compliance with regulations such as [GDPR](/docs/advanced/security/gdpr) and [HIPAA](/docs/advanced/security/hipaa). These regulations mandate stringent access control measures to safeguard personal and sensitive information. By adopting RBAC, organizations can more easily meet these compliance requirements. One of the key benefits of RBAC is its adherence to the principle of least privilege. This principle ensures users have minimal access to the necessary tasks. This minimizes potential security risks associated with excessive permissions. Here are some reasons RBAC enhances data security: - **Minimizes unauthorized access**: Only assigned roles have access. - **Simplifies user management**: Streamlined handling of user permissions. - **Improves auditability**: Easier to track access and responsibilities. RBAC bolsters security and streamlines processes, making organizations more efficient in managing data protection policies. {% call_to_action title="Add secure role-based permissions with Appwrite" description="Appwrite Auth makes it easy to manage roles, permissions, and user access, all from one place." point1="Define custom roles and fine-grained permissions" point2="Built-in RBAC and team-based access" point3="Works out of the box with Appwrite Auth" point4="Easy to manage from the Console or API" cta="Learn more" url="/products/auth" /%} ### Core components of RBAC: Roles, Permissions, and Users The foundation of Role-Based Access Control (RBAC) is built on three main components: roles, permissions, and users. Each plays a critical part in establishing an effective access control system. **Roles** serve as predefined job functions or titles within an organization. They determine the level of access a user has to system resources. For instance, a "Manager" role might have more privileges than a "Clerk." **Permissions** define the actions users can perform on specific resources. These are tied to roles rather than individual users, simplifying permission management. For example, a role may have permissions like "read," "write," or "delete." **Users** are individuals who are assigned roles based on their job requirements. This assignment process makes it easy to maintain and modify access, as roles change with the organization's needs. In summary, roles map users to the permissions they need, thus streamlining the access control process. This modular approach aids in minimizing administrative overhead while enhancing security. ### RBAC vs. Other access control models (DAC, MAC, ABAC, ACL) **Role-Based Access Control (RBAC)** is just one approach among several access control models. Each model has unique characteristics and applications. **Discretionary Access Control (DAC)** grants access based on identity and allows resource owners to determine permissions. This model is flexible but can be vulnerable if not managed carefully. **Mandatory Access Control (MAC)** uses a centralized authority to decide access, often based on classifications. It's strict and primarily used in environments requiring high security, such as government systems. **Attribute-Based Access Control (ABAC)** relies on dynamic policies using attributes, such as user roles and environmental conditions. This model excels in environments needing fine-grained controls. **Access Control Lists (ACL)** specify individual permissions per resource. While straightforward, they can become complex and unwieldy when scaled. Comparing these models: - **RBAC**: Role-based, efficient management. - **DAC**: Identity-based, flexible but risky. - **MAC**: Strict, highly secure. Exploring more: - **ABAC**: Dynamic, flexible. - **ACL**: Resource-focused, complex to scale. RBAC offers structured simplicity, making it suitable for many organizations. It combines efficiency with robust security, standing out especially in large, dynamic environments. The balance between these access control models depends on specific organizational needs and security requirements. ### Benefits of implementing RBAC Implementing Role-Based Access Control (RBAC) offers multiple advantages. It simplifies managing user permissions across an organization by aligning them with defined roles. By leveraging RBAC, organizations can improve data security and protection. Access is granted based on a user's role, reducing the risk of unauthorized access. RBAC also supports compliance with regulations like GDPR and HIPAA. These regulations require stringent access control measures, which RBAC fulfills effectively. The key benefits of RBAC include: - Enhanced security through structured access management. - Simplified compliance with legal regulations. - Increased operational efficiency. - Reduced risk of data breaches. Moreover, RBAC enhances the onboarding and off-boarding processes. New users receive predefined roles, streamlining access management tasks. ### Introduction to Appwrite and its security features Appwrite is an open-source, all-in-one cloud development platform. Use built-in backend infrastructure and web hosting, all from a single place. You can build your entire backend within minutes and deploy effortlessly, adding Authentication, Databases, Functions, Storage, Messaging, and more to your projects using the frameworks and languages of your choice. One of Appwrite's standout features is its strong focus on security. It provides robust tools essential for safeguarding data. Implementing Role-Based Access Control (RBAC) is seamless with Appwrite, allowing for precise access management. Appwrite's security features include: - Robust [authentication systems.](/products/auth) - Customizable permissions and roles. - Integration with [other security protocols](/docs/products/auth). - [Data encryption](/docs/advanced/security/encryption) for enhanced protection. These features make Appwrite an excellent choice for applications requiring stringent security measures. Additionally, its flexibility allows developers to tailor security features to specific project needs. Appwrite is continuously updated with user feedback. This ensures it remains a reliable solution for modern security challenges while supporting developers to build secure and efficient applications. ### How Appwrite implements RBAC Appwrite effectively incorporates Role-Based Access Control (RBAC) into its system. It allows developers to create granular access controls effortlessly. The implementation begins with setting up user authentication. This ensures that only verified users access resources, forming the foundation for RBAC. Next, Appwrite allows developers to define custom roles. Each role is associated with specific permissions, detailing what actions users can perform. Assigning roles to users follows the role-definition process. This setup restricts access based on user roles, enhancing application data security. Appwrite supports the principle of least privilege, ensuring users receive the minimal required permissions. Key features of Appwrite’s RBAC include: - Customizable roles tailored to business needs. - Fine-grained permissions for precise control. - Easy role assignment to enhance data protection. By employing these elements, Appwrite's RBAC system facilitates robust, flexible, and secure access control. This integration allows developers to tailor security to their specific project requirements while maintaining high standards. ### Setting up RBAC in Appwrite Setting up Role-Based Access Control (RBAC) in Appwrite is pretty straightforward and can be done directly from Appwrite Console by creating different sets of [permissions](/docs/advanced/platform/permissions). [Learn how to use Appwrite Labels and Team to manage user permissions.](/blog/post/manage-user-permissions-with-labels-and-teams) ### Best practices for managing RBAC with Appwrite Effective management of RBAC in Appwrite is vital for maintaining system security. Following best practices helps avoid common pitfalls. First, regularly review and update roles. Ensure they align with current organizational needs and security policies. Consider implementing the principle of least privilege. This limits user access to only what they need to perform their job, enhancing security. ### Recommended practices - **Regular audits**: Conduct frequent audits of roles and permissions. - **Least privilege**: Assign minimal necessary permissions to users. - **Role clarity**: Define clear and concise roles and responsibilities. - **Monitor changes**: Track and log changes to RBAC configurations. Documentation plays a significant role in RBAC management. Keep detailed records of role definitions and changes made over time. This practice aids in troubleshooting and ensures compliance with security standards. Regular monitoring of your RBAC setup in Appwrite ensures optimal access control is maintained. ### Common challenges and how to overcome them Implementing RBAC can present challenges, particularly in complex systems. Identifying and addressing these can enhance system security and efficiency. #### Typical challenges and solutions - **Role explosion**: Too many roles can complicate management. **Solution**: Consolidate roles and refine permissions. - **Over-permissioning**: Users may have excessive access. **Solution**: Regularly audit and adjust permissions. - **User Role drift**: Users might retain roles needlessly. **Solution**: Regularly review and update user roles. Navigating these challenges requires vigilance and periodic reviews. By continuously evaluating and refining RBAC configurations, you can maintain an efficient and secure access control system in Appwrite. {% call_to_action title="Add secure role-based permissions with Appwrite" description="Appwrite Auth makes it easy to manage roles, permissions, and user access, all from one place." point1="Define custom roles and fine-grained permissions" point2="Built-in RBAC and team-based access" point3="Works out of the box with Appwrite Auth" point4="Easy to manage from the Console or API" cta="Learn more" url="/products/auth" /%} ### Real-world use cases: RBAC with Appwrite Role-based access control (RBAC) is versatile and applicable in many fields. Organizations across various sectors rely on RBAC for secure data management. In financial services, RBAC restricts sensitive data access to authorized personnel. Healthcare organizations use RBAC to comply with regulations such as HIPAA and safeguard patient records. #### Examples of RBAC use cases with Appwrite - **E-commerce platforms**: Limit access to order management for sales teams. - **Educational institutions**: Separate permissions for faculty, students, and administrative staff. - **Healthcare systems**: Control access to patient data based on staff roles. Appwrite’s ease of use makes implementing RBAC straightforward, thereby securing data across different industries effectively. ### Frequently asked questions about RBAC and Appwrite Many developers have questions about implementing RBAC in their systems. Here are some frequently asked questions and their answers. #### Common questions - **What is RBAC?** RBAC is a system that limits access based on user roles. - **Why use Appwrite for RBAC?** Appwrite offers a straightforward way to set up and manage RBAC, with secure access control that aligns with standards like HIPAA. - **Can RBAC enhance security?** Yes, it helps in limiting access to sensitive data efficiently. Understanding RBAC with Appwrite can significantly improve data security and ease of access management. ### Conclusion: Enhancing Data Security with RBAC and Appwrite Adopting role-based access control through Appwrite revolutionizes data security. It provides a scalable, flexible framework adaptable to any organization's needs. By implementing RBAC with Appwrite, organizations not only enhance security but also streamline user management. Embracing this approach ensures compliance and elevates protection standards effortlessly. --- ## The shift from SaaS to Vertical AI: What startup founders need to know https://appwrite.io/blog/post/saas-to-vertical-ai B2B enterprise SaaS is about to undergo a major shift. While SaaS revolutionized how software is built and distributed, allowing businesses to streamline operations with cloud-based tools, it had one major problem: it’s still built on a one-size-fits-all approach, with general-purpose solutions that require constant customization and integration. This led to complexity and inefficiency, as businesses needed multiple tools and human resources to make everything work. This is where vertical AI agents come in. These agents are hyper-specialized and built to automate specific business functions, reducing the need for multiple systems and manual intervention. In this blog, we’ll explain what vertical AI agents are, how they differ from SaaS, and the massive opportunity they present to founders. ### What are vertical AI agents? Think of Vertical AI agents as AI systems designed to perform very specific, niche tasks within a business or industry. Unlike general-purpose AI tools, they don’t just perform general tasks like drafting emails, answering questions, or generating text. Vertical AI agents can replace entire human teams and business processes, making operations more efficient and cost-effective. Take, for example, AI-powered QA testing, customer support, or payroll systems. These vertical AI agents are highly specialized to handle the intricacies of these tasks in a way that broad AI models like OpenAI’s GPT can’t. While general-purpose AI can handle a variety of functions, vertical AI agents are designed to dig deep into one area, automating entire workflows and removing the need for human involvement. This makes vertical AI agents more effective and scalable, allowing businesses to reduce both their software and labor costs. They go beyond just being tools. They become the whole system. ### Vertical vs. Horizontal AI agents #### Vertical AI Agents Vertical AI agents specialize in one specific task within a business or industry, automating entire processes. For example, an AI agent for payroll handles everything from calculating wages to filing taxes, all without human intervention. #### Horizontal AI Agents Horizontal AI agents are general-purpose and can be used across various industries and tasks. A general AI like OpenAI’s GPT is an example. It can assist with many tasks, but it isn’t specialized enough to replace a payroll department or fully automate customer service. In short, vertical AI agents are **specialists** who focus on one thing and do it exceptionally well, while horizontal AI agents are **generalists** who serve many functions across industries. ### Best time to start a Vertical AI agent startup In the early 2000s, SaaS companies like Salesforce revolutionized industries by moving software online, making it scalable and accessible. Now, vertical AI agents are taking it a step further. Where SaaS enables better management, Vertical AI automates entire workflows. It’s the difference between having a tool to help and a system that does the job for you. That’s why vertical AI agents will be much bigger than SaaS. #### Here are five reasons why it’s the best time to start a Vertical AI agent startup. ##### More mature Large Language Models (LLMs) With the recent AI advancements, we now have better, faster, and more compact models compared to the past 15-18 months. These advancements provide the necessary foundational technology for creating highly functional vertical AI solutions. ##### A shift in startup strategies The old way of scaling a startup, where you’ll hire thousands of employees, is changing. Now, startups are leveraging AI tools and agents to stay lean and earn more profit with fewer employees. Startups are actively looking to add AI agents to their tech stack. ##### Cost savings Businesses today spend far more on payroll than on software. Vertical AI agents will allow companies to streamline their operations by automating manual, repetitive tasks (like customer support, data entry, or QA testing), reducing costs. ##### Higher customer value and retention By default, vertical AI agents are designed to address the unique needs of specific industries, offering businesses a more tailored and effective solution than general-purpose SaaS. Customers will value these specialized AI agents more because they’re built to solve their unique pain points. ##### Early-mover advantage From healthcare to finance to retail, the application of vertical AI agents is virtually limitless. As vertical AI agents are still relatively new, there’s a massive opportunity for early movers to establish themselves as leaders in their chosen niche. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} ### Conclusion Vertical AI agents are set to revolutionize industries, offering companies greater efficiency and cost savings by automating workflows and replacing software and human labor. With advancements in AI technology, the right market conditions, and a shift in startup strategies, now is the perfect time to jump into this space. For founders looking to take advantage of this trend, Appwrite’s startup program offers the perfect platform to build and scale these AI-powered solutions. It provides you with the tools, resources, and support needed to bring your Vertical AI agent idea to life. [Apply to the program](https://appwrite.io/startups) --- ## The Appwrite Scale plan is now publicly available https://appwrite.io/blog/post/scale-plan-now-available Over a year ago, we launched our pricing structure and introduced the Pro plan. Today, we’re excited to unveil the Scale plan. A new offering that provides more support and flexibility. This plan is specifically tailored for teams within agencies, startups, scale-ups, and SMBs, ensuring they have the tools and resources to drive their business forward. ### The Scale Plan After running an extensive six-month beta, we validated the need for the Scale plan among participants in our Startups program and select Pro users. The feedback reinforced the importance of delivering more robust support and features for scaling teams. With this launch, we’re thrilled to empower larger teams and introduce new opportunities for developers. ### Why the Scale Plan? While the Pro plan is ideal for building scalable applications, we identified that organizations with larger teams often require more tailored solutions. These teams frequently tackle complex, sensitive projects where priority support and greater customization can make a critical difference. The Scale plan is designed to meet these demands, offering the necessary tools and assistance to help agencies, startups, scale-ups, and SMBs successfully manage and grow their operations. In addition to priority support, we’ve integrated advanced features such as enhanced flexibility, compliance options, and extended log retention—ensuring that the Scale plan continues to evolve alongside your needs. ### What’s Included? Starting at $599 per organization per month, the Scale plan includes all features of the Pro plan, plus: - **Priority support** - **Private Slack channel (only business hours/days)** - **Custom organization roles** - **Unlimited members** - **Custom backup policies** - **Compliance features** (BAA, SOC 2, CCPA) - **28-day log retention** - **Activity logs -** coming soon - **Network logs -** coming soon - **SSO** - coming soon You can review the complete overview on our [pricing page.](https://appwrite.io/pricing) ### Looking Ahead We will continue to introduce new features and improvements based on your feedback, ensuring the plan evolves to meet your unique needs. In the future, Scale customers will also receive priority consideration for feature requests and early access to new releases. Your input will directly influence how we shape Appwrite to support your success. We’re excited to see how you’ll use the Scale plan to take your projects to the next level. --- ## Streamline receipt scanning with Appwrite Cloud https://appwrite.io/blog/post/scan-receipts-with-appwrite-functions I downloaded a personal finance app a few weeks ago to help me track my expenses. I uninstalled that app about five minutes later. All it took to lose interest was the giant form I had to fill out to add a receipt. With over [30+ apps installed on a typical user's phone](https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/average-number-of-apps-on-smartphones/) on average, fighting for engagement and retention is tougher than ever. Users are sensitive to friction, with a reported 50% of users [opting for new online merchants just for lowered friction](https://www.pwc.co.uk/industries/documents/frictionless-retail-the-future-of-shopping.pdf) and Shoppify reporting [low-friction payment methods and flows can lead to >50% increase in conversion](https://web-assets.bcg.com/e4/0c/a1acfc2a4e52a029cb37c6fe83ba/bcg-leading-online-shoppers-to-the-finish-line-june-2023.pdf). No one has time to fill out giant forms. To help you and your users avoid filling out giant forms, we're going to build a simple receipt processing endpoint with Appwrite that will extract the total amount from a receipt image using a Hugging Face model. ### Prerequisites Before you start, you'll need to know the basics of working with Appwrite Cloud and [Appwrite Functions](/docs/products/functions). We're going to be using Appwrite Functions to run our receipt processing code, which calls a [Hugging Face model](https://huggingface.co/AdamCodd/donut-receipts-extract) to extract data from a receipt image. Make sure you also have a **Hugging Face API key** to use the model. ### Scanning receipts We're going to use the [Hugging Face Donut Receipts Extract model](https://huggingface.co/AdamCodd/donut-receipts-extract) to extract relevant information from the receipt image. Hugging Face's API makes this pretty simple. We'll send a POST request to the model's endpoint with the image data, and it will return the extracted information. Our script can be as simple as: ```python import requests ### define the URL and image path API_URL = "https://api-inference.huggingface.co/models/AdamCodd/donut-receipts-extract" headers = {"Authorization": "Bearer hf_"} IMAGE = "./test.jpg" def scan_receipt(filename): # read the image data with open(filename, "rb") as f: data = f.read() # Post the image data to the model response = requests.post(API_URL, headers=headers, data=data) return response.json() ``` The response comes back as a set of XML-like tags, we can use Regex to extract the relevant information from the model response. ```python import re def parse_receipt(text): # Use regular expressions to manually extract data between tags data = {} # Extract fields using regular expressions data['store_name'] = re.search(r'(.*?)', text).group(1) data['store_address'] = re.search(r'(.*?)', text).group(1) data['phone'] = re.search(r'(.*?)', text).group(1) data['date'] = re.search(r'(.*?)', text).group(1) data['time'] = re.search(r'(.*?)', text).group(1) data['subtotal'] = re.search(r'(.*?)', text).group(1) data['service_charge'] = re.search(r'(.*?)', text).group(1) data['tax'] = re.search(r'(.*?)', text).group(1) data['total'] = re.search(r'(.*?)', text).group(1) data['tips'] = re.search(r'(.*?)', text).group(1) data['discount'] = re.search(r'(.*?)', text).group(1) return data ``` And putting it all together: ```python ### Call the function and parse the receipt using regex parsed_receipt = parse_receipt( scan_receipt(IMAGE)[0]['generated_text'] ) print(parsed_receipt) ``` Let's also quickly save our dependencies in a `requirements.txt` file by running these commands: ```bash pip install pipreqs pipreqs . ``` The outputted file for me just looks like this: ```txt appwrite==5.0.2 Requests==2.31.0 ``` ### Integrating with Appwrite Functions This script is great for running locally, but we want to run it in the Cloud so we can call it from our app. We also want to make sure we're not exposing our Hugging Face API key in our app and add user based permissions to our endpoint. Let's create a new function in Appwrite Functions to run our script. We'll use the Python starter template, which will automatically create a repo on your account, add boilerplate code, and deploy the function to Appwrite Cloud. ![Scan function in Appwrite](/images/blog/scan-receipts-with-appwrite-functions/scan-function.png) Navigate to the settings tab of the function and add the following environment variables: ![Environment Variables](/images/blog/scan-receipts-with-appwrite-functions/environment-variables.png) The generated starter code will look like this when you clone the repo: ```python from appwrite.client import Client import os ### This is your Appwrite function ### It's executed each time we get a request def main(context): # Why not try the Appwrite SDK? # # Set project and set API key # client = ( # Client() # .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) # .set_key(context.req.headers["x-appwrite-key"]) # ) # You can log messages to the console context.log("Hello, Logs!") # If something goes wrong, log an error context.error("Hello, Errors!") # The `ctx.req` object contains the request data if context.req.method == "GET": # Send a response with the res object helpers # `ctx.res.text()` dispatches a string back to the client return context.res.text("Hello, World!") # `ctx.res.json()` is a handy helper for sending JSON return context.res.json( { "motto": "Build like a team of hundreds_", "learn": "https://appwrite.io/docs", "connect": "https://appwrite.io/discord", "getInspired": "https://builtwith.appwrite.io", } ) ``` We will update the function to accept a file ID from Appwrite Storage, download the file, and run our receipt scanning script on it. First, setup the Appwrite SDK in the function. ```python import os import requests import re from appwrite.client import Client from appwrite.services.storage import Storage def main(context): HF_API_KEY = os.environ['HF_API_KEY'] HF_ENDPOINT = os.environ['HF_ENDPOINT'] BUCKET_ID = os.environ['BUCKET_ID'] headers = {'Authorization': HF_API_KEY} # Set project and set API key # client = new Client() # .set_project(os.environ['APPWRITE_FUNCTION_PROJECT_ID']) # .set_key(context.req.headers['x-appwrite-key']); storage = Storage(client) # ... rest of the code ``` Next, we'll download the file from Appwrite Storage and run our receipt scanning script on it. ```python # ... still in main() file_id = context.req.body["$id"] def scan_receipt(data): response = requests.post(HF_ENDPOINT, headers=headers, data=data) return response.json() def parse_receipt(text): data = {} data['store_name'] = re.search(r'(.*?)', text).group(1) data['store_address'] = re.search(r'(.*?)', text).group(1) data['phone'] = re.search(r'(.*?)', text).group(1) data['date'] = re.search(r'(.*?)', text).group(1) data['time'] = re.search(r'(.*?)', text).group(1) data['subtotal'] = re.search(r'(.*?)', text).group(1) data['service_charge'] = re.search(r'(.*?)', text).group(1) data['tax'] = re.search(r'(.*?)', text).group(1) data['total'] = re.search(r'(.*?)', text).group(1) data['tips'] = re.search(r'(.*?)', text).group(1) data['discount'] = re.search(r'(.*?)', text).group(1) return data image = storage.get_file_download(bucket_id=BUCKET_ID, file_id=file_id) parsed_receipt = parse_receipt( scan_receipt(image)[0]['generated_text'] ) # Return the data we got as a JSON response return context.res.json({ "receipt": parsed_receipt }, 200) ``` ### Make a request Now that our function is set up, we can make a request to it from our app to scan a receipt. Using a CURL request on the function's [domain](/docs/products/functions/domains): ```bash curl -X POST https://66325723b6d05e9f6262.appwrite.global \ -H "Content-Type: application/json" \ -d '{"$id": "66328472002415a37392"}' ``` Which might return something like: ```json { "receipt": { "store_name": " Boutique", "store_address": " 4143 STATE STREET LOS ANGELES, CA", "phone": "213-555-1234", "date": "2022-03-05", "time": "13:05PM", "subtotal": " $183,87", "service_charge": "", "tax": " $22,98", "total": " $206,85", "tips": "", "discount": "" } } ``` ### Next steps There are a few things you can do to integrate this into your app. For example, you could integrate this with a web app by uploading the receipt image to Appwrite Storage and calling the function with the file ID. ```ts import { Storage, Functions, ExecutionMethod } from 'appwrite'; const file = await storage.createFile({ bucketId: '6632844900292d283610', // bucketId fileId: '66328472002415a37392', // fileId file: document.getElementById('uploader').files[0] // file from html input }); const result = await functions.createExecution({ functionId: '66325723000399792782', // functionId body: JSON.stringify({$id: file.$id}), // data async: false, // async (optional) xpath: '/', // xpath (optional) method: ExecutionMethod.POST, // method headers: {"Content-Type": "application/json"} // headers }); ``` Another option is to trigger the function with a webhook when a new file is uploaded to Appwrite Storage. ![Appwrite event triggers for Function](/images/blog/scan-receipts-with-appwrite-functions/events.png) The function will already have the file ID in the request body, you'll just need to store the returned data in a collection instead of returning it as a response. ### Resources Visit our documentation to learn more about Appwrite, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/ai) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Self-hosting Appwrite with Coolify https://appwrite.io/blog/post/self-hosting-appwrite-with-coolify Updated on October 6, 2025 Appwrite was built with self-hosting in mind, making running on your own hardware as easy as possible with minimal hassle. Self-hosting allows users to have complete control over their stored data and provides extensive customization capabilities, enabling users to modify and tailor the source code to perfectly align with their needs. This blog will discuss how to self-host Appwrite on your servers from scratch and some troubleshooting tips you might need. ### What is Coolify? Coolify is an all-in-one Platform as a Service (PaaS) that allows developers to self-host applications, databases, or services, including Appwrite, without the hassle of managing the servers. It is an open-source and self-hostable solution that provides one-click deployment for hosting various services, including Appwrite on your servers. It’s intuitive dashboard serves as a centralised command center, allowing users to effortlessly manage and configure all their services from a single, user-friendly interface. ![Coolify Dashboard](/images/blog/self-hosting-appwrite-with-coolify/coolify-dashboard.png) ### Setting up a VPS A VPS stands for Virtual Private Server, a machine you can rent on the cloud from various cloud providers. Some examples include Elastic Compute Cloud (EC2) from Amazon AWS, Virtual Machines from Azure, Droplets from DigitalOcean and much more. Appwrite also offers one-click deployments without using Coolify on DigitalOcean, AWS Marketplace, etc. If you are interested, you can learn more about them on our [Github repository](https://github.com/appwrite/appwrite#one-click-setups). This blog will focus on droplets from the DigitalOcean, but the same steps should apply to any VPS after you have it set up and running. 1. Go to [DigitalOcean's website](https://www.digitalocean.com/) and create an account or sign into an existing one. 2. After logging in to the control panel, click the **"Create"** menu in the top navigation bar and select **"Droplets"**. 3. You can choose any region and data centre. (Choosing one near your location might improve performance and latency.) 4. ![Create Droplet](/images/blog/self-hosting-appwrite-with-coolify/create-droplet.png) 4. For OS, select **Ubuntu** and select the latest version. 5. Choose a Droplet plan based on your expected workload. 6. Add block storage if you expect to store large files *(optional but recommended for production)*. 7. Enable backups for production environments *(optional but recommended)*. 8. Add your SSH key for secure access: **Adding SSH Key** SSH stands for **Secure Shell**. It is a protocol that allows you to securely access your server without using a password. It is more secure than passwords and easier to manage. **a.** Select an existing key if you've previously added one. ![Add SSH Key](/images/blog/self-hosting-appwrite-with-coolify/add-ssh-key.png) **b.** Click on “New SSH Key” if creating a new one. **c.** Open your system’s command line or terminal, and generate a new SSH key pair using `ssh-keygen -t rsa -b 4096`. ![Generate SSH Key](/images/blog/self-hosting-appwrite-with-coolify/generate-ssh-key.png) **d.** The command will generate two keys, one private and one public. One ending with .pub will be the public one; copy it. **e.** Add the public key to DigitalOcean and give it a name. ![Add SSH Key to DigitalOcean](/images/blog/self-hosting-appwrite-with-coolify/add-ssh-key-to-digitalocean.png) Choose a hostname that helps identify your Appwrite instance (e.g., appwrite-self-hosting), and then click "Create Droplet". It will take a few minutes to provision the droplet. ### Initial server setup Once your Droplet is provisioned, SSH into your server using your private key: ```bash ssh root@your_server_ip ``` Now, update your system packages: ```bash apt update && apt upgrade -y ``` Set up a firewall (UFW) to protect your server: ```bash ufw allow OpenSSH ufw allow 80/tcp ufw allow 443/tcp ufw enable ``` Create a non-root user with sudo privileges *(optional but recommended)*: ```bash adduser appwrite-admin usermod -aG sudo ``` ### Setting up Appwrite on Coolify Now that we have a VPS setup, the rest of the process should be the same regardless of which VPS you rent from any cloud provider. Install Coolify on your server using this command: ```bash curl -sSL | bash ``` Once the installation is complete, open the Coolify dashboard in your web browser. 1. Sign up for a new account. 2. Click the **Create Project** button to start a new project. 3. Select or create an environment type. By default, a **production** environment is already available. 4. Click **Add new resource**. 5. Search for and select **Appwrite** from the list of services. ![Add Appwrite](/images/blog/self-hosting-appwrite-with-coolify/add-appwrite.png) The configuration fields will be pre-filled with the recommended settings, but you can customise them. When ready, click the **Deploy** button to initiate the deployment process. After deployment, access your Appwrite instance's console by clicking the console link in the **Links** section of the Appwrite service. ### Troubleshooting common Issues #### Too many redirects While accessing the console, you might run into the error `ERR_TOO_MANY_REDIRECTS` *(Site redirected you too many times)*. There can be a couple of issues here: 1. Since Coolify uses its configuration of **Traefik**, which is a reverse proxy and load balancer, instead of the official Appwrite one, you will have to turn off the **Strip Prefixes** option in the **Settings** page for the Appwrite console and Appwrite Realtime service. 2. If you are using Cloudflare to manage your deployment's DNS and turning off the above option doesn't help, you must update the SSL/TLS encryption setting to **Full** in the Cloudflare dashboard. From the dashboard, navigate to Your Domain -> SSL/TLS -> Overview and change the setting to Full. #### Container Health Checks Failing ![Container Health Checks Failing](/images/blog/self-hosting-appwrite-with-coolify/container-health-checks-failing.png) Currently, Coolify will mark all the Appwrite containers as unhealthy due to a lack of health checks configured. Still to verify if a container is healthy manually: 1. Check container logs in the Coolify dashboard for specific error messages. 2. Ensure your server meets the minimum resource requirements. 3. Verify that all required ports are open and not blocked by firewalls. 4. Check for Docker storage issues with df -h to ensure you haven't run out of disk space. ### Appwrite Cloud vs Self-hosting with Coolify Appwrite also offers Cloud, a subscription-based all-in-one cloud platform that eliminates the complexities of managing infrastructure and hosting. With Appwrite Cloud, you can build, deploy, and host your entire application, frontend and backend, from a single platform. It provides instant access to Appwrite’s complete suite of services, including authentication, databases, functions, storage, messaging, and hosting, all tightly integrated and managed for you. This means no manual server setup, no configuration headaches, and no maintenance, just a seamless integrated environment where you can go from idea to live app in minutes. Choosing between Appwrite Cloud and self-hosting with Coolify depends on your project's needs. Here’s a side-by-side comparison to help you decide: | Aspect | Appwrite Cloud | Self-Hosting with Coolify | |--------|---------------|--------------------------| | **Ease of setup** | Quick and beginner-friendly. | Requires technical expertise to set up VPS, Coolify, and Appwrite. | | **Scalability** | Automatically adjusts based on demand, ensuring seamless performance. | You must provision additional resources as your app grows. | | **Maintenance** | Appwrite handles updates, scaling, and security patches. | You are responsible for server maintenance, updates, and security patches. | | **Security** | Built-in security features,DDoS protection, encryption, and backups without extra configuration. | Security is fully in your hands, configure firewalls, SSL, and backups manually. | | **Support** | Priority support depending on your Appwrite Cloud plan — faster issue resolution. | Community support or self-managed troubleshooting. | | **Cost** | Predictable, subscription-based pricing, ideal for teams wanting to avoid unexpected infrastructure costs. | Pay for VPS, storage, and bandwidth and other infrastructural requirements. | | **Customization** | Limited customization and depends on available features and settings provided by Appwrite. | Highly customizable since you can modify source code and integrate with other services. | | **Data sovereignty** | Data is stored in Appwrite’s cloud infrastructure, which is fully complaint with GDPR regulations. | Data remains on your own infrastructure. | ### Conclusion Self-hosting Appwrite with Coolify combines the strength of an all-in-one dev platform with the flexibility and autonomy of self-hosting. It enables developers to create powerful applications while retaining data sovereignty and possibly incurring lower operational expenses. By following this guide, you've successfully deployed a production-capable Appwrite instance that you completely own. As your application grows, the union of Appwrite's robust features and Coolify's efficient management will continue to provide a solid foundation for your development requirements. ### Frequently asked questions (FAQs) **1. Can I self-host Appwrite on my own server?** Yes, absolutely! Appwrite was built with self-hosting in mind. You can run it on your own hardware, a VPS, or cloud providers like DigitalOcean, AWS, or Azure. Self-hosting gives you control over your data, security, and setup. You can even modify the source code to fit your exact needs. Tools like **Coolify** make it even easier by handling the deployment for you, so you don’t have to manage containers or complex configurations manually. **2. When should I choose Appwrite Cloud over self-hosting?** If you want to move fast: launch prototypes, MVPs, or production apps without managing servers, Appwrite Cloud is ideal. It’s fully managed, so you can deploy and host your entire stack (frontend + backend). It’s also great for teams that want predictable costs and built-in scalability. The platform automatically handles updates, scaling, and backups, letting you focus on building, not maintaining infrastructure. **3. Can I start with Appwrite Cloud and move to self-hosting later?** Yes, absolutely. That’s one of the biggest advantages of Appwrite being open-source and cloud-compatible. You can start on Appwrite Cloud to build and test your product quickly, and later migrate to a self-hosted setup with Coolify if you want to. The APIs and developer experience stay consistent between both environments, so switching later in the future should not be a huge deal. ### More resources To learn more about self-hosting Appwrite on Coolify, you can check out the following resources: - [Self Hosting Docs - Appwrite](https://appwrite.io/docs/advanced/self-hosting) - [Coolify Docs for Appwrite](https://coolify.io/docs/services/appwrite) - [Self Host Appwrite on Coolify by Self-Host Everything](https://www.youtube.com/watch?v=9plbBKZNuvY) --- ## Serverless functions 101: Best practices https://appwrite.io/blog/post/serverless-functions-best-practices Serverless functions have become a popular choice for modern application development due to their scalability, cost-effectiveness, and ease of use. Appwrite offers an integrated serverless platform that allows you to build and deploy functions seamlessly. However, to make the most of serverless functions, it's essential to follow best practices that ensure efficiency, security, and maintainability. In this guide, we'll explore some dos and don'ts of serverless functions and provide tips for optimizing your functions. ### Define clear objectives Before writing any code, clearly define what you want each function to accomplish. This clarity helps in creating focused, efficient functions. Clear objectives prevent scope creep, making functions easier to test, maintain, and optimize. For example, if you're building a function to process user registrations, define the inputs, outputs, and expected behavior of the function. It might look like this: - **Inputs**: User data (name, email, password) - **Outputs**: Success or error message - **Behavior**: Validate user data, create a new user account, and send a welcome email - **Error handling**: Return appropriate error messages for invalid data or failed operations ### Document your functions Proper documentation is essential for maintaining and scaling serverless functions. Documenting your functions helps other developers understand how they work and how to interact with them. It's best practice to document your functions in your project's README. A well-documented function includes the following details: #### Endpoints For each endpoint, provide: - **Description**: What the endpoint does - **Method**: HTTP method used (GET, POST, etc.) - **Path**: The URL path - **Params**: Parameters required or optional - **Headers**: Any required or optional headers - **Success response example**: A sample of a successful response - **Failed response example**: A sample of a failed response - **Response structure**: Any variations in the structure of the response body #### Environment variables List all environment variables that need to be set to ensure the function runs correctly. This includes any API keys, database URLs, or other configurations. For each variable, include the following details: - **Variable name**: The name of the environment variable. - **Description**: A brief explanation of the variable's purpose and how it affects the function. - **Example value**: An example of a typical value that the variable might take. - **Required**: Indicate whether the variable is mandatory or optional. - **Link to documentation**: Provide a link to any relevant documentation. #### Entrypoint and build command Specify the entry point (main file) for the function and the command needed to build it. This will help you understand how to run and deploy the function. #### Deployment configuration Detail the configuration needed for deployment on your chosen platform. For example, if using Appwrite, include: - **Timeout**: The function's timeout setting. - **Scopes**: Permissions required by the function. - **Execute permissions**: Who or what can execute the function. - **Cron**: If the function is scheduled, provide the cron expression. - **Events**: Events that trigger the function. #### Development setup Explain how to run the function in a development environment. For Appwrite, this would include using the command `appwrite run function`. Proper documentation ensures that other developers can understand and work with your code efficiently. ### Keep functions small and focused Adhering to the single responsibility principle means each function should perform one task. This approach simplifies testing, debugging, and scaling. Smaller functions are easier to manage, and they can be independently updated or replaced without affecting the entire application. So, instead of having a single function that handles user registration, payment processing, and email notifications, it's better to break them down into separate functions. However, there are exceptions. If your function acts as an API server, you might want to consolidate multiple services into a single API function. This approach can minimize cold-start times, as the function remains warm longer due to frequent invocations. In such cases, balancing the need for focused functions with the benefits of reduced latency is key. ### Choose appropriate specifications for your functions Configuring the right specifications ensures that your functions have the necessary resources for optimal performance. Proper allocation balances performance and cost, helping to avoid unnecessary expenses while maintaining efficiency. For example, if your function handles image processing, it may require more resources than a function that processes text data. In such cases, allocating more CPU and RAM can enhance performance. In Appwrite, you can configure your function specification by navigating to your function settings and scrolling to “**Runtime**”: ![Functions-specifications](/images/blog/serverless-functions/1.png) This will allow you to choose the CPU and memory allocation for your function. It's important to choose the right settings based on your function's requirements to ensure optimal performance. ### Minimize cold starts Cold starts occur when a function is invoked after being idle, causing a delay as the runtime environment initializes. This can impact the user experience by increasing latency. Minimizing cold starts ensures a smoother, more responsive application. Some languages are faster at cold starts than others. Choosing the right language for your serverless functions is important, especially for user-facing functions. Compiled languages like Go or Dart typically have faster start times compared to interpreted languages. However, for interpreted languages like Node.js, Python, or PHP, you could use a build tool like ESBuild to bundle all your code into a single file. This reduces the number of files that need to be loaded and extracted during the cold start, speeding up the process. You could also consider using warm-up strategies to keep functions warm and ready to respond quickly, like scheduling periodic pings to your functions. Although this may incur additional costs and could be a form of abuse on free-tier plans. So, it's essential to weigh the trade-offs and choose the best approach for your use case. Additionally, you can optimize warm starts by reusing resources between executions. For example, you can maintain a pool of database connections at a global level to avoid repeated handshakes on each request. Also, implementing caching for database queries that don't require the latest data on every invocation can help reduce latency. However, be cautious with caching as it can lead to stale data if not managed properly. ### Use environment variables Avoid hardcoding configuration values like API keys or database URLs. Instead, use environment variables to manage these configurations securely. Hardcoding sensitive information poses a security risk and reduces flexibility. Environment variables keep sensitive data out of your codebase and make it easy to change configurations without modifying code. In Appwrite, you can set environment variables for your functions in the Cloud Console. Navigate to your function settings and scroll down to the "Environment Variables" section. Here, you can add key-value pairs for your configurations. It should look like this: ![Environment-variables](/images/blog/serverless-functions/2.png) After setting environment variables, you can access them in your function code like this (for Node.js): ```jsx const apiKey = process.env.NAME_OF_YOUR_ENV_VARIABLE ``` Depending on your runtime and programming language, the above code might look different. ### Ensure API keys are secure Always set minimal scopes to the API keys used in your functions to maintain security. This limits access to only the necessary resources. Additionally, set an expiration date for your API keys and regularly rotate them to enhance security further. Depending on your application, you might want to rotate the keys more or less frequently, but a general recommendation is every 1-3 months. ### Manage dependencies effectively Keep your function dependencies lean by including only necessary libraries. This will reduce deployment size, improve performance, and simplify maintenance. Large deployment packages slow down the function initialization process and increase resource consumption. If a library is not essential or you only need a small part of it, consider searching for a more lightweight alternative or writing custom code. ### Implement authentication and authorization Ensure only authorized users can execute your functions. Appwrite provides built-in authentication and authorization features that you can leverage to secure your functions. This is important for protecting sensitive data and preventing your functions from being misused or easily exploited. You must ensure that authorization is enforced on your serverless functions and not solely on the client side. Client-side authorization can be bypassed, leading to security vulnerabilities. Appwrite Databases and Storage services can be configured to enforce access control rules. For example, you can restrict read and write access to documents by navigating to your database collection settings in the Appwrite Console and setting the appropriate permissions. It looks like this: ![Functions-authorization](/images/blog/serverless-functions/3.png) You can also configure **execute access** for your Appwrite functions, allowing you to specify who can execute the function using the client API. ![Functions-access](/images/blog/serverless-functions/4.png) ### Encrypt sensitive data Always encrypt sensitive data, both in transit and at rest, to protect it from unauthorized access. Encryption ensures that even if data is intercepted or accessed by unauthorized users, it cannot be read or tampered with. For example, if your function interacts with a database, ensure that the database connection is encrypted using SSL/TLS. Additionally, encrypt sensitive data before storing it in the database. Appwrite already does a great job of applying encryption in authentication, enforcing HTTPS connections, and generating TLS certificates for domains. But in addition to that, Appwrite also use encryption for storage. However, you should ensure that there are no loose ends or security loopholes in your function code that could expose sensitive data. In addition to encryption, consider allowing access only from specific IP ranges to enhance security. Note that encryption can impact query performance. So, you should carefully plan your encryption strategy to balance security and performance, possibly using selective encryption for highly sensitive data while ensuring efficient access to less sensitive information. ### Write unit and integration tests Unit tests validate individual parts of your function, while integration tests ensure that different parts of your system work together correctly. Testing catches bugs early in the development process, ensuring that your functions work as intended and reducing the likelihood of issues in production. Here's what a unit test for a function that calculates the sum of two numbers might look like: ```jsx const sum = require('./sum') test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3) }) ``` For integration tests, you can use tools like Jest, Mocha, or Cypress to test the interaction between different components of your application. You can also include your test command as part of your build command, so your deployments run tests automatically. If you need a more complex test pipeline, you can use GitHub Actions or similar tools to manage and run your tests. ### Use logging and monitoring tools Logging helps you track the execution of your functions, identify issues, and monitor performance. By logging key events and data, you can gain insights into how your functions are behaving and quickly troubleshoot problems. Appwrite provides built-in logging and monitoring tools that you can use to track function execution, view logs, and monitor resource usage. This helps you gain visibility into your functions' performance and ensure they are running smoothly. When using Appwrite Functions, you can keep your log statements within the source code and disable logging from the function settings. This allows you to re-enable logging with a single click in the future if you need to debug or monitor specific requests. ![Functions-logging](/images/blog/serverless-functions/5.png) ### Automate deployment process Automate your deployment process with CI/CD pipelines. This ensures consistent deployments and helps catch issues early in the development cycle. CI/CD pipelines streamline the deployment process, reduce the risk of human error, and ensure that code changes are thoroughly tested before reaching production. Appwrite has built-in support for linking your functions to a Git repository and deploying them automatically when changes are pushed. However, you can also use external CI/CD tools like GitHub Actions or Jenkins to manage your deployment pipeline, which can be integrated with Appwrite using the [Appwrite CLI](https://appwrite.io/docs/tooling/command-line/installation). ### Version your functions Versioning your functions helps you manage updates and rollbacks effectively, especially when making breaking changes. By assigning version numbers to your functions, you can track changes, maintain backward compatibility, and ensure a smooth deployment process. ### Use cost-effective architectures Design your serverless architecture to minimize costs. Use event-driven models and offload heavy processing to external services to optimize resource usage and reduce expenses. For example, instead of processing tasks like image recognition within your functions, you can use specialized services like AWS Rekognition or Google Cloud Vision to handle these tasks more efficiently. Another tip is to use event triggers to invoke functions only when necessary. This ensures that resources are used efficiently and costs are minimized. Appwrite provides built-in support for event triggers, allowing you to trigger functions based on events like database changes, file uploads, or HTTP requests. Here's a more detailed guide on [Appwrite Events](https://appwrite.io/docs/advanced/platform/events). ### Avoid long-running functions Serverless platforms have execution time limits. Avoid long-running tasks to prevent timeouts and increased costs. When a function runs for an extended period, it consumes resources and can impact the performance of other executions. So, design your functions to be short-lived and efficient. And when you notice that a function is taking too long to execute, consider breaking it down into smaller tasks. ### Frequently asked questions (FAQs) **1. What are serverless functions?** Serverless functions let you run small pieces of code in the cloud without managing servers. You just define a function, deploy it, and the platform (like Appwrite, AWS Lambda, or Vercel) automatically scales it up or down based on usage. You only pay for the time your code runs, no idle costs, no manual infrastructure setup. **2. How do I decide what logic should go into a serverless function?** Keep functions small, focused, and event-driven. A good rule of thumb: one function should do one thing well, like sending an email, processing an image, or verifying a payment. Avoid cramming multiple workflows into a single function; it makes testing, scaling, and debugging much easier. **3. How can I reduce the cost of running serverless functions?** Design your architecture to be event-driven and avoid functions running longer than needed. Split heavy or continuous tasks (like file processing or large data syncs) into smaller chunks or move them to specialized services. Also, clean up unused triggers and logs, they can silently add to costs over time. **4. How do I secure my serverless functions?** Enforce authentication and authorization at the function level. Use minimal API scopes and regularly rotate keys. Never rely solely on client-side validation. Always validate tokens or permissions in the function itself. In Appwrite, you can configure execute permissions to restrict who can run each function. **5. How do I manage versions of serverless functions?** Versioning helps you roll back safely if something breaks. Assign version numbers when making breaking changes and maintain backward-compatible routes for existing clients. Appwrite lets you manage multiple function versions directly from the console. ### Conclusion By following these best practices, you can build efficient, secure, and scalable serverless functions. This enables you to fully leverage the advantages of Appwrite Functions, resulting in robust and scalable solutions tailored to modern development needs. Resources: - [Appwrite functions documentation](https://appwrite.io/docs/functions) - [Appwrite events documentation](https://appwrite.io/docs/advanced/platform/events) - [Appwrite environment variables documentation](https://appwrite.io/docs/environment-variables) --- ## How to set up Google authentication in React with Appwrite https://appwrite.io/blog/post/set-up-google-auth-appwrite-react Updated on October 6, 2025 In this article, we'll explore how to set up Google authentication in a React application using Appwrite. Appwrite is an open-source, all-in-one cloud development platform. Use built-in backend infrastructure and web hosting, all from a single place. You can build your entire backend within minutes and deploy effortlessly, adding Authentication, Databases, Functions, Storage, Messaging, and more to your projects using the frameworks and languages of your choice. While this tutorial focuses on Google OAuth2, Appwrite supports many [authentication methods](/docs/products/auth), including [passwordless authentication](/blog/post/improve-ux-passwordless-auth), magic URL, email OTP, phone number OTP, email/password and various OAuth2 providers. By the end of this tutorial, you'll have a React app that allows users to log in with their Google account seamlessly. Let's get started! ### Prerequisites Before diving in, ensure you have the following: - Basic knowledge of React - An Appwrite project set up - Learn how to create an Appwrite project [here](/docs/tutorials/react/step-3) - Access to a Google Cloud account - Node.js and npm installed on your machine ### Setting up Google OAuth2 in Appwrite First, we need to configure Appwrite to support Google OAuth2. Log in to your Appwrite console and navigate to the **Auth** section on your left sidebar. ![Appwrite auth page](/images/blog/set-up-google-auth-appwrite-react/auth-page.png) Click the settings tab and scroll down to the **OAuth2 Providers** section. The OAuth2 providers are arranged alphabetically, so you'll find Google OAuth2 when you scroll to 'G'. Click it, and a popup will appear with a toggle button and a form to enter your Google OAuth2 credentials. ![Appwrite Google OAuth2 settings](/images/blog/set-up-google-auth-appwrite-react/appwrite-google-oauth.png) At the bottom of the form, you'll see a "URI" field. Click the copy button beside the URI to save it for later use. This URI is required to set up your Google OAuth client. Appwrite simplifies OAuth2 integration by supporting multiple providers out of the box, such as Facebook, GitHub, and Twitter. This flexibility makes it easy to extend your authentication methods as your application grows. ### Creating OAuth2 client ID in Google cloud Let's set up an OAuth2 Client ID in the Google Cloud Console to allow our Appwrite project to communicate with Google for authentication. Log in to the [Google Cloud Console](https://console.cloud.google.com/). From the projects list, select a project or create a new one. If the APIs & Services page isn't already open, open the console's left-side menu and select APIs & Services. On the left, click Credentials, then “Create credentials,” and select OAuth client ID. ![Google cloud console view](/images/blog/set-up-google-auth-appwrite-react/google-cloud-view.png) ![Google cloud create credentials](/images/blog/set-up-google-auth-appwrite-react/google-cloud-create-credentials.png) If this is your first time creating a client ID, you need to set up your consent screen. Go to the Google API Console OAuth consent screen page and add the required information like your user type, product name, and support email address. Click Add Scope, and select the scopes your project uses on the dialog that appears. Sensitive scopes display a lock icon next to the API name. When you're finished adding details to the OAuth consent screen, click Save and Continue. For the purposes of this tutorial, you don't need to submit your app for verification immediately. Google Auth will work for your dev environment even if you skip the verification steps, which include submitting a video demonstration of your app. Select "Web application" as the application type and add the redirect URI provided by Appwrite. Click Create and save your credentials. Note down the Client ID and Client Secret. You'll need these credentials back in the Appwrite console. ![Google cloud OAuth client ID](/images/blog/set-up-google-auth-appwrite-react/google-credentials-id.png) ### Configuring Appwrite with Google OAuth2 credentials With your Google Cloud credentials ready, let's complete the setup in the Appwrite console. Return to your Appwrite console and navigate to the Auth section where you enabled Google OAuth2. In the popup, you'll see two fields for "App ID" and "App Secret." Enter your Google OAuth Client ID into the "App ID" field and your Google OAuth Client Secret into the "App Secret" field. Finally, toggle the "Disabled" button to enable Google OAuth2 authentication and save your changes. ![Appwrite Google OAuth2 credentials](/images/blog/set-up-google-auth-appwrite-react/appwrite-google-oauth-2.png) You now have Google OAuth2 set up in your Appwrite console! With Appwrite configured and your Google Cloud credentials integrated, let's move on to setting up your React project. ### Setting up your React project To start, open your terminal and run the following command to create a new React application: ```bash npx create-react-app appwrite-google-auth ``` Feel free to use any other method you prefer to create your React application, like [Vite](https://vitejs.dev/) for example. Navigate into your project directory and install the default dependencies: ```bash cd appwrite-google-auth && npm install ``` Next, install the Appwrite SDK: ```bash npm install appwrite ``` Create a new file named `appwrite.js` in the `src` directory. Here, we'll initialize the Appwrite client and account service: ```js // src/appwrite.js import { Client, Account, OAuthProvider } from 'appwrite' const client = new Client() client .setEndpoint('https://.cloud.appwrite.io/v1')// The Appwrite API endpoint .setProject('project-id')// Your Appwrite project IDexport const account = new Account(client) export { OAuthProvider } ``` In the `appwrite.js` file, the `Client` object, used to interact with Appwrite services, is initialized with the Appwrite API endpoint and your project ID. The `account` object is created using the client to interact with the Appwrite account service, while the `OAuthProvider` is an enum that contains the OAuth2 providers supported by Appwrite. We are exporting it from this file so you don't have to import both from `appwrite` and `appwrite.js` when you need to use both account and OAuthProvider in a component. ### Create authentication functions With your React project and Appwrite client set up, you can now create functions to handle login and logout. Create a new file named `auth.js` in the `src` directory. Add the following functions to handle login and logout: ```jsx // src/auth.js import { account, OAuthProvider } from './appwrite' export const loginWithGoogle = async () => { try { await account.createOAuth2Session({ provider: OAuthProvider.Google }) } catch (error) { console.error(error) } } export const logoutUser = async () => { try { await account.deleteSession('current') } catch (error) { console.error(error) } } export const getUser = async () => { try { return await account.get() } catch (error) { console.error(error) } } ``` In the `auth.js` file, we've created three functions: `loginWithGoogle`, `logoutUser` and `getUser`. - The `loginWithGoogle` function uses the `createOAuth2Session` method to initiate a Google OAuth2 session. The `OAuthProvider` object is imported from the `./appwrite.js` file, which contains the Google OAuth2 provider. - The `logoutUser` function uses the `deleteSession` method to log out the current user. The `deleteSession` method expects the session ID to be deleted as an argument. In this case, we're passing `'current'` to delete the current session. - The `getUser` function uses the `get` method to retrieve the current user's account details. This function will be used to display the user's information after they log in or to check if the user is already logged in. ### Set up component for authentication Next, create a new component named `Auth.js` (this time, with a capital 'A') in the `src` directory. This component will handle the login and logout functionality using the functions we created earlier. ```jsx // src/Auth.js import React, { useState, useEffect } from 'react' import { loginWithGoogle, logoutUser, getUser } from './auth' const Auth = () => { const [user, setUser] = useState(null) useEffect(() => { const checkUser = async () => { try { const userData = await getUser() setUser(userData) } catch (error) { setUser(null) } } checkUser() }, []) return (
    {user ? ( <>

    Welcome, {user.name}!

    ) : ( )}
    ) } export default Auth ``` In the `Auth.js` file, we've created a functional component named `Auth`. This component uses the `useState` and `useEffect` hooks to manage the user's authentication state. The `checkUser` function is called when the component mounts to check if the user is already logged in. If the user is logged in, the component displays a welcome message with the user's name and a logout button, which triggers the `logoutUser` function when clicked. If the user is not logged in, the component displays a login button that triggers the `loginWithGoogle` function when clicked. You've now set up the basic structure for Google authentication using Appwrite in your React application. Next, we will integrate and test our authentication flow in the React app. ### Integrate and test Google auth in your React app With your authentication component set up, it's time to integrate it into your main application and test the Google OAuth2 authentication flow. #### Step 1: Add the `Auth` Component to Your Main Application Open the `src/App.js` file and add the `Auth` component: ```jsx // src/App.js import React from 'react' import Auth from './Auth' const App = () => { return (

    Appwrite Google Auth Example

    ) } export default App ``` This integration will display the `Auth` component within your main application, providing Google authentication functionality to your users. #### Step 2: Run your React application To see everything in action, start your React development server by running the following command in your terminal: ```bash npm start ``` Open your browser and navigate to `http://localhost:3000`. You should see your application with the login button. #### Step 3: Test the authentication flow ##### Testing login 1. Click on the "Login with Google" button. 2. You will be redirected to the Google login page. 3. After logging in with your Google account, you should be redirected back to your application. 4. If successful, you should see a welcome message with your Google account name and a "Logout" button. ##### Testing logout 1. Click on the "Logout" button. 2. The welcome message should no longer be there, and the "Login with Google" button should show. #### Step 4: Verify user authentication Every request made through the Appwrite SDK after the user is logged in will include their authentication details automatically. This ensures that actions like reading from or writing to the database will be authenticated without requiring additional credentials. ### Conclusion You've successfully integrated Google OAuth2 authentication into your React application using Appwrite. With this setup, users can log in with their Google accounts, and you can securely manage their authentication state and data access within your application. Appwrite supports many [OAuth2](/docs/products/auth/oauth2) providers, including GitHub, Microsoft, Apple, Facebook, and more. This flexibility allows you to easily integrate various authentication options based on your application's needs. Additionally, Appwrite offers a variety of other authentication flows, such as Magic URL, phone authentication, anonymous sessions, team invites, and more. You can explore these options and learn how to implement them in your applications by checking out the [Appwrite Auth Documentation](/docs/products/auth). Feel free to explore further and build on this foundation to create even more sophisticated and secure applications. If you need any help, don't hesitate to reach out to the Appwrite community on [Discord](https://discord.com/invite/appwrite) for support and guidance. ### Frequently asked questions (FAQs) **1. What exactly is OAuth2, and why do developers use it?** Think of OAuth2 as a way to let users log in with accounts they already trust, like Google or GitHub, without you ever handling their passwords. Instead of creating a new account, they just say, “Hey Google, tell this app who I am.” It makes signing up easier for users and keeps you, the developer, out of the business of storing sensitive credentials. It’s basically convenience + security in one. **2. How does Appwrite make OAuth2 easier?** Implementing OAuth2 manually can get complicated. You need to manage redirects, handle access tokens, and ensure secure communication with the provider. Appwrite takes care of all of this for you. When a user clicks “Login with Google,” Appwrite handles the entire OAuth2 flow: it connects with Google, verifies the user, and returns a valid session to your app. You don’t need to handle tokens or complex authentication steps yourself. This lets you focus on building your application while Appwrite manages the security and authentication behind the scenes. **3. Why should I use OAuth2 instead of the classic email and password login?** With email and password, you’re on the hook for storing, encrypting, and securing passwords. That’s extra work and responsibility. OAuth2 takes all that off your plate. The user logs in through Google (or another provider), and you just get a verified identity. It’s faster for users and way less risky for you. Plus, people are more likely to trust “Sign in with Google” than creating yet another password. **4. Is OAuth2 safe to use in production apps?** Absolutely. In fact, OAuth2 is one of the most widely used and tested authentication standards out there. Companies like Google, Apple, and Microsoft all rely on it. As long as you use secure redirect URIs and handle tokens through a trusted service like Appwrite, you’re in great shape. The heavy lifting—like token security and validation—is handled by Appwrite and the provider. **5. Can I mix OAuth2 with other login methods in my app?** Definitely! That’s one of the best parts of using Appwrite. You can let users choose—some might prefer logging in with Google, others might want the traditional email/password route or even phone OTPs. Appwrite supports all of that out of the box. You just toggle on the methods you want, and users can pick whatever works best for them. ### More resources - [OAuth 2 login documentation](/docs/products/auth/oauth2) - [Appwrite Auth documentation](/docs/products/auth) - [Building custom authentication flows with Appwrite](/blog/post/building-custom-auth-flows) --- ## Setting up “Sign in with Google” in your app https://appwrite.io/blog/post/setting-up-google-signin This article will teach you how to configure **Sign in with Google** in your applications. We will focus on two key parts: 1. Set up and configure an app in your Google Cloud Console to complete the following 1. OAuth consent screen 2. OAuth client ID and secret 2. Client-side configuration If you prefer guides in a video format, we already have a [video](https://youtu.be/tgO_ADSvY1I) you can watch and follow along while integrating. ### Configuring the project on Google Cloud Console #### Setting up a project To use Google as an OAuth provider, you need to set the following things up in your Google Developer Console: - **OAuth consent screen**: This is displayed to users when they try to sign in using Google. - **OAuth client ID and secret**: These are used to identify your Google Cloud project through your app. Head to [Google Cloud Console](https://console.cloud.google.com) and sign in with your Google Account. After signing in, depending on whether you have already used Google Cloud Console before, you may be asked to create a new project. #### Creating an OAuth Consent Screen After creating your project, you will be redirected to your project dashboard, which should look similar to the following image. ![Google developer console project dashboard](/images/blog/setting-up-google-signin/google-console.png) - Click on **APIs & Services** in the quick access section and then on **OAuth Consent Screen** in the sidebar. If you're doing this for the first time, you should see a page that mentions the Google Auth Platform is not set up. Click on **Get Started**, which should open a wizard that guides you to creating your OAuth consent screen. - When creating an OAuth consent screen, you will be presented with a wizard that asks a series of questions, starting with your app name and user support email address. Google will share this email with your users for support-related queries. Hit **Next** once you’re ready to proceed to the next step. ![Setting up audience for OAuth consent screen](/images/blog/setting-up-google-signin/audience.png) - In the **Audience** section, as shown in the above image, you must mention who will use your application. - If your application is meant to be used by a closed group of people, select **Internal**. - If you plan on submitting your app to app stores or releasing it for public use (i.e., anyone not in your organization), select **External**. This is important because if you choose external, you might need to submit your app for review once you reach a certain number of users, whereas if you choose internal, no verification is required. For this tutorial, we will choose external. Once the setup is complete, you can also edit the branding of your consent screen by clicking on the **Branding** option on the sidebar. #### Selecting OAuth scopes When working with Google OAuth, there are details about the users to whom your app can request access for example email address, profile picture, etc. OAuth scopes determine specifically what you can access and the next step would be to configure these. Click on **Data Access** on the sidebar and click on **Add or remove scopes**. A menu should now appear displaying all the available scopes you can enable. ![Setting up scopes for OAuth consent screen](/images/blog/setting-up-google-signin/scopes.png) For standard usage, you can use `.../auth/userinfo.email`, `.../auth/userinfo.profile` and `openid`. These scopes allow you to get the very basic information about the user. However, if you want to perform sensitive actions on behalf of your users, you might need to enable sensitive scopes that require immediate review of your application by Google. #### Creating an OAuth client ID Now that your consent screen is set up, we can create an OAuth Client ID. An OAuth Client ID is used in your application so that Google can identify your project and provide authentication services to your users. To create an OAuth Client ID, click on **Clients** on the sidebar and then click on **Create Client** on the top bar. This should open another wizard to help you configure your OAuth Client ID. ![Setting up OAuth client ID](/images/blog/setting-up-google-signin/client.png) Let’s dive into the fields that you need to enter here: - **Application type:** the platform you wish to use for your application. In this case, we will select “Web application”. - **Name:** the name you desire to assign to your OAuth Client ID. This could be anything and does not impact further steps in the tutorial. - **Authorized JavaScript Origins:** if you chose a web application as your application type, you need to provide your website URL. This should be the front end of your application. - **Authorized Redirect URIs:** the URLs Google can trust to redirect the users back to. This will depend on how your app and the back end are configured. We will cover this in the next step. When you have filled all the required fields, click on **Create**. This should process your request and create your OAuth Client ID. Now, go ahead and open the entry for your client and you should see a screen similar to the following. ![OAuth client ID details](/images/blog/setting-up-google-signin/client-details.png) Note the **Client ID** and **Client secret;** we will need them later in this guide. The client ID could be public, but make sure you don’t share your client secret with others. ### Implementing Google authentication in your apps Once you have created your Google OAuth credentials, you can integrate them into your application and enable your users to authenticate using Google. Client-side integration will be different for every provider and package you use. In our case, we will use Appwrite as our auth provider, which will handle everything related to our users. #### Setting up Google authentication in the Appwrite console - Go to [Appwrite Cloud](https://cloud.appwrite.io) and log in using your preferred authentication method. Once you’re logged in, click on **Create Project**. - Give the project a name, select a desired location (which should be closer to most of your users), and click **Create**. You should now be presented with your project dashboard. ![Appwrite Cloud Console](/images/blog/setting-up-google-signin/appwrite-console.png) - Now, click on **Auth** on the sidebar, and click on the **Settings** tab. Here you will see many OAuth providers you can configure for authentication in your application. Click on **Google,** and you will find options to fill out in Appwrite and the Google Cloud Console. ![Setting up Google authentication in Appwrite console](/images/blog/setting-up-google-signin/appwrite-google-oauth.png) - Enter the client ID and secret you obtained from the Google Cloud Console. Also, copy the URI and add it to the redirect URIs section of your Google client on Google Cloud Console. Once that's done, click on **Update**. This should enable Google to be an authentication provider in your Appwrite app. #### Initiating Google authentication flow Now, you can simply initialize an [Appwrite client](/docs/quick-starts/web) and call the following method to trigger Google sign-in using the Appwrite Web SDK. ```jsx import { Client, Account, OAuthProvider } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject(''); // Your project ID const account = new Account(client); // Go to OAuth provider login page account.createOAuth2Session( OAuthProvider.Google, // provider 'https://example.com/success', // redirect here on success 'https://example.com/failed', // redirect here on failure ); ``` The `createOAuth2Session()` method from the `account` object allows you to initiate an OAuth authentication session. By running this function, the user will be redirected to Google's authentication servers to authenticate and will be redirected back to your app, depending on how you've configured your success and failure redirect URLs. If you're facing problems where cookies are not being set after authentication, you should use `createOAuth2Token()` method instead. We have a [troubleshooting guide](/blog/post/fixing-oauth2-issues-in-appwrite-cloud) to help you transition to that flow, which should not be a huge change. You can use the following method to get more information about the current logged-in session. ```jsx const session = await account.getSession('current'); ``` The `getSession()` method will provide you with all the information about the session of the current logged-in user. ### Conclusion Hopefully, this guide helped you integrate Google authentication functionality in your apps. If you have any questions, feel free to join our [Discord server](https://appwrite.io/discord), and we would be happy to help! Here are some related resources. - [Understanding OAuth and OpenID Connect](/blog/post/oauth-openid) - [OAuth 2 login documentation](/docs/products/auth/oauth2) --- ## Setting up route protection in React Native https://appwrite.io/blog/post/setting-up-route-protection-in-react-native In this guide, we'll walk through implementing protected routes in React Native using a simplified authentication approach with hardcoded values. The implementation uses a straightforward application structure that's easy to follow along with. While you can start from scratch, we recommend having a basic React Native project already set up. If you need help with the initial setup, refer to our React Native Appwrite SDK Tutorial below: {% youtube src="https://www.youtube-nocookie.com/embed/YSUmzHH_OMg?si=EWm7gnPUASMKygrr" thumbnail="/images/blog/setting-up-route-protection-in-react-native/thumbnail-1.png" /%} ### Prerequisites Before we begin, you should have: - A React Native project with Appwrite integration - Follow our [quick start guide](/docs/quick-starts/react-native) to set it up - Basic understanding of React Native and navigation concepts ### Step 1: Setting up the file structure First, we'll separate our routes into two categories: protected (which requires authentication) and public. We'll create this separation by wrapping our protected routes in a folder called (app): ``` src ├── app │ ├── _layout.tsx // Root layout │ └── signin.tsx // Public route │ └── (app) // Protected routes group ├── _layout.tsx // Protected layout └── index.tsx // Protected home screen ``` To protect the routes in our protected route group, we'll add an auth check within the protected route group's `_layout.tsx` file. For now, we'll use a hardcoded session value to keep things simple: ```typeScript // app/(app)/_layout.tsx import { Slot, Redirect} from "expo-router"; export default function AppLayout() { const session = false return !session ? : } ``` This is as simple as it gets - we're just blocking users from accessing any route that uses this layout if there's no session. However, in a real scenario, we want to pull the user state from some sort of provider, which we'll set up next. ### Step 2: Creating our auth context Let's create our auth context that we'll use throughout the app: ```typescript // app/context/AuthContext.js import { useContext, createContext, useState, useEffect } from 'react'; import { Text, SafeAreaView } from 'react-native'; const AuthContext = createContext() const AuthProvider = ({children}) => { const [user, setUser] = useState(false) const [session, setSession] = useState(false) const [loading, setLoading] = useState(true) const signIn = async () => {} const signOut = async () => {} const contextData = { user, session, signIn, signOut } return ( {loading ? Loading... : children} ) } export {AuthContext, AuthProvider} const useAuth = () => {return useContext(AuthContext)} export {useAuth} ``` ### Step 3: Setting up the root layout To use our auth context, we'll wrap the `root` layout with the `AuthProvider`: ```typescript import { Slot } from 'expo-router'; import { AuthProvider } from './context/AuthContext.js'; export default function Root() { return ( ); } ``` ### Step 4: Implementing protected routes Now let's create our protected layout that will guard our private routes: ```typescript // app/(app)/_layout.tsx import { Slot, Redirect} from "expo-router"; import {useAuth} from '../context/AuthContext.js' export default function AppLayout() { const {user, session} = useAuth() return !session ? : } ``` ### Step 5: Setting up the sign-in page Finally, let's handle the case where an authenticated user tries to visit the sign-in page: ```typeScript // app/signin.tsx import { Redirect } from 'expo-router' import { useAuth } from '@/context/AuthContext' import { SafeAreaView, Text } from 'react-native' const signin = () => { const {session} = useAuth() if (session) return return ( signin ) } export default signin ``` This completes our implementation of protected routes in React Native. You now have a foundation for managing authenticated and public routes using React Context to handle the global session state. Now that you have a basic route protection system in place, you can enhance it by implementing actual authentication logic using Appwrite. If you run into any issues or have questions, the Appwrite community on [Discord](https://discord.com/invite/appwrite) is always ready to help. ### More resources If you would like to learn more about React Native and Appwrite, we have some resources that you should visit: - [Getting started with React Native and Appwrite](https://appwrite.io/docs/quick-starts/react-native) - [Authentication with Appwrite](https://appwrite.io/docs/products/auth/quick-start) - [React Native Appwrite starter project](https://github.com/divanov11/react-native-appwrite/tree/getting_started) - [Quick start with React Native](https://reactnative.dev/docs/environment-setup) - [Appwrite Account API reference](https://appwrite.io/docs/references/cloud/client-react-native/account) --- ## Should you stop using OTP SMS now? https://appwrite.io/blog/post/should-you-stop-using-otp-sms > Should you stop using SMS OTPs for authentication now? > Yes, because they are vulnerable to attacks and can incur huge costs from telecom providers. There you go; there's no need to read the rest of the article. However, isn't SMS OTP authentication also a very convenient and popular passwordless method? Since there isn't a cut-and-dry answer, does this mean we have to look at this issue with nuance? Let's get started. ### Why passwordless and OTP-based authentication became popular In recent times, the move toward passwordless systems has been driven by the inherent weaknesses of traditional passwords, such as password reuse and weak password usage. OTP-based authentication, particularly through SMS, came up as a convenient alternative. In an SMS OTP authentication workflow, a random numeric or alphanumeric string is generated by a server and sent to a user's mobile device. The user inputs the OTP, which is then validated against the server's generated value. The simplicity of user experience within this workflow and high accessibility (of mobile phones) made SMS OTP authentication a very popular passwordless authentication solution. However, while SMS-based OTP systems initially seemed to resolve password fatigue and provided an easy-to-deploy second factor of authentication, they've been challenged by both security concerns and reliability issues. Despite the technical simplicity of generating and verifying OTPs, the reliance on mobile carriers introduces vulnerabilities that app developers cannot always control, making SMS-based OTP systems an increasingly questionable choice. ### Pros of SMS OTP authentication From a technical perspective, SMS OTP offers several practical benefits: 1. **Ease of implementation**: Most SMS OTP solutions are easy to integrate using APIs provided by services such as Twilio and Vonage. These services handle OTP generation and delivery and provide APIs for server-side validation. 2. **Reduced password dependency**: SMS OTP eliminates the need for complex passwords, reducing attack vectors like credential stuffing or brute-force attacks. Passwords are replaced by short-lived tokens, reducing exposure. 3. **Cost-effective for user coverage**: Mobile phone penetration is nearly universal, meaning that users don't need additional devices or applications for SMS OTP. This can significantly lower the barriers to adoption in systems where basic security enhancements are needed. 4. **Asynchronous delivery**: SMS OTPs allow for asynchronous message delivery, which can be useful in low-bandwidth or high-latency environments where real-time authentication (like push notifications) may not be reliable. 5. **Simpler UX:** SMS OTPs allow users to have a [simpler authentication experience](https://appwrite.io/blog/post/improve-ux-passwordless-auth) as they do not need to memorize or manage more passwords. ### Cons of SMS OTP authentication The technical drawbacks of SMS OTP often outweigh its benefits, especially in the context of modern security concerns: 1. **Vulnerable to SIM-swapping**: Attackers can exploit weaknesses in mobile carriers' systems through SIM-swapping attacks, effectively taking control of the target's phone number. Once an attacker has access to the SMS stream, they can intercept OTPs, gaining unauthorized access. 2. **Man-in-the-Middle (MITM) attacks**: In certain cases, attackers can intercept SMS OTPs using malware or by exploiting vulnerabilities in the SS7 protocol, which governs how messages are exchanged in cellular networks. This introduces a significant attack vector outside the control of the app or system utilizing the OTP. 3. **Delivery failures and latency**: SMS delivery is dependent on mobile network reliability, which varies across geographies. Message delays or failures can degrade the user experience, especially in critical workflows such as multi-factor authentication (MFA). 4. **No end-to-end encryption**: SMS does not provide end-to-end encryption, meaning that messages are susceptible to interception at various points, whether through carrier vulnerabilities or rogue network infrastructure. 5. **Replay attacks**: While most OTP systems implement time-based restrictions, if the OTP isn't invalidated after first use or is used in conjunction with insecure session management practices, it opens the door for replay attacks. 6. **Social engineering**: One of the most common ways attackers bypass SMS OTP systems is through social engineering attacks. Attackers impersonate service providers or technical support teams and trick users into revealing OTPs sent to their phones. Since the user unwittingly gives the attacker the valid OTP, this renders the entire security process ineffective. SMS OTP solutions do not provide any built-in mechanism to defend against these types of attacks, leaving users vulnerable. ### Alternatives to SMS OTP authentication To overcome the limitations of SMS OTPs, several more secure and technically advanced alternatives are gaining traction: 1. **TOTP (Time-based One-Time Password) via authenticator apps**: Authenticator apps like Google Authenticator or Authy generate time-based tokens based on a shared secret (HMAC-based OTPs). This solution is more secure than SMS since it is decoupled from the cellular network, leveraging cryptographic algorithms to generate OTPs locally. TOTP systems are more resilient to man-in-the-middle attacks as they don't transmit OTPs over vulnerable networks. They have grown to become a very popular second factor of authentication. 2. **Push-based authentication**: Push notifications, which trigger real-time approval requests to a user's trusted device, can authenticate users without needing a code. Push-based methods use secure, encrypted communication channels between the client and server, reducing the attack surface. These systems are typically integrated via SDKs like Firebase Cloud Messaging (FCM) or Apple Push Notification service (APNs). The method is effective for mobile apps; however, it will not work for web apps without a mobile companion. 3. **Magic links**: Magic links are an increasingly popular alternative in passwordless authentication flows. When users attempt to log in, they receive a time-bound link via email, which authenticates them upon clicking. Since no credentials are exchanged during the login, magic links eliminate the risk of password-related attacks and are immune to phishing as long as the email system itself is secure. 4. **Email-based OTPs**: Email OTPs follow the same principle as SMS OTPs but are delivered via email. While email can still be vulnerable to phishing and interception, email-based OTPs reduce the risk of SIM-swapping and mobile network vulnerabilities. For improved security, emails can be encrypted and coupled with secure email providers, but the method remains vulnerable to phishing if the user's email account is compromised. 5. **FIDO2/WebAuthn (Public Key Cryptography)**: The FIDO2/WebAuthn standard uses public key cryptography to authenticate users. Instead of sending a shared secret like an OTP, the authentication process involves signing a challenge with a private key stored in a secure element on the user's device (e.g., a YubiKey, fingerprint sensor, or any other hardware security module). The server validates the signature with the corresponding public key, making this method highly resistant to phishing and other common attacks. This method is designed to eliminate the weaknesses of passwords and OTPs entirely. ### Passwordless authentication with Appwrite Appwrite Authentication also features three passwordless authentication methods: [magic links](https://appwrite.io/docs/products/auth/magic-url), [email OTPs](https://appwrite.io/docs/products/auth/email-otp), and [SMS OTPs](https://appwrite.io/docs/products/auth/phone-sms), which can be integrated into applications seamlessly with Appwrite's client-side SDKs. {% tabs %} {% tabsitem #magicurl title="Magic URLs" %} Magic URL authentication is a two-step process in Appwrite. First, we initialize the login process by sending an email with the magic URL. If the email has never been used, a new account is also generated. ```jsx import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const user = await account.createMagicURLToken({ userId: ID.unique(), email: 'email@example.com' }); ``` After receiving the secret from an email, you can create a session for the user. ```jsx const urlParams = new URLSearchParams(window.location.search); const secret = urlParams.get('secret'); const userId = urlParams.get('userId'); const user = await account.createSession({ userId, secret }); ``` {% /tabsitem %} {% tabsitem #emailotp title="Email OTPs" %} Email OTP authentication is a two-step process in Appwrite. First, we initialize the login process by sending an email. If the email has never been used, a new account is also generated. ```jsx import { Client, Account, ID } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const account = new Account(client); const sessionToken = await account.createEmailToken({ userId: ID.unique(), email: 'email@example.com' }); const userId = sessionToken.userId; ``` After receiving the secret (6-digit number) in the email, you can use it along with the returned user ID to confirm the user. ```jsx const session = await account.createSession({ userId: userId, secret: '[SECRET]' }); ``` {% /tabsitem %} {% tabsitem #smsotp title="SMS OTPs" %} SMS OTP authentication is a two-step process in Appwrite. First, we initialize the login process by sending an SMS. If the phone number has never been used, a new account is also generated. ```dart import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); final account = Account(client); final sessionToken = await account.createPhoneToken( userId: ID.unique(), phone: '+14255550123' ); final userId = sessionToken.userId; ``` After receiving the secret (6-digit number) in the SMS, you can use it along with the returned user ID to confirm the user. ```dart final session = await account.createSession( userId: userId, secret: '' ); ``` {% /tabsitem %} {% /tabs %} Additionally, Appwrite offers a solution that allows developers to [integrate any external authentication method](https://appwrite.io/blog/post/integrate-custom-auth-sveltekit) with their Appwrite project. ### Conclusion So, now we know what SMS OTPs are good at and bad at. If your app solves a more casual use case, you are very much good to go with OTP authentication. However, if you have very sensitive data and higher security requirements, a [multi-factor authentication](https://appwrite.io/docs/products/auth/mfa) solution using alternative methods like TOTPs and FIDO2/WebAuthn is the way to go. #### Further reading - [Appwrite Authentication docs](https://appwrite.io/docs/products/auth) - [Appwrite YouTube channel](https://www.youtube.com/@Appwrite) - [Appwrite Discord server](https://appwrite.io/discord) --- ## How Twilio simplifies messaging for developers https://appwrite.io/blog/post/simplify-messaging-twilio Every day, we receive multiple messages from different web and mobile applications, whether OTPs for transactions or promotional messages. If you're a developer, you might have even developed one yourself. If you haven't already guessed, this blog post focuses on messaging - explaining what it is, the tools you can utilize to build a messaging system, and more. ### What is Messaging? Messaging refers to sending messages directly to a user's mobile device from an app or website, even when the user is not actively using the application. It is a powerful tool for engagement, providing timely information, reminders, alerts, or updates. It can be used for various reasons, including: - **Transactional messages:** These messages relate to the user's actions or account, such as payment confirmation. - **Promotional messages**: These marketing messages promote products, services, or offers. - **Reminder messages**: These remind users about events, tasks, or actions they must take. - **Location-based messages**: These are triggered by the user's location. #### Challenges faced when building a messaging system While messaging is a standard feature, it can be a complex task involving various technical and design challenges. Developers often encounter issues related to scalability, reliability, and cross-platform compatibility. In terms of scalability, some common challenges faced are: - **Handling high volume**: Messaging apps can experience sudden spikes in usage, requiring quick infrastructure scaling without performance degradation. - **Data management**: Efficiently storing, retrieving, and managing large volumes of messages is a significant challenge, especially with growing user bases. - **User connections**: Maintaining concurrent user connections and ensuring real-time message delivery without delays. - **Diverse platforms**: Ensuring consistent functionality across various iOS, Android, and web platforms. ### How Twilio can simplify implementation [Twilio](https://www.notion.so/How-tools-like-Twilio-can-simplify-messaging-for-developers-9475a3f11992419c9266cf8af703c95e?pvs=21) is a cloud communications platform that offers a wide range of services, including SMS, voice, video, and email. It's known for its flexibility, scalability, and ease of integration into various applications. Twilio also provides a robust push notification service, part of its broader suite of communication tools. Here are some of the benefits of using Twilio: - **Multi-channel support**: Twilio provides a single platform to handle different types of messaging, including SMS, MMS, email, and push notifications. This unified approach means developers can manage various communication channels through one interface. - **Cross-platform messaging**: Twilio supports push notifications for both iOS and Android platforms, simplifying the process of creating cross-platform compatible messages. - **Cloud infrastructure**: Being a cloud-based service, Twilio scales automatically to handle varying loads, which is crucial for applications that may experience sudden spikes in user activity. - **High availability**: Twilio's robust infrastructure ensures high availability and reliable message delivery, reducing concerns about downtime or message loss. - **Personalization and segmentation**: Twilio allows for the customization and personalization of messages, enabling targeted communication strategies. - **Scheduling and automation**: Developers can schedule notifications and automate messaging based on user actions or predefined conditions. - **Delivery reports**: Twilio provides feedback on message delivery status, which is crucial for understanding and improving the effectiveness of notifications. - **Real-time analytics**: Access to real-time analytics helps monitor the performance of messaging campaigns. One of Twilio’s products that lets developers implement messaging across various channels such as SMS, MMS, and WhatsApp is Programmable Messaging. #### Twilio Programmable Messaging Twilio Programmable Messaging allows you to send alerts, notifications, promotions, and marketing messages to your customers' preferred channels using a single API. Since the offering leverages a RESTful API, it is compatible with various programming languages and frameworks. It utilizes webhooks for real-time notifications of message delivery status and inbound messages. Security features like HTTPS and API keys ensure secure message transmission. Additionally, the Messaging API automatically queues messages when sending high volumes or when rate limits are reached. Here's an overview of the Twilio Programmable Messaging: - **Multiple messaging services**: Supports SMS, MMS, and chat services, allowing developers to send and receive messages across different platforms. - **Scalable infrastructure**: The API is backed by Twilio's robust cloud infrastructure, ensuring high availability and scalability to handle large volumes of messages. - **Messaging insights:** Allows you to analyze your application's messaging, identify and debug issues, optimize delivery, and find areas to boost engagement with your end users. - **Content template builder:** This lets you create and send rich messaging content over any Twilio-supported messaging channel. - **Link shortening:** Allows you to send messages with shortened links using your company-branded domain and track clicks. - **Message scheduling:** This lets you schedule SMS, MMS, and WhatsApp messages to be sent at a fixed time in the future. ### Using Twilio with Appwrite Messaging As a part of our latest release, Appwrite 1.5, we have launched a new product, Messaging, that helps developers implement communication services within their applications. As a part of the SMS offering within Messaging, we have added an adapter to integrate Twilio Programmable Messaging with Appwrite. Once you have access to your account SID, auth token, and phone number from Twilio, you will be able to send messages to your users with just a few lines of code. ```server-nodejs const sdk = require('node-appwrite'); // Init SDK const client = new sdk.Client(); const messaging = new sdk.Messaging(client); client .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint .setProject('') // Your project ID .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key ; const promise = await messaging.updateTwilioProvider( '', // providerId '', // name (optional) false, // enabled (optional) '', // accountSid (optional) '', // authToken (optional) '' // from (optional) ); ``` ### Resources Visit our documentation to learn more about Messaging, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/messaging/twilio) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Simplify your data management with relationships https://appwrite.io/blog/post/simplify-your-data-management-with-relationships Managing collections of data is an essential task for any application, but it can quickly become complex when you need to keep track of the relationships between different collections. For instance, if you have two collections of data, such as movies and reviews, you may need to retrieve all the reviews associated with a particular movie. However, writing complex code to retrieve this data and merge it together manually can be time-consuming and prone to errors. That's where one of Appwrite’s newest features comes in. Database relationships help you manage the links between your collections of data more easily. With this feature, you can create links between different collections by simply adding a new attribute to them. You can choose from four types of relationships: one-to-one, one-to-many, many-to-one, and many-to-many. ### Relationship Types #### One to One ![one to one](/images/blog/simplify-your-data-management-with-relationships/one-to-one.png) A one-to-one relationship means that each record in one collection is associated with only one record in another collection. For example, if you have a collection of users and a collection of profiles, each user can have only one profile, and each profile can belong to only one user. #### One to Many ![one to many](/images/blog/simplify-your-data-management-with-relationships/one-to-many.png) A one-to-many relationship is when each record in one collection can be associated with multiple records in another collection. For instance, if you have a collection of artists and a collection of albums, each artist can have many albums released, but each album can only be released by one artist. #### Many To One ![many to one](/images/blog/simplify-your-data-management-with-relationships/many-to-one.png) In contrast, many-to-one relationships are when multiple records in one collection can be associated with a single record in another collection. Inversely to the previous example, if you have a collection of albums and a collection of artists, many albums can be released by a single artist. While this may seem the same as a one-to-many relationship, it differs once you take the direction into account. A many-to-one relationship that is one-way can be used to represent a relationship that is only seen on the many side. #### Many to Many ![many to many](/images/blog/simplify-your-data-management-with-relationships/many-to-many.png) Finally, many-to-many relationships describe a scenario where multiple records in one collection can be associated with multiple records in another collection. For example, if you have a collection of books and a collection of authors, each book can have multiple authors, and each author can write multiple books. By understanding the differences between these relationship types, you can choose the best one that suits your application's needs and create a more efficient database management system. ### Relationship Directions Appwrite relationships offer a high degree of flexibility and customization, allowing developers to create various scenarios with the available options. In addition to choosing from the four relationship types, developers can also decide whether the relationship should be one-way or two-way. #### One Way One-way relationships are a type of relationship where only the collection where the relationship attribute was created will see the relationship. For example, suppose a developer creates a one-way relationship between a movies collection and a reviews collection. In that case, the movies collection will have an attribute containing the related reviews, but the reviews collection will not have an attribute containing the related movie. This type of relationship is useful in scenarios where the parent collection holds all the necessary information, and the child collection only needs to access the parent's data. The movie response: ```json { "$id": "642b9afc785532a807d8", "$databaseId": "marvel", "$collectionId": "movies", "$createdAt": "2023-04-04T03:35:24.493+00:00", "$updatedAt": "2023-04-04T03:35:24.493+00:00", "$permissions": [ ], "title": "Spiderman", "reviews": [ { "$id": "642b9d627d866e646602", "$databaseId": "marvel", "$collectionId" :"reviews", "$createdAt": "2023-04-04T03:45:38.514+00:00", "$updatedAt": "2023-04-04T03:45:38.514+00:00", "$permissions": [ ], "content": "Great movie" } ] } ``` The review response, notably with no review attribute: ```json { "$id": "642b9d627d866e646602", "$databaseId": "marvel", "$collectionId": "reviews" "$createdAt": "2023-04-04T03:45:38.514+00:00", "$updatedAt": "2023-04-04T03:45:38.514+00:00", "$permissions": [], "content": "Great movie" } ``` #### Two Way On the other hand, a two-way relationship is a type of relationship where both collections will see the relationship. In the same example, if a two-way relationship is created between the movies and reviews collections, both collections will have attributes containing the related data. This type of relationship is beneficial in scenarios where both the parent and child collections need to access each other's data. The movie response: ```json { "$id": "642b9afc785532a807d8", "$databaseId": "marvel", "$collectionId": "movies", "$createdAt": "2023-04-04T03:35:24.493+00:00", "$updatedAt": "2023-04-04T03:35:24.493+00:00", "$permissions": [], "title": "Spiderman", "reviews": [ { "$id": "642b9d627d866e646602", "$databaseId": "marvel", "$collectionId": "reviews", "$createdAt": "2023-04-04T03:45:38.514+00:00", "$updatedAt": "2023-04-04T03:45:38.514+00:00", "$permissions": [], "content": "Great movie" } ] } ``` The review response, now with the movie attribute: ```json { "$id": "642b9d627d866e646602", "$databaseId": "marvel", "$collectionId": "reviews", "$createdAt": "2023-04-04T03:45:38.514+00:00", "$updatedAt": "2023-04-04T03:45:38.514+00:00", "$permissions": [], "content": "Great movie", "movie": { "$id": "642b9afc785532a807d8", "$databaseId": "marvel", "$collectionId": "movies", "$createdAt": "2023-04-04T03:35:24.493+00:00", "$updatedAt": "2023-04-04T03:35:24.493+00:00", "$permissions": [], "title": "Spiderman" } } ``` One-way and two-way relationships offer different advantages and disadvantages, depending on the specific requirements of the application. In some cases, one-way relationships can provide better performance by reducing the amount of data that needs to be stored and accessed. However, two-way relationships can provide more flexibility by allowing both collections to access each other's data. ### On Delete Behavior Managing related data can still be challenging, especially when deleting data. To address this, Appwrite relationships offer three deletion strategies: restrict, cascade, and set null. #### Restrict If you select the restrict option, you won't be able to delete a parent document if it has any related child documents. This option is helpful if you want to ensure that data integrity is maintained and that you don't accidentally delete data that is still relevant. #### Cascade If you choose cascade, deleting a parent document will also delete all related child documents. This option can be helpful if you want to remove all data associated with a particular parent record, such as when you want to delete a user and all their associated data. #### Set Null Finally, the set null option means that deleting a parent document will remove the relationship to the parent document for all of its related children. This can be useful if you want to retain the child's records but simply remove the relationship. Each of these options has its own use cases, and the choice ultimately depends on your specific application requirements. By providing these different options, Appwrite allows you to manage related data in a flexible and intuitive way, making it easier for you to build complex applications without worrying about data management. ### Other Benefits In addition to simplifying your collection management, database relationships also provide several other benefits. First, they can help to ensure data consistency and integrity by enforcing referential constraints between related collections. This means that you can prevent orphaned documents or other data inconsistencies that might arise if you were to manage the relationships between collections manually. Additionally, using relationships can also improve the performance of your requests and reduce the amount of code you need to write, since you can retrieve related data in a single request rather than having to fetch it separately. Overall, database relationships are a powerful tool that simplifies your collection management and saves you time and effort. By easily linking your collections and retrieving related data, you can focus on developing your application's core features. Check out the [docs](/docs/databases-relationships) for more information. We encourage you to give it a try today and see how it can benefit your development process. - [Appwrite GitHub](https://github.com/appwrite) - [Appwrite Docs](/docs) - [Discord Community](https://appwrite.io/discord) --- ## Social media authentication: convenience vs privacy https://appwrite.io/blog/post/social-media-auth Social media authentication has become an integral part of our digital lives, offering a streamlined way to access various online services. We may not even realize it at times; however, logging in with Facebook, X, Linkedin, and other such providers are common authentication methods we come across in most major applications today. There are challenges to using social media providers as authentication methods, though. A [massive Facebook incident in 2021](https://abcnews.go.com/International/facebook-outage-highlights-risks-overdependence-single-tech-giant/story?id=80413709) rendered over a billion people helpless as they lost access to a major part of the internet. Such dependence on major social media providers for essential internet activity can have major drawbacks, too. Therefore, in this blog, let us discuss the pros and cons of social media authentication and the delicate balance of convenience and privacy surrounding it. ### The benefits of social media authentication Social media authentication has a number of benefits for developers and consumers of their products: - **Simplified login process**: Social media authentication streamlines the login process. Users can access multiple services without the need to create and remember different usernames and passwords. This ease of access reduces friction and enhances user experience significantly. - **Increased registration and conversion rates**: For websites and apps, offering social media authentication can lead to higher registration rates. Users are more likely to sign up for a service if they can do it with just a few clicks rather than filling out a lengthy registration form. - **Reduced password fatigue**: With the overwhelming number of online accounts today, password fatigue is a real issue. Social media authentication helps mitigate this by reducing the number of passwords a user has to remember. - **Faster account recovery**: Recovering accounts is generally more straightforward with social media authentication, as it often involves fewer steps than traditional email/password recovery processes. - **Social proof and trust**: Users often trust social media platforms they frequently use. By offering authentication through these platforms, services can leverage this trust, making users feel more secure about signing up or logging in. - **Improved security**: Often, social media platforms have robust security measures in place. Using their authentication systems can, in some cases, offer better security than relying on traditional username-password systems, especially if users tend to create weak passwords. - **Access to social data**: For businesses, social media authentication can provide access to certain user data from their social media profiles (with user consent). This data can be used to personalize and enhance the user experience. ### The drawbacks of social media authentication While the list of benefits that social media authentication brings is not small, the drawbacks are not insignificant. Here are some of the cons to keep in mind: - **Privacy concerns**: Social media authentication often requires sharing personal information from social media accounts with third-party services. Users may unintentionally grant access to more data than they realize, raising serious privacy concerns. - **Data security risks**: Linking multiple accounts through social media authentication can create a single point of failure. If a user's social media account is compromised, it could potentially jeopardize the security of all connected accounts. - **Limited control over data**: Users typically have less control over what data is shared when using social media login options. They might be sharing personal information like email addresses, friend lists, or even browsing habits without explicit knowledge. - **Dependency on social media platforms**: This authentication method creates a dependency on social media platforms. If a social media service experiences downtime or decides to change its policy or authentication process, it can directly impact all linked services. Additionally, if a user loses access to their social media account, it also means losing access to all relying services. - **Loss of anonymity**: Users may prefer to keep their social media profiles private and separate from other online activities. Social media authentication can erode this separation, leading to a loss of anonymity online. - **Potential for data breaches**: The interconnectedness of accounts through social media authentication can escalate the impact of data breaches. A breach in one service could potentially expose information across multiple platforms. - **Legal and compliance issues**: Relying on social media authentication can complicate compliance with privacy laws and regulations, such as GDPR, especially when it comes to obtaining consent for data sharing and processing. ### Implementing alternative authentication methods Developers have several alternative authentication methods at their disposal, offering different benefits in terms of security, privacy, and user experience. Here are some noteworthy alternatives: - **Email and password**: The traditional method of using a unique combination of email and password remains popular. It gives users full control over their credentials, though it requires robust security measures to prevent breaches. - **Biometric authentication**: Utilizing fingerprints, facial recognition, or iris scans for authentication. This method offers high security and a good user experience but requires specific hardware and can raise privacy concerns. - **Magic links**: These are one-time-use links sent to the user's email address. Clicking the link logs the user into the application. Magic links enhance security by eliminating the need for passwords and are user-friendly. - **OTP-based authentication**: This involves sending a code via SMS or email to a user, which they enter into the application. While user-friendly, this method has been criticized for security vulnerabilities (especially with SMS). - **Smart cards and USB keys**: Physical devices like smart cards or USB keys can be used for authentication. They provide a high level of security but require users to have the physical device on hand. - **Federated identity**: This involves linking the user's identity across multiple systems and services, allowing for the portability of identity and personal information. It's complex to implement but offers a seamless user experience. One major platform that leverages this method is Mastodon. Of these methods, Appwrite offers email-password authentication, magic links, and OTP-based authentication as a part of its suite of authentication offerings. You can learn more about them in [Appwrite’s documentation](/docs/products/auth). ### Moving forward Social media login is convenient but has privacy and security issues. It's important for both users and developers to understand these risks. Fortunately, there are other ways to log in, like using email and password, biometric data, magic links, etc. These alternatives can protect user privacy better in certain circumstances and reduce dependence on social media platforms. Ultimately, the choice of authentication method should be guided by a thorough understanding of its benefits and limitations, always with the end user's best interests in mind. Learn more about Appwrite Authentication from our [docs](/docs/products/auth) and join our [Discord community](https://appwrite.io/discord) to interact with fellow developers using the same. --- ## Sound null-safety for your Dart functions https://appwrite.io/blog/post/sound-null-safety-for-your-dart-functions Dart 3 runtimes are now available on Appwrite Cloud. Among the many cool features introduced in Dart 3, sound null safety stands out as an important change for Appwrite Functions. Sound null safety means you can expect to know when a variable is not null. More importantly, it helps you make sure you properly handle potential null values by warning you at compile time. When you build with Appwrite Functions, you’ll often build integrations that interface with external services and user input directly. In these cases, you have no control over what’s sent over the request. This helps prevent all types of errors at run time, which can be difficult to catch and debug otherwise. ```dart The getter 'age' was called on null. Receiver: null Tried calling: type ``` ### The setup Let’s take an example function that takes a JSON object that’s expected in the request to document which people showed up at an event when they came, and when they left. The JSON would look like this under ideal situations. ```json { "profiles": [ { "name": "John", "age": "25", "stay": { "arrivedAt": "2022-01-01T00:00:00Z", "leftAt": "2022-01-01T02:00:00Z" } }, ... more profiles ], "count": 15 } ``` ### The naive implementation ```dart import 'dart:async'; class Stay { final String arrivedAt; final String leftAt; Stay({this.arrivedAt, this.leftAt}); factory Stay.fromJson(Map json) { if (json == null) { return null; } return Stay( arrivedAt: json['arrivedAt'], leftAt: json['leftAt'], ); } } class Profile { final String name; final String age; final Stay stay; Profile({this.name, this.age, this.stay}); factory Profile.fromJson(Map json) { if (json == null) { return null; } return Profile( name: json['name'], age: json['age'], stay: Stay.fromJson(json['stay']), ); } } // This is your Appwrite function // It's executed each time we get a request Future main(final context) async { context.log("Here are the people that showed up!"); var people = context.req.body; // This is JSON parsed as a map List profiles = (people['profiles'] as List) .map((profile) => Profile.fromJson(profile)) .toList(); for (var profile in profiles) { context.log("Name: ${profile.name}"); context.log("Age: ${profile.age}"); context.log("Arrived at: ${profile.stay.arrivedAt}"); context.log("Left at: ${profile.stay.leftAt}"); } context.res.empty(); } ``` In Dart 2, without null safety, this code will happily compile and run. Well, until your API receives an incomplete JSON like this: ```dart { "profiles": [ { "name": "Bob", "age": "35" } { "name": "Bob", "age": "35", "stay": { "arrivedAt": "2022-01-01T02:00:00Z", "leftAt": "2022-01-01T04:00:00Z" } }, {} ], "count": 3 } ``` This will give you an unexpected runtime error. If you’re building integrations or new APIs with Appwrite functions, you’d want to gracefully handle the null values instead of seeing this error in your logs. ```dart The getter 'age' was called on null. Receiver: null Tried calling: age ``` ### Handling nulls in Dart 3 If you tried to follow along in Dart 3, you’ll find the compiler screaming at us to handle null values. ```dart compiling... ../build/lib/main.dart:8:14: Error: The parameter 'arrivedAt' can't have a value of 'null' because of its type 'String', but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier. Stay({this.arrivedAt, this.leftAt}); ^^^^^^^^^ ... And more errors for every single non-null safe access! ``` While this is annoying, forcing you to explicitly handle nulls at compile times make sure you get no surprises at runtime. Let’s add some null handling to our function. First, lets look at the Profile class. ```dart class Profile { final String name; // Can't be null final String age; // Can't be null final Stay ?stay; // Can sometimes be null // You must provide name and age, age might not be provided Profile({required this.name, required this.age, this.stay}); factory Profile.fromJson(Map json) { return Profile( name: json?['name'] ?? 'No name provided', age: json?['age'] ?? 'No age provided', stay: json?['stay'] != null ? Stay.fromJson(json['stay']) : null, ); } } ``` Notice that `final String name` and `final String age` are declared as class variables that cannot be null, but `final Stay ?stay` is explicitly stated to be nullable. This is why the constructor of `Profile({required this.name, required this.age, this.stay})` specifies that name and age are required. If you try to pass null, or a nullable value into these required params without providing non-null defaults, the compiler will complain. When constructing a `Profile` class from a JSON, we also did some null handling. We access members using `json?[]` syntax, which checks if `json` is null before attempting access, preventing null pointer exceptions. Since stay is nullable, we check if stay exists in the `json` passed in, and if not, we default to null. Later when accessing member `stay`, we’ll be reminded to handle this properly. In `Stay` there are similar concepts applied. ```dart class Stay { final String? arrivedAt; final String? leftAt; Stay({this.arrivedAt, this.leftAt}); factory Stay.fromJson(Map? json) { return Stay( arrivedAt: json?['arrivedAt'], leftAt: json?['leftAt'], ); } } ``` Declaring `arrivedAt` and `leftAt` as optional forces us to explicitly check to null safety during access. ```dart context.log("Name: ${profile.name}"); context.log("Age: ${profile.age}"); context.log("Arrived at: ${profile.stay?.arrivedAt}"); context.log("Left at: ${profile.stay?.leftAt}"); ``` Notice how we can safely access `name` and `age`, but we must check if stay is null. If we decide to access `arrivedAt` or `leftAt` to split the date string, for example, we’ll also be warned to handle null values here since these are also nullable. ```dart context.log("Arrived at: ${profile.stay?.arrivedAt?.split('T')[0] ?? 'unknown'}"); context.log("Left at: ${profile.stay?.leftAt?.split('T')[0] ?? 'unknown'}"); ``` ### The new function Here’s the new Appwrite with all the null handling in place. ```dart import 'dart:async'; class Stay { final String? arrivedAt; final String? leftAt; Stay({this.arrivedAt, this.leftAt}); factory Stay.fromJson(Map? json) { return Stay( arrivedAt: json?['arrivedAt'], leftAt: json?['leftAt'], ); } } class Profile { final String name; final String age; final Stay ?stay; Profile({required this.name, required this.age, this.stay}); factory Profile.fromJson(Map json) { return Profile( name: json?['name'] ?? 'No name provided', age: json?['age'] ?? 'No age provided', stay: json?['stay'] != null ? Stay.fromJson(json['stay']) : null, ); } } // This is your Appwrite function // It's executed each time we get a request Future main(final context) async { context.log("Here are the people that showed up!"); var people = context.req.body; // This is JSON parsed as a map List profiles = (people['profiles'] as List) .map((profile) => Profile.fromJson(profile)) .toList(); for (var profile in profiles) { context.log("Name: ${profile.name}"); context.log("Age: ${profile.age}"); context.log("Arrived at: ${profile.stay?.arrivedAt?.split('T')[0] ?? 'unknown'}"); context.log("Left at: ${profile.stay?.leftAt?.split('T')[0] ?? 'unknown'}"); } context.res.empty(); } ``` Which will gracefully handle even malformed JSONs like the one we showed above and still output to the best of its ability. ```dart Here are the people that showed up! Name: Jane Age: 30 Arrived at: unknown Left at: unknown Name: Bob Age: 35 Arrived at: 2022-01-01 Left at: 2022-01-01 ``` ### Wrapping up Dart 3 with sound null-safety will save you from hours of debugging when tracking down null-value related errors at runtime. Forcing you to be conscious of handling null values brings these null-value caused errors from runtime to compile time. Having compiler errors is always a better time than errors in your precious production functions. If you’re still on Dart 2.x, I strongly recommend you migrate to Dart 3 to benefit from safety. Dart’s documentation has a [migration guide](https://dart.dev/null-safety#migrate) to help you simply the process. ### Resources Visit our documentation to learn more about Dart, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/functions/runtimes) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## SQL vs NoSQL: Choosing the right database for your project https://appwrite.io/blog/post/sql-vs-nosql If you've been wondering whether to use SQL or NoSQL for your next project, you found the right article. Choosing which database to use is a key part of system design, as each type offers specific advantages and limitations. This choice impacts everything from application performance and scalability to data management and operational costs. In this guide, we'll cover SQL and NoSQL databases in detail, examining data structure, scalability, performance, querying capabilities, and practical use cases. By the end, you should have a clearer understanding of which database type best fits your project. ### **Understanding SQL databases** **SQL databases**, also known as **relational databases**, structure data into tables with rows and columns. Common SQL databases include **MySQL**, **MariaDB**, **PostgreSQL**, and **Oracle**. The relational model links data across tables using **primary** and **foreign keys**, providing a highly organized, consistent data structure. #### **Key SQL features and how they impact projects** SQL databases follow a **predefined structure**, where each table's columns and data types, like integers, strings, or dates are defined ahead of time. This structure enforces data consistency across records, though it can be limiting when changes are needed later on, as altering the structure may require database migrations or updates that impact application performance temporarily. SQL databases maintain high data accuracy by following **ACID properties** (Atomicity, Consistency, Isolation, Durability). These principles guarantee that SQL databases handle data reliably, even under high transaction volumes. Here's a closer look at what each part means: - **Atomicity** ensures that each transaction completes fully or not at all. - **Consistency** enforces valid data states throughout transactions. - **Isolation** means that each transaction runs independently. - **Durability** ensures that once a transaction finishes, its results are permanent, even if the system crashes. SQL databases' **relational model** allows developers to use **JOIN** operations, which pull data from related tables in a single query. This is helpful in applications where data from different tables needs to be combined regularly, such as customer and order data in e-commerce. SQL supports complex queries, aggregations, and nested queries, which makes data management easier. **Scalability in SQL** databases is usually **vertical**, meaning you add more resources (like CPU or RAM) to a single server to handle more data. SQL databases can also replicate data across multiple servers to increase read performance, but horizontal scaling (distributing data across servers) is harder and may require special setup, such as splitting data into smaller parts. #### **SQL database use cases** With its strict structure and emphasis on data integrity, SQL is well-suited for applications that demand consistent, reliable data management. Examples include financial software, enterprise resource planning (ERP) systems, and customer relationship management (CRM) tools. ### **Understanding NoSQL databases** **NoSQL databases** take a more flexible approach to data storage. They allow varied data structures and don't require predefined tables and columns. This is useful for modern applications with fast-changing data needs. Common NoSQL databases include **MongoDB**, **Cassandra**, **Redis**, and **Neo4j**. #### **Different NoSQL types and their structures** Unlike SQL's single-table format, NoSQL has multiple types of databases, each with specific data models suited for different applications. The main types are: - **Document stores**: Stores data as documents, often in JSON format, allowing for nested data and varied fields within each document. **MongoDB** is a popular document store. - **Key-value stores**: Stores data as key-value pairs, making data easy and quick to retrieve by key. **Redis** is widely used for caching and quick access to session data. - **Wide-column stores**: Uses a column-based layout, where data is organized by columns, making reads and writes faster on large datasets. **Cassandra** is often used for analytics. - **Graph databases**: Organizes data as nodes and connections, allowing for fast searches across complex relationships. **Neo4j** is a common choice for applications with connected data, like social networks. These different NoSQL types offer flexibility in data models, allowing developers to choose the right model based on their application's needs. #### **Flexibility and schema design in NoSQL** One major benefit of NoSQL databases is their ability to work with varied data models. Without a set structure, NoSQL databases can adjust quickly to changing requirements, which can be very useful in projects with evolving data. For example, NoSQL's flexibility makes it easy to add new data attributes on the fly, unlike SQL databases, where major changes may require extra setup. Instead of following SQL's strict ACID properties, many NoSQL databases use **BASE principles** (Basically Available, Soft state, Eventually consistent). BASE principles allow data to be handled more flexibly, sacrificing strict consistency for better availability and response time. In distributed NoSQL systems, **eventual consistency** means that data will sync over time across all servers, even if there are temporary differences. This approach works well in applications where availability matters more than real-time data accuracy. In contrast, SQL databases maintain a structured schema, which can provide clarity and consistency over time. While adding or altering attributes in SQL databases typically involves table locking, meaning affected tables may experience temporary downtime during updates, this structured approach supports strict data validation, which ensures consistent data across the database. #### **Scalability and high availability in NoSQL** NoSQL databases are built for **horizontal scaling**, meaning they can distribute data across multiple servers as needed. This model lets NoSQL databases handle large amounts of data by balancing the load across different servers. Partitioning and replication are typically built into NoSQL systems, which makes it easier to manage larger amounts of data while keeping it accessible even if some servers fail. **Use cases for NoSQL**: NoSQL's flexible data model and high availability make it a strong choice for applications with varied data needs and fast-growing data. These include social media platforms, Internet of Things (IoT) networks, and real-time analytics, where data types and needs may change frequently. ### **Cost implications** Cost is an important factor when choosing between SQL and NoSQL databases, as each can involve different expenses related to setup, scaling, and maintenance. 1. **Setup Costs**: SQL databases often involve licensing fees, especially with commercial options like **Oracle** or **SQL Server**. While open-source SQL databases like **MySQL** and **PostgreSQL** are free, enterprise features sometimes require a paid version. NoSQL databases are often open-source and free to use, but advanced features or support may come at a cost. 2. **Scaling Costs**: SQL's vertical scaling model can quickly increase costs as your project grows because adding resources to a single server tends to be more expensive than distributing the workload. NoSQL's horizontal scaling model allows for adding cheaper servers incrementally, making it more cost-effective at large scales. 3. **Operational Costs**: SQL databases can incur extra costs for backup, replication, and specialized hardware if the system has complex requirements. NoSQL's distributed design can reduce these operational expenses by simplifying storage and replication across commodity hardware. Choosing a database often involves balancing upfront costs with future scaling expenses. If rapid growth is expected, NoSQL can be a more affordable long-term option. However, for small to medium projects, SQL's cost model may be manageable. ### **Security considerations** Security practices and built-in protections vary between SQL and NoSQL databases, so it's essential to consider the specific security requirements of your application. - **SQL Security**: SQL databases traditionally support **role-based access control** (RBAC), which lets administrators set permissions based on user roles. SQL databases also commonly use **encryption** for data at rest and in transit. Most enterprise SQL systems support multi-layer security, which can be useful in regulated industries like finance or healthcare. - **NoSQL Security**: NoSQL databases vary widely in security features, and some lack native access controls or encryption, especially in open-source versions. Many NoSQL systems rely on application-level security measures instead of database-level controls, which can add complexity to development. That said, commercial NoSQL providers like **MongoDB Atlas** offer robust security tools, including encryption, access control, and compliance certifications. In projects handling sensitive data, SQL's structured security model may provide more reliability, though managed NoSQL services are closing the gap with improved security options. ### **Data consistency and handling trade-offs** One of the main distinctions between SQL and NoSQL is how each handles data consistency. SQL databases prioritize strong consistency, making them ideal for applications where each transaction must be valid and complete before moving to the next step. However, in distributed NoSQL systems, consistency is often relaxed to support better performance and availability. **Consistency Models**: - **SQL**: Strong consistency is a priority, where every transaction is validated immediately across all data before proceeding. This prevents conflicts but can slow down performance in distributed environments. - **NoSQL**: NoSQL databases often follow an eventual consistency model, which allows data to sync across nodes over time. This means that data may not be immediately consistent in all locations, which is acceptable in applications where occasional delays are tolerable (e.g., social media or caching systems). These differences reflect the **CAP theorem**, which states that distributed systems can only optimize two out of three factors: Consistency, Availability, and Partition Tolerance. SQL prioritizes Consistency and Partition Tolerance (CP), while NoSQL focuses on Availability and Partition Tolerance (AP). The choice depends on your project's tolerance for delayed consistency. ### **Performance and Speed** Performance and speed are essential in choosing a database, especially for real-time or high-volume applications. SQL and NoSQL databases each have unique strengths in these areas. #### **SQL Database Performance** SQL databases are optimized for complex transactions and structured queries. They support indexes, which allow fast data retrieval without scanning entire tables. Indexes improve query speed, while locks ensure data consistency in concurrent transactions, though they can cause delays in high-traffic environments. - **Transaction Integrity**: SQL databases follow ACID principles to maintain data accuracy, making them suitable for applications needing strong consistency, like financial systems. - **Indexes and Locks**: Both SQL and NoSQL databases use indexes to speed up data access. SQL databases also rely on locks to prevent conflicts during updates, though heavy locking in high-traffic situations can slow down performance. - **Scaling Constraints**: SQL databases typically scale vertically (adding resources to a single server), which can be costly, and complex joins with frequent locks may impact performance as data volume grows. #### **NoSQL Database Performance** NoSQL databases are designed for fast read/write operations and horizontal scaling across multiple servers, handling large datasets with minimal delay. - **High Throughput**: Horizontal scaling distributes the workload across servers, allowing better performance as data volume increases. - **Indexes and Reduced Locking**: NoSQL databases also use indexes for faster data retrieval. With fewer complex joins, they typically require fewer locks, enabling faster operations under high load. - **Low Latency**: In-memory stores like Redis reduce disk access times, which makes NoSQL well-suited for real-time applications. ### **Maintenance and Operational Complexity** Both SQL and NoSQL databases have specific maintenance and operational considerations, especially as the project scales. - **SQL maintenance**: SQL databases require careful maintenance due to dependencies created by their relational structures. Changes to one table often impact related tables, and schema changes can require database downtime for migrations. Backup and disaster recovery strategies are generally well-supported in SQL systems, but replication and sharding can add to the complexity of managing a SQL database at scale. - **NoSQL Maintenance**: NoSQL databases tend to have simpler maintenance in terms of schema flexibility, as they allow adding new fields without major disruptions. However, distributed NoSQL systems require careful setup for data consistency and partitioning. With eventual consistency, NoSQL may also need regular monitoring to ensure data syncs properly across servers, which can add operational tasks. In summary, SQL databases may be simpler to maintain in structured environments, while NoSQL databases allow more flexibility but can involve additional setup for distributed systems. ### **Comparison table** | **Aspect** | **SQL** | **NoSQL** | | --- | --- | --- | | **Data Model** | Relational, structured tables | Flexible: document, key-value, wide-column, graph | | **Schema** | Predefined, rigid, requires migrations | Schema-less, allows easy changes | | **Scalability** | Vertical (single server); horizontal is complex | Horizontal (multi-server); built for distribution | | **Consistency** | Strong consistency with ACID compliance | Eventual consistency with BASE compliance | | **Querying** | SQL language, supports joins and aggregations | Varies by type; JSON-like, CQL, or key-based queries | | **Use Cases** | Transactional systems, finance, CRM | High-growth, flexible data, low-latency apps (e.g., social) | | **Cost** | Licensing fees possible; vertical scaling costs can be high | Often open-source; lower scaling costs with horizontal setup | | **Security** | Strong role-based access, encryption | Varies; managed solutions offer good security options | | **Maintenance** | Structured maintenance, migrations needed | Easier schema updates; needs careful distributed setup | | **Best for** | Consistent, structured data with clear relationships | Flexible, fast access, and scalable applications | ### **Practical considerations and final decision** Choosing between SQL and NoSQL depends on your project's data structure, performance needs, and scalability requirements. If your project needs a dependable system for managing data with clear relationships and strict accuracy, SQL's structured approach and ACID properties offer key benefits. But for applications that need flexible data handling, fast access, and scalable data management, NoSQL's adaptable structure and BASE principles are often a better fit. In making this choice, consider the specific requirements of your application. SQL suits applications that need consistency and predictable structure, while NoSQL is a good option for projects with growing or varied data needs. With a clear understanding of each, you can choose a database that supports both the immediate needs and long-term goals of your application. ### More resources - [Integrate SQL, NoSQL, Vector, Graph, or any database into your Appwrite project](https://appwrite.io/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project) - [How to plan and execute database migrations with Appwrite CLI](https://appwrite.io/blog/post/how-to-execute-database-migration-with-appwrite-cli) - [Best database pagination techniques](https://appwrite.io/blog/post/best-pagination-technique) --- ## What is an accelerator? A guide for tech startups https://appwrite.io/blog/post/startup-accelerator-guide Whether you're a solo founder, part of a small team, or just starting to turn an idea into a business, you've probably heard the word "accelerator" tossed around. Accelerators have become a key part of the modern startup ecosystem. They exist to give startups a short, intense burst of support, usually over a few months, aimed at accelerating their journey from early traction to steady growth. However, not all accelerators are the same. Some are global giants with vast investor networks. Others are hyper-focused on niche markets or specific technologies. Some ask for equity, others don't. In this guide, we'll break down what startup accelerators actually are, how they work, and the top 10 best accelerators you can consider applying to. Let's dive in. ### What is a startup accelerator? A startup accelerator is a time-bound program designed to fast-track the growth of early-stage startups. Typically last between 3 to 6 months and offer a mix of funding, mentorship, training, and access to investors, usually in exchange for equity. Although not a mandatory criterion, many Accelerators expect you to at least have a [minimum viable product (MVP)](https://appwrite.io/blog/post/how-can-you-rapidly-build-an-mvp-for-your-startup), some early traction, and a founding team to be a part of the program. Most accelerators follow a structured model: - **Competitive application process:** Startups apply and are selected based on team, traction, and potential. - **Fixed program duration:** Programs typically run for 3 to 6 months with an intense, hands-on schedule. - **Mentorship from experienced founders and investors:** Startups get direct guidance from people who’ve built and funded companies. - **Workshops and resources:** Regular sessions on product, fundraising, growth, hiring, legal, and more. - **Peer learning and community:** Founders grow alongside a cohort of startups solving different problems. - **Final demo day:** Startups pitch to a room full of investors, press, and partners to secure follow-on funding. Some accelerators are generalists (like Y Combinator or Techstars), while others specialize by industry, geography, or founder background. What unites them is the goal: compress years of learning into a few focused months and help startups move faster toward product-market fit and funding. ### Benefits of a startup accelerator #### 1. Speed and structure Accelerators give startups a defined timeline, clear milestones, and focused support to move faster than they could on their own. What might take a year to figure out can often be done in a few months, with fewer mistakes. #### 2. Access to capital and investors Most accelerators provide initial funding and introduce you to a network of investors. Demo Day alone can open doors to angel investors, venture capital firms, and strategic backers who trust the accelerator’s vetting process. #### 3. Mentorship and network Startups gain direct access to seasoned founders, operators, and subject-matter experts. This mentorship, paired with a strong alumni network, can unlock insights, avoid pitfalls, and lead to long-term partnerships. ### Drawbacks of a Startup Accelerator #### 1. Equity trade-off Most accelerators take 5–10% equity in exchange for funding and support. For some early-stage founders, this can feel steep, especially if you're already bootstrapped or have other capital options. #### 2. Intense time commitment Accelerator programs are demanding. Expect full-time involvement, mandatory sessions, and tight deadlines. If your team isn’t ready to focus fully, the pressure can become a distraction. #### 3. Unfulfilled promises Some accelerators may not deliver on the promised mentorship, investor connections, or growth and therefore the program doesn’t lead to meaningful progress. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### Accelerator vs Incubator The startup world is filled with buzzwords: incubator, accelerator, venture studio, pre-seed, angel, and so on. It's easy for the lines between them to blur. Many of these programs overlap in how they support founders, but they serve very different needs depending on your stage, goals, and business model. Understanding those differences is key to choosing the right path for your startup. #### Accelerator - Fixed-term, cohort-based programs (usually 3–6 months) - Target startups with MVPs, early traction, and full-time founding teams - Provide seed funding in exchange for 5–10% equity - Intense pace: weekly check-ins, mentorship, and strict growth milestones - Access to a vetted investor network; programs often culminate in a Demo Day - Designed to help startups scale quickly and raise follow-on funding - Ideal for VC-track startups aiming for fast, venture-scale growth #### Incubators - Flexible, often open-ended programs with rolling admissions - Support idea-stage startups or solo founders validating concepts - Rarely provide funding upfront; some offer office space or stipends - May take equity, but less common or negotiable depending on the model - Focus on foundational work: product validation, team building, early prototypes - No pressure to scale immediately or pitch on Demo Day - More exploratory in nature. Ideal for very early-stage or technical R&D-driven startups ### 10 best accelerators for tech startups globally #### 1. [Y Combinator (YC)](https://www.ycombinator.com/) Y Combinator is often credited with pioneering the modern startup accelerator model. Founded in 2005 in Silicon Valley, YC has since funded over 3,500 startups and boasts a community of 9,000+ founders. Its portfolio’s combined valuation exceeds $1 trillion, a testament to YC’s impact. The program runs two 3-month batches per year in California, where startups receive intensive mentorship and $500k in seed funding. - **Location**: Mountain View, California (HQ). Program in Silicon Valley, with a global applicant pool. - **Industry focus:** Broad and sector-agnostic. Accepts startups across any industry or technology. - **Notable startups:** Airbnb (home rental marketplace), Dropbox (cloud storage), Stripe (online payments), Instacart (on-demand grocery). ![Y Combinator](/images/blog/startup-accelerator-guide/ycombinator.png) #### 2. [Techstars](https://www.techstars.com/) Techstars is a globally networked accelerator founded in 2006, known for its **“Give First”** mentorship culture and worldwide footprint. It operates **47+ accelerators across 15+ countries**, often in partnership with corporations or local startup hubs. Over **3,500** companies have been through Techstars programs, which provide $120k seed funding (plus an optional $100k convertible note) and a 3-month mentorship-driven curriculum. - **Location:** Headquartered in Boulder, Colorado (USA). Major hubs in cities worldwide (New York, London, Berlin, Tel Aviv, etc.) - **Industry focus:** Broad technology focus, with programs both general and industry-specific (e.g., fintech, IoT), covering most tech sectors - **Notable startups:** ClassPass (fitness class platform), DigitalOcean (cloud hosting), SendGrid (email infrastructure), and PillPack (an online pharmacy). ![Techstars](/images/blog/startup-accelerator-guide/techstars.png) #### 3. [500 Startups (500 Global)](https://500.co/) 500 Startups, rebranded as 500 Global, is a truly global seed accelerator and venture fund. Since its launch in 2010, 500 has invested in over **2,600** companies across **75+ countries**, leveraging a team spread in 27 countries. Its 4-month accelerator program in San Francisco provides $150,000 for 6% equity, focusing heavily on growth marketing and investor access. A unique aspect is 500’s breadth: it runs multiple micro-funds and programs targeting different regions and themes yet remains broadly inclusive of all sectors. - **Location:** San Francisco, California (HQ). Programs and investments span North America, Asia, Europe, Latin America, and the Middle East - **Industry focus:** Sector-agnostic. ****500 Startups backs companies in e-commerce, fintech, SaaS, healthcare, education, and more - **Notable startups:** Grab (ride-hailing, Southeast Asia), Canva (design platform, Australia), Credit ****Karma (personal finance, USA) ![500](/images/blog/startup-accelerator-guide/500.png) #### 4. [Plug and Play Tech Center](https://www.plugandplaytechcenter.com/) Plug and Play is an accelerator and innovation platform known for its vast corporate network and unique **zero-equity** model. Based in Silicon Valley but with programs on multiple continents, Plug and Play connects startups with over 500 corporate partners to facilitate pilots, investments, and acquisitions. It runs many industry-specific batches (fintech, health, mobility, etc.), yet as an organization, it spans nearly every sector. Unlike many accelerators, Plug and Play usually does not take equity from startups in its programs. Instead, its business model is to invest selectively and to charge corporate partners. - **Location:** Sunnyvale, California (HQ). International offices and innovation hubs in Paris, Amsterdam, Stuttgart, Beijing, Singapore**,** and more. - **Industry focus:** Multi-industry, implemented via vertical programs. Plug and Play runs accelerators in fintech, insurtech, health tech, mobility, IoT, etc., covering a wide range of innovative fields - **Notable startups:** Dropbox (file sharing), PayPal (online payments), N26 (digital banking) ![Plug and play](/images/blog/startup-accelerator-guide/plugandplay.png) #### 5. [MassChallenge](https://masschallenge.org/) MassChallenge stands out as a non-profit, equity-free accelerator that supports high-impact startups across industries. Founded in 2010 in Boston, it has expanded to a global presence (with programs in Boston, London, Tel Aviv, Mexico City, Lausanne, etc.). MassChallenge takes no equity from startups; instead, it provides mentorship, training, and network access during a 4-month program and then awards cash prizes (up to $1M) to top startups at the culmination. - **Location:** Boston, Massachusetts (HQ). Major hubs in Austin, London, Lausanne, Jerusalem, Mexico City, and more, with a presence on several continents. - **Industry focus:** Broad/Multi-sector. Startups in tech, biotech, social impact, energy, education, and beyond are all part of MassChallenge’s portfolio. - **Notable startups:** Ginkgo Bioworks (bioengineering, now a public company), Flywire (fintech, went public in 2021), PillPack (online pharmacy acquired by Amazon) ![MassChallenge](/images/blog/startup-accelerator-guide/masschallenge.png) {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} #### 6. [Startupbootcamp](https://www.startupbootcamp.org/) Startupbootcamp (SBC) is one of Europe’s largest accelerator networks, offering intensive 3-month programs in cities worldwide. Founded in 2010 in Copenhagen, it grew a network of industry-focused accelerators across Europe, Asia, and beyond. Each Startupbootcamp cohort centers on a specific industry (fintech, smart cities, IoT, food tech, etc.), allowing tailored mentorship and corporate partner access in that domain. - **Location:** Originally founded in Copenhagen and Amsterdam. Today, Startupbootcamp has hubs in London, Amsterdam, Singapore, Dubai, Melbourne, and Mexico City, among others. - **Industry focus:** Multi-industry via vertical tracks – e.g., separate accelerators for FinTech, InsurTech, HealthTech, Energy, Commerce, etc., making the overall organization broad. - **Notable startups:** Talkdesk (cloud call center software, valued >$3B), Relayr (IoT, acquired by Munich Re), and Kontact.io (Industrial IoT) ![Startupbootcamp](/images/blog/startup-accelerator-guide/startupbootcamp.png) #### 7. [AngelPad](https://angelpad.com/) AngelPad is a boutique accelerator that, despite its small size, consistently ranks at the top for its outcomes. Founded in 2010 by ex-Google executive Thomas Korte, it runs in New York City and San Francisco with only about 15 startups per batch – enabling very hands-on mentorship. Uniquely, AngelPad was rated the ****#1 U.S. accelerator ****in MIT’s Seed Accelerator Rankings for every year from 2015 onward. The program emphasizes product-market fit and fundraising preparation, offering $120k for 7% equity. - **Location:** San Francisco and New York City (dual hubs in the USA) - **Industry focus:** Broad tech focus. ****No specific vertical specialization, AngelPad companies range from SaaS and marketplaces to hardware and biotech, united by strong founding teams. - **Notable startups:** Postmates (on-demand delivery, valued ~$2B and acquired by Uber), Pipedrive (sales CRM, acquired at ~$1.5B valuation) and Vungle (mobile ad tech, acquired for $750M) ![AngelPad](/images/blog/startup-accelerator-guide/angelpad.png) #### 8. [Seedcamp](https://seedcamp.com/) Seedcamp is Europe’s leading seed-stage accelerator fund, often described as the “Y Combinator of Europe.” Launched in 2007 in London, it was one of the first accelerators outside the U.S. and has been instrumental in the rise of the European tech ecosystem. Seedcamp initially ran classic accelerator cohorts and has evolved into a combination of pre-seed funds and a continuous program. It invests roughly £300–500k for a small equity stake in startups and provides lifelong mentorship and network access. - **Location:** London, United Kingdom (HQ). While based in London, it attracts startups from across Europe and beyond. - **Industry Ffcus:** Tech-centric but not single-sector. Seedcamp backs companies in fintech, SaaS, healthtech, marketplaces, etc. - **Notable startups:** TransferWise (now Wise) and Revolut. ![Seedcamp](/images/blog/startup-accelerator-guide/seedcamp.png) #### 9. [Entrepreneur First (EF)](https://www.joinef.com/) Entrepreneur First is a unique kind of accelerator – a “talent investor” that builds startups from scratch by bringing individual founders together. Founded in 2011 in London, EF has since spread to Asia and North America, running programs in London, Paris, Bangalore, and Toronto (and recently launched in New York). EF’s model: select talented engineers and professionals before they have a startup, help them find co-founders and develop ideas, and then invest in the newly formed companies. - **Location:** HQ in London, UK. Programs currently in London, Paris, Bangalore, and New York, with alumni communities worldwide (previously also run in Singapore, Berlin, etc) - **Industry focus:** Broad (Frontier tech emphasis)**.** EF is sector-agnostic but leans toward cutting-edge technology startups – e.g., AI, biotech, fintech, etc. – driven by its technical founder pool. - **Notable startups:** Tractable (AI for insurance claims, EF’s first unicorn) and Magic Pony Technology (AI video processing, acquired by Twitter), Cleo (AI fintech app) and Sonantic (AI voice tech, acquired by Spotify). ![Entrepreneur First](/images/blog/startup-accelerator-guide/entrepreneurfirst.png) #### 10. [Start-Up Chile](https://startupchile.org/) Start-Up Chile is a groundbreaking accelerator program launched by the Chilean government in 2010 – widely recognized as the first government-backed accelerator of its kind ****in the world. Its mission is to transform Chile into a Latin American innovation hub by attracting entrepreneurs globally. The program offers equity-free grants (initially ~$40k) and a 6+ month residency in Santiago, Chile, along with a range of resources (visa support, co-working space, mentorship). Over 1,600 startups from 85 countries have been through Start-Up Chile, and it has inspired similar government accelerators in dozens of other nations. - **Location:** Santiago, Chile (program location and HQ). International founders relocate to Santiago during the program, creating a diverse, global cohort in Chile. - **Industry focus:** Sector-agnostic. Any innovative startup at an early stage can apply, from software to social enterprises to biotech. - **Notable startups:** NotCo (Chile’s AI-driven plant-based food unicorn). Simpliroute (logistics optimization SaaS) and Lab4U (ed-tech science labs). ![Startup Chile](/images/blog/startup-accelerator-guide/startupchile.png) ### Conclusion Whether you’re looking for funding, mentorship, a network, or just structure and speed, the right accelerator can help you compress years of learning into a few intense months. But, like any strategic decision, it comes down to fit. Know your goals, understand what’s on the table, and choose a program that matches your ambition. No matter which accelerator you choose, the Appwrite Startup Program is here to support you as you build. Use Appwrite, an all-in-one development platform to build, deploy, and even host your application all from a single platform. Plus, you get discounts on paid plans and priority support from our team. [Apply now](https://appwrite.io/startups) --- ## What is an Incubator? A guide for tech startups https://appwrite.io/blog/post/startup-incubator-guide Building a tech startup is hard, especially for first-time founders. You're expected to move fast, ship a great product, find early users, and raise money. All at once, with limited resources and almost no room for error. That's where startup incubators come in. Designed to make those first 6–12 months less chaotic and more productive. Whether you're a solo technical founder or a team still figuring things out, a good incubator can be your multiplier. In this guide, we will learn about incubators, their benefits, and what to look for in an Incubator. ### What is a startup incubator? Startup Incubators are specialized organizations designed to support early-stage startups by providing them with the tools, guidance, and environment they need to get off the ground. Think of them as a launchpad that can help your startup go from initial idea or prototype to a functional business. They typically offer a mix of: - **Mentorship** → Guidance from experienced entrepreneurs, investors, and domain experts who've built and scaled startups. - **Resources →** Access to office space, developer credits, product tools, and legal or accounting support to reduce operational overhead. - **Networking →** Connections with investors, fellow founders, alumni, and other key players in the startup ecosystem. - **Workshops and sessions →** Sessions on building an MVP, fundraising, GTM strategy, hiring, pitching, and more. - **Time-bound programs →** Time-bound formats (typically 3 to 12 months) with regular check-ins, progress tracking, and demo days to showcase your startup. ### How do they work? Startup incubators support companies in the earliest stages, often before there's a product, revenue, or even a finalized business model. Their goal is to help founders go from a concept to a structured, validated business. Here’s how Startup Incubators work: #### 1. Selection Incubators typically run application cycles where founders pitch their [startup ideas](/blog/post/startups-ideas-for-developers-2024). The selection process looks for founder-market fit, technical potential, clarity of vision, and the problem being solved. Some programs are open to solo founders, while others prefer early teams. #### 2. Pre-Incubation In this phase, the incubator helps founders sharpen their core assumptions, define the product vision, understand the market, and build a business plan. For tech startups, this often includes guidance on choosing the right stack, validating technical feasibility, mapping user personas, and leveraging cloud credits or dev tool support to set up the foundation efficiently. #### 3. Incubation Now it's time to build. You start developing your [MVP](/blog/post/how-can-you-rapidly-build-an-mvp-for-your-startup), setting up early marketing, talking to users, and preparing for growth. You may attend workshops, peer feedback sessions, and 1:1s with mentors. Incubators also help with early hiring plans, GTM strategy, and investor readiness. #### 4. Post-Incubation Once the program ends, many incubators offer ongoing support through alumni networks, investor connections, or optional follow-on programs. Some provide co-investment opportunities or introductions to accelerators and VCs. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="/startups" /%} ### Benefits of a startup incubator Startup Incubators can add value to an early-stage startup in many shapes and forms. Here are some top benefits of being part of an Incubator: - **Office space:** Many incubators provide access to shared office spaces, which can help small companies save on rent and offer unique networking opportunities to other startups. - **Mentorship:** The biggest value in an incubator is in the people, mentors, and partners. You get direct access to founders and mentors who've built successful companies in the past to help you make smart product, hiring, and business decisions. - **Seed funding:** Incubators can assist you by connecting with potential VCs for seed funding that can help you grow and take your startup to the next level. ### Drawbacks of a startup incubator While there are plenty of benefits, there are some drawbacks to consider. Here are a couple of them: - **Equity trade:** Many incubators trade for equity in exchange for access to resources and expertise they offer, which may not make sense if you're already well-resourced or further along in your startup journey. - **Time commitment:** Founders are required to commit to the program, which can run over several months with multiple sessions, check-ins, or deliverables. Yes, you will learn a lot, but you'll also spend a fair amount of time doing it. - **Fit and alignment:** There's no one-size-fits-all incubator. Many are built for specific industries, markets, or stages. You can waste your time, equity, or money by joining the wrong incubator. ### 10 best incubators for startups globally #### 1. [Station F](https://stationf.co/) Widely regarded as the world's largest startup incubator campus, hosting over 1,000 startups and 30 different programs under one roof. Station F offers everything from coworking space and on-site housing to 600+ annual events and workshops, providing a comprehensive ecosystem for international early-stage ventures. **Location**: Paris, France **Industries:** Broad tech focus (from general digital tech to climate tech) **Top startups incubated:** Hugging Face, Alan ![Station F](/images/blog/startup-incubator-guide/stationf.png) #### 2. [Plug and Play Tech Center](https://www.plugandplaytechcenter.com/) An early Silicon Valley incubator that famously made seed investments in Google and PayPal, Plug & Play now runs a no-equity "open innovation" platform connecting startups to hundreds of corporate partners worldwide. It offers continuous mentorship, coworking space, and business development programs without the typical demo-day accelerator format. **Location**: Sunnyvale, USA – with 35+ global offices **Industries:** Runs vertical programs in AI, IT, SaaS, retail, energy, fintech, insurtech, health, mobility, and more **Top startups incubated:** PayPal, Dropbox ![Plug and Play](/images/blog/startup-incubator-guide/plugandplay.png) #### 3. [DMZ](https://dmz.torontomu.ca/) DMZ is a tech incubator that was ranked the [#1 university-based incubator](https://www.notion.so/What-is-an-incubator-A-guide-for-tech-startups-210fb53fe82d804b8227dc53f88ccca3?pvs=21) in the world (tied with SETsquared) in 2018. It focuses on helping pre-seed and early-stage startups through 18-month programs with mentorship and resources, and its companies have raised over $400 M and created 3,000+ jobs since inception **Location**: Toronto, Ontario, Canada **Industries:** AI, fintech, edtech, prop tech, health tech, and other digital domains **Top startups incubated:** Mejuri, Borrowell, Ada ![DMZ](/images/blog/startup-incubator-guide/dmz.png) #### 4. [Idealab](https://www.idealab.com/) Idealab is the longest-running tech incubator and is often credited as one of the first of its kind. It follows a studio model of generating and testing ideas in-house and has launched 150+ companies with over 45 successful IPOs/acquisitions to date. Idealab's enduring track record and its focus on big problem-solving ideas make it uniquely influential in the startup world. **Location**: Pasadena, California, USA **Industries:** Focus on technology and innovation. Historically web & software, but also clean energy and other science-driven ventures **Top startups incubated:** Pica, [GoTo.com](http://goto.com/) ![Idealab](/images/blog/startup-incubator-guide/idealab.png) #### 5. [1871](https://1871.com/) A nonprofit incubator named after Chicago's rebirth after the 1871 fire, it has been ranked the [world's #1 business incubator](https://1871.com/about/#:~:text=These%20values%20make%20up%20the,Most%20Promising%20Women%20Founders%20Incubator) by UBI Global. Located in a 150,000 sq ft coworking space, 1871 supports early-stage companies with mentorship, workshops, investor connections, and specialized programs (e.g., for women founders), serving as the tech startup nexus of the US Midwest. **Location**: Chicago, Illinois, USA **Industries:** Broadly tech (sector-agnostic digital startups), with an emphasis on supporting Chicago's tech ecosystem (e.g., B2B software, fintech, health, etc.) **Top startups incubated:** Cameo, SpotHero ![1871](/images/blog/startup-incubator-guide/1871.png) {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="/startups" /%} #### 6. [MaRS Discovery District](https://www.marsdd.com/) MaRS is North America's largest urban innovation hub, supporting 1,200+ science and technology startups under one roof. Housed in a 1.5 million sq ft downtown campus, this incubator provides long-term venture support (in cleantech, health, fintech, etc.) with lab facilities, mentorship, and corporate partnerships. Its portfolio has raised over $19 B in capital and employs 33,000 people. **Location**: Toronto, Ontario, Canada **Industries:** Mix of health tech, cleantech, fintech, and enterprise software **Top startups incubated:** Wattpad, Kobo, Wealthsimple ![mars](/images/blog/startup-incubator-guide/mars.png) #### 7. [Captial Factory](https://capitalfactory.com/) Often called the "center of gravity for entrepreneurs in Texas," Capital Factory runs a rolling incubator where startups get 6+ months of coworking space and mentorship in exchange for just 1% equity. It hosts hackathons, investor meetings, an accelerator (optional), and a vast mentor network. As the hub of Texas's startup scene, it's also the region's most active early-stage investor and a magnet for international founders looking to enter the US market. **Location**: Austin, Texas, USA **Industries:** Generally tech startups with an emphasis on SaaS, enterprise software, AI, and consumer apps **Top startups incubated:** SpareFoot, Favor ![Capital Factory](/images/blog/startup-incubator-guide/capitalfactory.png) #### 8. [Antler Residency](https://www.antler.co/residency) Antler is a global early-stage startup incubator and venture capital firm, renowned for its intensive Residency program that helps founders build companies from scratch. Founded in 2017 (in Singapore) by experienced entrepreneurs, Antler’s mission is to “enable and invest in the world’s most exceptional people to build the businesses of tomorrow”. Antler brings together talented individuals (often pre-team or even pre-idea) into cohort programs typically lasting about 3–6 months. During this period, founders are matched with co-founders, develop a viable business idea, and rapidly validate it through market feedback. **Location**: Singapore and Ho Chi Minh City, Vietnam **Industries:** Fintech, Healthtech, and Proptech **Top startups incubated:** Sampingan, Xailient ![Antler Residency](/images/blog/startup-incubator-guide/antler.png) #### 9. [SETsquared Partnership](https://www.setsquared.co.uk/) A collaboration of five universities (Bath, Bristol, Exeter, Southampton, Surrey), SETsquared has been ranked the [world's #1 university business incubator](https://www.setsquared.co.uk/programme/global-no-1-homepage/#:~:text=In%20November%202019%2C%20we%20were,third%20time%20in%20a%20row) three times (most recently in 2019). Since 2002, it has incubated over 5,000 companies that collectively raised more than £2.7 B. Its incubators offer multi-year support, industry mentoring, and investor access without a fixed program timeline, welcoming international founders across all tech sectors. **Location**: UK **Industries:** Technology, healthcare, clean energy, AI, and advanced engineering. **Top startups incubated:** Brightpearl, Zynstra ![SETsquared Partnership](/images/blog/startup-incubator-guide/setsquared.png) #### 10. [CodeBase](https://thisiscodebase.com/) CodeBase is the largest technology incubator in the UK and one of the fastest-growing in Europe. It has supported some of Scotland's most successful tech startups and is central to the country's national Techscaler program, aimed at scaling Scottish startups globally. It's a non-equity, founder-first incubator focused on supporting early to growth-stage tech startups through mentorship, education, infrastructure, and access to Scotland's national tech ecosystem. **Location**: Edinburgh, UK **Industries:** Primarily technology startups (software, digital, and IT) **Top startups incubated:** FanDuel, Skyscanner ![CodeBase](/images/blog/startup-incubator-guide/codebase.png) ### What should you look for in an incubator? When evaluating startup incubators, it's important to ask the right questions to ensure the program aligns with your business needs. Here are key questions to guide your decision: #### 1. What perks and resources does the incubator offer? - Do they provide legal, marketing, or funding support? - Do mentors have relevant industry experience? #### 2. Does the curriculum match your needs? - Is the training aligned with your business goals? - Can you manage it alongside daily operations? #### 3. How strong is the incubator's track record? - Have past startups succeeded? - Can you speak to alumni for honest feedback? #### 4. What does it cost to join? - Are there fees for space or services? - How much equity do they take, if any? - Is the value worth the trade-off? #### 5. Is the location convenient? - Is frequent in-person attendance required? - Would relocation be necessary? ### Conclusion Joining a startup incubator can be a great decision, but only if it aligns with your startup's unique needs, goals, and stage of growth. The right incubator can accelerate and take your startup to the next level. But the wrong one can cost you time, equity, and focus. Did you know Appwrite has a special program for startups? You can leverage Appwrite's all-in-one cloud platform to build your products. Plus, you will receive Cloud credits and a discount for Appwrite's paid plans. [Apply now](/startups) --- ## How can you rapidly build an MVP for your startup? https://appwrite.io/blog/post/startup-mvp-guide Nearly [70% of startups fail](https://www.embroker.com/blog/startup-statistics/) within 2-5 years of starting operations, and while many factors contribute to this, one key factor is developing a product that doesn't align with customer needs. Founders often invest all their time, energy, and resources into solving a problem that might not be as important to their audience as they initially thought. But what if you can avoid this and build something that people care about? And that's exactly what MVP is meant for. In this blog, we'll explain what exactly an MVP is, why it matters, and the fastest path to build an MVP for your startup. ### What is an MVP? A Minimum Viable Product (MVP) is the quickest, simplest version of your product that solves a key problem for your target audience. Think of it as a first draft of your product, a way to get your product into the hands of real users as soon as possible, get real feedback, and improve it based on their needs. #### Key things to consider While there are many factors to consider when building an MVP, the key is to focus on things that help you maximize learning and minimize risk as much as possible. So here are two important things to consider: ##### Speed drives quick feedback Startups don't have the luxury of time. You ship, you break things, you fix them fast. If you're moving slowly, you're already behind. Instead of spending months on research, surveys, and perfecting the product, get your MVP into the hands of real users as quickly as possible. The sooner you do, the sooner you can learn what works and what doesn't. That's exactly what UNDO, a software company part of Appwrite's Startup Program, did. After discovering product-market fit, Jonas, co-founder of UNDO, rapidly started developing the solution. As a solo developer, he needed a fast, scalable solution. That's when he turned to Appwrite, leveraging its backend-as-a-service platform to speed up development and scale seamlessly. [Read their story](https://appwrite.io/blog/post/customer-stories-undo) ##### Focus on early adopters Early adopters are key to your MVP's success. These are the users with their hair on fire, which means they have a pressing real problem and are even willing to use an imperfect product to solve it. So, by focusing on the early adopters, you can ensure that your MVP evolves with the users' needs while also building a loyal customer base from the start. ### Practical tips for building an MVP quickly Now that you know what an MVP is and the key things you should consider, let's cover some practical tips that help you build an MVP quickly: #### Set a clear deadline You don't have to spend too much time overthinking and iterating endlessly. Instead, give yourself a specific timeline (e.g., 2 weeks to 1 month) to build an MVP. #### Write down a spec Clearly define the features your MVP must have to avoid getting stuck in constant feature debates. Your MVP should have just enough to solve a specific problem. Don't worry about extras. #### Cut back on features Review your spec and remove anything that isn't absolutely necessary to solve the core customer problem. Ask yourself, "Does this feature solve an urgent problem for the user?" If not, remove it. #### Don't fall in love with your MVP The MVP is just a starting point, and it will change over time. So don't fall in love with your MVP. Fall in love with your customers and their pain points. That's how you win in the long-term. ### Common pitfalls to avoid #### Overcomplicating your MVP As already mentioned, during MVP phase you don't have to build a product that does everything. Don't overcomplicate your MVP, just focus on solving a core problem. This can help you avoid delays, wasted resources, and an unfocused product. For instance, adding advanced analytics, customizable dashboards, or integrations with multiple third-party services can slow you down when you should actually be focusing on solving the core problem. #### The fear of rejection Founders often fear that if users don't like the MVP, it will ruin their business. But negative feedback is valuable. It shows where improvements are needed. And probably, your startup doesn't die just because one customer doesn't like the product. #### Underestimating scalability needs Founders often build an MVP without considering future scalability, resulting in products that cannot scale as user demand grows. Even in the MVP phase, think about scalability. Build a flexible, scalable infrastructure that can handle future growth. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} ### Examples MVPs that turned into successful products There's no right or wrong way to build an MVP. Many big companies kicked off with MVPs. Some got started with just a short demo video, while others with a lean MVP. However, in all cases, they began with very limited functionality that solved a real problem for a small group of early users. Here are two of the best examples: #### **Dropbox** Did you know Dropbox just started out with a simple demo video? They launched with an MVP, a short demo video of their product to test market demand before investing heavily in product. This strategy allowed them validate their idea and attract early adopters. #### **Stripe** Stripe, now a global payments leader, began with a simple, lean MVP focused on a clean API for accepting payments without integrating full financial infrastructure. With a tight feedback loop and rapid iterations, they refined the product based on real user needs. ### Conclusion Building an MVP can be overwhelming, but it doesn't have to be. With the right mindset and tools, you can focus on solving real problems. By prioritizing speed, engaging early adopters, and iterating quickly based on feedback, you can bring your idea to life efficiently. Just like **UNDO**, which went from idea to first customer in 2-3 months using Appwrite's backend-as-a-service platform: Appwrite's Startups Program can help you do that by providing the tools and support you need to build an MVP quickly and scale it according to your needs. Apply now --- ## 10 startup ideas for developers https://appwrite.io/blog/post/startups-ideas-for-developers-2025 Last updated: June 15, 2025 As a developer, you have a unique advantage when it comes to building online businesses. Your technical skills allow you to create and iterate quickly, but the challenge often lies in coming up with profitable ideas. This article will walk you through effective strategies to brainstorm business ideas and provide examples of successful solo developer projects to inspire you. ### How to find a profitable startup idea #### Identify common problems and pain points One of the most effective ways to come up with a business idea is to identify common problems or pain points that people face. Think about your own experiences or issues you encounter in your daily life and work. If you can solve a problem efficiently, there's a good chance others will find value in your solution. **Tip:** think about the tools you use yourself and learn more about their origin story. ##### Useful tools and platforms to identify common problems and pain points - **Reddit**: Explore discussions across various subreddits to discover the common issues people are facing and the topics that generate the most interest. - **Twitter**: Monitor trending topics and engage with the developer community to gain insights into the challenges your peers are encountering. #### Leverage your skills and interests Focus on areas where you have expertise and passion. Building a business around something you enjoy can keep you motivated and make the process more enjoyable. Consider your hobbies, professional background, and personal interests as potential starting points. **Tip:** join hackathons to work on your skills with other developers. Many great success stories once started at a hackathon. ##### Useful tools and platforms to leverage your skills and interests **Hackathon platforms**: - [Devpost](https://devpost.com/): A platform for finding and participating in hackathons. It also allows you to showcase your projects and connect with other developers. - [Hackerearth](https://www.hackerearth.com/): Offers hackathons, coding challenges, and competitions that allow developers to showcase their skills, learn new technologies, and win prizes. - [Hacktoberfest](https://hacktoberfest.com/): An annual event in October that encourages developers to contribute to open source projects on GitHub, with incentives like T-shirts and other rewards for participants. **Dev tools**: - [GitHub](https://github.com/): A platform for version control and collaboration, allowing you to work on projects with other developers, contribute to open source, and showcase your work. - [Stack Overflow](https://stackoverflow.com/): A Q&A platform for developers to ask questions, share knowledge, and learn from the community. - [JetBrains IntelliJ IDEA](https://www.jetbrains.com/idea/): A comprehensive IDE for Java and other programming languages, offering advanced coding assistance and developer tools. #### Explore market trends Keeping an eye on market trends can help you identify emerging opportunities. You will see countless reasons why tools like [AI video editors](https://www.veed.io/tools/ai-video/ai-video-editor?dofollow=true) are emerging in the market and why these products keep on adding advanced features, such as [screen recorders](https://www.veed.io/tools/screen-recorder?dofollow=true ), subtitle generators, music visualizers, voice dubbers and translators, teleprompters, and [AI avatar](https://www.veed.io/tools/ai-avatar?dofollow=true) generator to their library. Use tools like Google Trends, industry reports, and social media to track what's gaining popularity. By tapping into a growing trend, you can position yourself ahead of the competition. **Tip:** join niche communities in your industry and observe patterns in how people discuss their challenges and solutions—this can reveal valuable insights. ##### Useful tools to explore market trends 1. **Trend analysis tools**: - [Google Trends:](https://trends.google.com/trends/) Analyze the popularity of search terms over time to identify rising trends. - [Exploding Topics](https://explodingtopics.com/): Identify rapidly growing trends in various industries to stay ahead of the curve. 2. **Social media monitoring**: - [Hootsuite](https://www.hootsuite.com/): Track social media mentions, monitor trends, and manage your social media presence. - [Brandwatch](https://www.brandwatch.com/): Advanced social media analytics and monitoring to understand public perception and trends. #### Validate your ideas Before diving into development, validate your ideas with real potential users. Use platforms like Reddit, Twitter, and indie hacker communities to gather feedback. Building a minimal viable product (MVP) can help you test the waters without significant investment. **Tip:** you can use Appwrite’s [open source backend-as-a-service](https://appwrite.io/) to help build your MVP quickly and share it with the world. ##### Useful tools to validate your ideas 1. **Community feedback platforms**: - [Reddit](https://www.reddit.com/): Post in relevant subreddits to gather feedback from specific communities. - [Indie Hackers](https://www.indiehackers.com/): Engage with a community of entrepreneurs and gather feedback on your ideas. 2. **Prototyping and MVP Tools**: - [Figma](https://www.figma.com/): Design and prototype your ideas visually and collaborate with others in real-time. #### Brainstorm with others Collaborating with fellow developers and entrepreneurs can spark creativity and lead to innovative ideas. Join online communities, attend meetups, and engage in discussions with others in your field. Brainstorming sessions can provide fresh perspectives and valuable insights. ##### Useful tools to brainstorm and mind map **Mind mapping tools**: - [MindMeister](https://www.mindmeister.com/): Create mind maps to brainstorm and organize your thoughts visually - [XMind](https://www.xmind.net/): A comprehensive mind mapping tool for idea generation and organization **Community feedback platforms:** - [Reddit](https://www.reddit.com/): Join discussions and brainstorming sessions in relevant subreddits. - [Indie Hackers](https://www.indiehackers.com/): Collaborate with other entrepreneurs and gather diverse perspectives on your ideas. ### 10 profitable startup ideas for solo developers #### 1. SaaS tools SaaS tools allow developers to create subscription-based software solutions that solve common business problems. They are often lightweight, scalable, and generate recurring revenue, which makes them highly profitable. SaaS tools benefit both developers and users by simplifying the user experience while providing a steady revenue stream for creators. **Example:** - [**Plausible Analytics**](https://plausible.io/): Marko Saric built Plausible, a privacy-focused alternative to Google Analytics. By addressing the need for simple, privacy-friendly web analytics, Plausible has gained a strong following and consistent revenue. #### 2. Niche marketplaces Building a niche marketplace allows solo developers to connect specific groups of people with products or services tailored to their needs. These platforms are easy to scale since they provide a community-driven content model, and they benefit creators by allowing consistent engagement and a potential stream of premium users or advertisers. **Example:** - [**CodePen**](https://codepen.io/): Chris Coyier created CodePen, a social development environment for front-end designers and developers. CodePen allows users to share and discover code snippets, fostering a community around web development. #### 3. Developer tools Creating tools for developers can be highly rewarding as the target audience is already familiar with software and its value. These tools are often simple to build and maintain and have strong word-of-mouth potential within the developer community. Developer tools increase efficiency, reduce repetitive tasks, and bring immediate utility, making them essential for both personal use and social sharing. **Example:** - [**Carbon**](https://carbon.now.sh/): Brian Dennis built Carbon, a tool for creating and sharing beautiful images of your source code. Carbon has become a popular tool among developers for showcasing their code on social media and blogs. #### 4. Content management systems (CMS) CMS platforms are the backbone of countless websites, and building one with a unique angle can be incredibly profitable. CMS solutions are relatively easy to start with basic features, and the opportunity to expand with plugins or themes can lead to additional revenue. The main benefit is providing users with a robust yet simple platform to manage content effectively. **Example:** - [**Ghost**](https://ghost.org/): John O'Nolan founded Ghost, an open-source CMS focused on professional publishing. Ghost's simplicity and performance have made it a popular choice for bloggers and online publishers. #### 5. Automation tools Automation tools are always in demand as they help businesses save time and streamline operations. As a solo developer, creating an automation tool is manageable by focusing on specific use cases, and the benefits for users are immense: reduced manual work, fewer errors, and more efficient operations. With automation being key to modern business, such tools also generate subscription-based income. **Example:** - [**Zapier**](https://zapier.com/): Wade Foster and Mike Knoop started Zapier to help users automate workflows by connecting different web applications. Zapier's success lies in its ability to save time and increase productivity for businesses of all sizes. #### 6. E-commerce solutions E-commerce continues to grow, and creating tools to facilitate online sales is a highly profitable niche. Such tools are relatively simple to build, offering custom integrations that target specific user needs. The primary benefit is flexibility, as users can enhance their existing platforms, making it an attractive option for small businesses and developers looking for quick solutions. **Example:** - [**Snipcart**](https://snipcart.com/): François Lanthier Nadeau developed Snipcart, a customizable e-commerce solution that allows developers to add a shopping cart to any website. Snipcart's flexibility and ease of integration have attracted a dedicated user base. #### 7. API services APIs provide the backbone for modern applications, and building specialized API services can yield substantial revenue. As a solo developer, building and maintaining an API service is feasible, and the subscription model provides recurring revenue. Users benefit from improved functionality, faster page loads, and optimized resources, making these services essential for modern web applications. **Example:** - [**Imgix**](https://www.imgix.com/): Chris Zacharias created Imgix, a real-time image processing service. Imgix helps developers and designers optimize and transform images on the fly, improving website performance and user experience. #### 8. Online learning platforms The rise of self-paced learning means online education platforms are in high demand. An online learning platform with high-quality content can bring immense value to users who want to upskill and provides steady revenue through subscriptions or one-time payments for premium courses. **Example:** - [**Egghead.io**](https://egghead.io/): Joel Hooks and John Lindquist launched [Egghead.io](http://egghead.io/), a platform offering bite-sized video tutorials for developers. By focusing on high-quality content and niche topics, [Egghead.io](http://egghead.io/) has built a loyal audience. #### 9. Productivity apps Everyone needs help staying organized, which is why productivity apps remain popular and profitable. Building a productivity tool as a solo developer is achievable by focusing on a single core feature and expanding over time. The primary benefit for users is improved organization and efficiency, while developers can profit through freemium models or paid tiers for additional features. **Example:** - [**Trello**](https://trello.com/): Michael Pryor and Joel Spolsky created Trello, a visual project management tool. Trello's intuitive interface and flexibility have made it a staple for teams and individuals looking to organize their work. #### 10. Community platforms Community platforms enable like-minded individuals to connect, share knowledge, and collaborate. These platforms are often easy to build using open-source frameworks, and their success comes from user-generated content. The benefits are twofold: users gain a valuable network and platform for their ideas, while developers can monetize through ads, sponsorships, or premium memberships. **Example:** - [**DEV Community**](https://dev.to/): Ben Halpern founded DEV Community, a platform for developers to share articles, discuss ideas, and connect with peers. DEV Community's focus on fostering a supportive and inclusive environment has led to its rapid growth. ### Conclusion Coming up with profitable online business ideas as a developer requires creativity, market awareness, and strategic thinking. By identifying problems, leveraging your skills, staying updated with trends, conducting research, and collaborating with others, you can generate ideas with high potential. Start brainstorming, validate your ideas, and take the first step toward building a successful startup. And, if your startup is taking off, don’t miss out — apply to the [Appwrite Startup program](https://appwrite.io/startups) and get a whole year of Pro and priority for free! --- ## The real benefits of startup founder programs beyond credits https://appwrite.io/blog/post/startups-program-benefits Startup founder programs are often marketed around cloud credits or discounts. While these perks are helpful, credits eventually expire and leave founders with the same challenges they started with: deploying applications, managing infrastructure, and scaling reliably. The real value of a founder program lies in the long-term benefits it provides for technical and business founders alike. This article explores what founder programs actually deliver, why deployment should be a central focus, and how developer-first programs like Appwrite go beyond credits to support startups from MVP to scale. #### Why credits are not enough Credits reduce costs for a limited time, but they do not solve the fundamental issues startups face: - How to deploy apps quickly - How to manage web deployment workflows without dedicated DevOps staff - How to scale deployments as users grow - How to build with flexibility and avoid lock-in Once credits run out, teams often find themselves with complex infrastructure and expensive deployment pipelines. {% call_to_action title="Build your startup with Appwrite" description="An all-in-one development platform for you to develop, host, and scale your products." point1="Cloud credits" point2="Priority support" point3="Ship faster" point4="Built-in security and compliance" cta="Apply for the program" url="https://appwrite.io/startups" /%} #### What founders actually need Successful startup programs focus on giving founders tools that provide lasting value, including: - **Predictable infrastructure** that grows with the company - **Simple deployment workflows** for MVPs and updates - **Web deployment support** that scales with user traffic - **Developer APIs** for core backend services - **Security and compliance** built into deployments These benefits help founders build and launch faster, with less risk of wasting time or resources. #### The problem with credits-only programs Credits-only programs create a temporary safety net but rarely help with deploying applications effectively. For example: - A founder may use credits to run cloud servers, but still spend weeks setting up deployment pipelines. - A technical co-founder may save money upfront, but face lock-in when it’s time to migrate. The gap between credits and deployment support is why many startups look for developer-first programs. #### The next generation of founder programs Modern programs are shifting toward a developer-first model that prioritizes: - **Hosting plus deployment**: one-click deployment alongside scalable hosting - **APIs and tools**: prebuilt backend services for faster development - **Community and mentorship**: guidance from peers and experts on deploying effectively - **Stage-specific support**: deployment workflows that evolve from pre-seed MVPs to series B enterprise needs #### How Appwrite delivers value beyond credits Appwrite’s Startup Program is designed to help startups deploy and scale without hidden tradeoffs. It offers: - Discounts on paid plans - Managed hosting with **one-click deployment** - Backend APIs for auth, storage, and databases - Automated **web deployment pipelines** that grow with your app - Open source flexibility to avoid vendor lock-in - Tailored support for every funding stage With Appwrite, founders do not just get credits. They get a full deployment-ready platform that makes building and scaling sustainable. #### Conclusion The real benefits of startup founder programs are not tied to credits but to the tools and support they provide for deploying and scaling applications. Deployment speed, reliability, and flexibility are what ultimately help startups succeed. Appwrite’s Startup Program is built around this principle, giving founders hosting, APIs, and deployment workflows that continue to add value long after credits expire. Apply to [Appwrite’s Startup Program](/startups) today and get the deployment advantage your startup needs. #### FAQ **Q: Why are credits not enough for founders?** Because credits expire and do not simplify deploying applications or managing infrastructure. **Q: What should founders look for in a program?** Founders should look for hosting, APIs, and deployment workflows that scale with their company. **Q: How does Appwrite support web deployment?** Appwrite provides one-click deployment and automated web deployment pipelines, reducing time to market and helping teams scale smoothly. --- ## State of audio processing https://appwrite.io/blog/post/state-of-audio-processing Audio processing is a rapidly advancing field within artificial intelligence where algorithms are designed to interpret and manipulate audio signals. From the early days of basic sound synthesis to today's voice assistants and automatic music generation, AI in audio processing has undergone significant development. Let's explore the history of this field and the key concepts and innovations that have shaped it. ### A (not so) brief history of AI in audio processing AI in audio processing began in the mid-20th century with work on sound synthesis methods. [Max Mathews at Bell Labs in the 1950s](https://en.wikipedia.org/wiki/Max_Mathews) laid the groundwork for digital audio synthesis. In the late 1970s, the potential of AI in understanding and changing audio started to be explored. Early projects like the ['Talking Typewriter' by Kurzweil and Shrobe in 1978](https://ieeexplore.ieee.org/document/1454357) demonstrated the possibilities of speech synthesis. The 1990s saw the introduction of machine learning techniques like hidden Markov models (HMMs) for speech recognition. [Rabiner's 1989 paper "A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition"](https://ieeexplore.ieee.org/document/18626) was influential. HMMs had limitations, requiring substantial manual work and struggling with complex speech patterns. ### The deep learning revolution A major breakthrough in audio processing came with deep learning in the 2010s. Convolutional neural networks (CNNs) and recurrent neural networks (RNNs), particularly Long Short-Term Memory (LSTM) networks, revolutionized speech recognition. The 2014 paper ["Deep Speech: Scaling up end-to-end speech recognition" by Hannun et al.](https://arxiv.org/abs/1412.5567) showed the power of CNNs in learning from raw audio data, improving speech recognition performance. LSTMs, introduced by [Hochreiter and Schmidhuber in 1997](https://www.bioinf.jku.at/publications/older/2604.pdf), proved effective in handling temporal dependencies in audio. These techniques enhanced speech recognition and enabled audio generation. Models like [WaveNet from DeepMind in 2016](https://arxiv.org/abs/1609.03499) generated lifelike speech and music by learning from large audio datasets. ### Key concepts in modern audio processing #### Fourier transforms and spectral analysis When you have a recording of a sound, it's a mix of many different frequencies (pitches) playing together. Fourier transforms are a way to separate these frequencies so you can see how much of each frequency is present in the sound. This is useful because: - You can see which notes are being played by different instruments - You can remove frequencies you don't want, like noise or certain instruments - You can change the volume of specific frequencies to alter the balance of the sound Here's a simple code example: ```python import numpy as np ### Make a simple sound wave t = np.linspace(0, 1, 500) f = 10 # Frequency in Hz y = np.sin(2*np.pi*f*t) ### Apply Fourier transform Y = np.fft.fft(y) ### Plot the frequency content import matplotlib.pyplot as plt plt.plot(np.abs(Y)) plt.show() ``` This code makes a simple sound wave, applies a Fourier transform to it, and plots the amount of each frequency present in the sound. ![Fourier transform plot](/images/blog/state-of-audio-processing/fourier.png) #### Deep neural networks Deep neural networks (DNNs) are a way to make computers learn to recognize patterns in data, like recognizing different instruments in a piece of music. They work by passing the data through several layers that gradually pick out more and more complex features. For example, to train a DNN to recognize musical instruments: 1. You give it many recordings of each instrument 2. The DNN learns patterns unique to each instrument 3. After training, the DNN can recognize the instrument in new recordings it hasn't heard before Here's a simple DNN in code: ```python import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.Dense(64, activation='relu', input_shape=(500,)), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(train_data, train_labels, epochs=10, batch_size=32) ``` This code makes a simple DNN. It expects 500 input features (which could be the frequency content of a sound clip). It has two layers that process the data, and an output layer that predicts which of 10 possible instruments the sound is. The model is trained on labeled data (recordings where we know what instrument is playing) using the `fit()` function. After training, it can predict the instrument for new recordings. DNNs are useful in audio for things like: - Converting speech to text - Identifying the genre of a piece of music - Detecting emotions in speech - Removing background noise from recordings The ability of DNNs to learn complex patterns makes them very useful for modern audio processing tasks. #### Automatic speech recognition (ASR) ASR systems convert spoken words into written text. They're incredibly useful for dictating messages, giving voice commands, or automatically transcribing meetings or lectures. ASR has improved dramatically thanks to deep learning, which allows these systems to learn patterns from vast amounts of speech data. One of the best ASR systems is DeepSpeech, developed by Mozilla. It uses a deep neural network architecture called a recurrent neural network (RNN), which is particularly good at handling sequential data like speech. DeepSpeech 2, released in 2017, achieved a word error rate of just 5.5% on a standard benchmark, which was a significant milestone. Google's recent Conformer model has pushed the state of the art even further. #### Sound generation and music composition Generative AI models can create new sounds and music by learning patterns from existing audio data. This opens up exciting possibilities for music creation, sound design, and personalized audio content. [WaveNet, developed by DeepMind in 2016](https://arxiv.org/abs/1609.03499), was a breakthrough in audio generation. It can generate realistic speech and musical sounds by predicting the next sample in an audio waveform based on the samples that came before it. [Google's Tacotron 2](https://arxiv.org/abs/1712.05884) built upon WaveNet to create high-quality synthetic speech that's nearly indistinguishable from human speech. For music composition, [OpenAI's MuseNet](https://openai.com/blog/musenet) and [Jukebox](https://arxiv.org/abs/2005.00341) models are important developments. MuseNet can generate 4-minute musical compositions with 10 different instruments, while Jukebox can generate music in the style of popular genres and artists. These models demonstrate the potential for AI to be a powerful tool for musicians and composers. ### Get started with audio processing in Appwrite Appwrite provides a platform to build your own audio processing applications. With our AI function templates and tutorials, you can quickly get started with voice recognition, music generation, and more. {% cards %} {% cards_item href="/docs/products/ai/tutorials/speech-recognition" title="Speech recongition" %} Efficiently convert speech to text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/text-to-speech" title="Text to speech" %} Generate lifelike speech from text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/music-generation" title="Music generation" %} Create unique musical compositions {% /cards_item %} {% /cards %} --- ## State of computer vision https://appwrite.io/blog/post/state-of-computer-vision Computer vision is a field of artificial intelligence where we work towards giving machines a comprehensive understanding of visual data from a variety of sources, a few examples are: Images, Videos, Point Clouds and X-Rays and MRI's from medical devices with the goal of being able to further parse and process this information for downstream tasks From rotating and scaling your images to applying your Snapchat and Instagram filters, the applications of computer vision are many fold! Computer vision technology has revolutionized many industries with it being used in the medical industry to diagnose the onset of cancer, tumors and other life threatening diseases, to the agriculture industry that uses the technology to identify weeds in their vast amount of crops, while the manufacturing sector uses it to identify faults in products before they ever come close to a customer's hands. All these new and exciting uses for the technology has resulted in the young computer vision industry to be predicted to be worth over [$50bn by 2030](https://www.statista.com/outlook/tmo/artificial-intelligence/computer-vision/worldwide). The purpose of this blog post is to give you a good understanding of the fundamentals of computer vision, the most popular tasks and applications of computer vision and how we can leverage Appwrite to build computer vision enabled applications. ### A (not so) brief history of computer vision The bedrock of which most computer vision systems are built upon was discovered from the least likely of places: the humble cat. In 1959, after a series of failures when neurophysiologists David Hubel and Torsten Wiesel were trying to map out how the brain processed images, they found completely by accident a neuron in the cat's brain lit up right when they switched slides. With this revelation they came to the conclusion that the cat and by extension the human brain processes lines and edges before moving into more [complex processing](https://hackernoon.com/a-brief-history-of-computer-vision-and-convolutional-neural-networks-8fe8aacc79f). At the same time the very first image scanner was being developed by Russell Kirsch and his team with the first image being captures of his then three month old son. The two building blocks were now in place, we had a starting point for how to teach machines to understand what they are seeing and a sensor that allowed for them to see for the first time. Jumping a bit forward, in 1966, a group within MIT's famous Project MAC (Project for Mathematics and Computing) led by Seymour Papert set out to solve the computer vision problem in a [couple months during a summer camp](https://dspace.mit.edu/bitstream/handle/1721.1/6125/AIM-100.pdf). Needless to say they were extremely optimistic and unfortunately the project was a failure, however it was still important as it is widely regarded as the birth of the field of computer vision in academic study. Following the summer camp various groups kept on working in the field of computer vision and in 1974 we saw the widespread introduction of OCR (Optical Character Recognition) technology into our lives which allowed machines to read actual text for the first time. 1982 came and saw British neuroscientist David Marr propose that vision is hierarchical and that its main objective is to translate what we saw into a 3D representation inside our heads so we can better understand and [interact with our environments](http://mechanism.ucsd.edu/teaching/f18/David_Marr_Vision_A_Computational_Investigation_into_the_Human_Representation_and_Processing_of_Visual_Information.chapter1.pdf). Just as David Marr was creating his groundbreaking thesis, around 5000 miles away Japanese computer scientist Kunihiko Fukushima had proposed a neural network he called [Neocognitron](https://www.cs.princeton.edu/courses/archive/spr08/cos598B/Readings/Fukushima1980.pdf) , this network is considered the grandfather of all convolutional neural networks (CNNs) and it should be noted that Kunihiko Kukushima also created the ReLU (rectified linear unit) activation function in 1969 which didn't become widely used until 2011 when it was found to be better at training deep neural networks and even today ReLU is the most popular activation function. In 1989 Yann LeCun while working on a system to recognise hand-written ZIP Codes found that creating the algorithms that allowed his neural network to detect them was laborious and time consuming. Instead he introduced a technique called “back propagation”, what this allowed the network to do was after it had calculated the result it would try and figure out how off its prediction was the intended result. It would then propagate that error backwards across the weights to figure out how much each weight contributed to the failure, after it had achieved that it would adjust the weights and try again. This removed the need for a human to try and figure out the optimal weights and instead [allowed the machine to learn from its own mistakes](http://yann.lecun.org/exdb/publis/pdf/lecun-89e.pdf). Yann LeCun would continue to experiment with this approach and later he would introduce LeNet-5. A revolutionary CNN built with 7 levels, it was designed to recognise handwritten numbers on bank cheques and did so with an astonishing [99.05% accuracy](http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf) but was limited to 32x32 pixel images as to process larger amounts of data required more and larger layers within the CNN which was extremely computationally intensive for the time. After LeCun's innovation and a brief renaissance, the use of CNNs in the field of computer vision stagnated. As I had mentioned before, the computational power to run increasingly more complex CNNs just didn't exist. The CV industry as a whole shifted its focus away from them and onto object detection during this time more great innovations were made In 2001, the first real-time facial recognition algorithm was developed by Paul Viola and Michael Jones. As the field continued to evolve so did the data they were working with, the way that data was annotated and tagged was standardized during this time and in 2010 the ImageNet dataset was released, containing over a million images all manually cleaned and tagged by humans giving all researchers an excellent dataset to train their algorithms on. With the introduction of ImageNet also came the introduction of the ImageNet Large Scale Visual Recognition Competition (ILSVRC) which pitted algorithms against each other to see which one had the highest accuracy, for two years the error rate stayed around 26%. Enter, AlexNet. Created in 2012 by researcher Alex Krizhevsky and aided by Ilya Sutskever and Geoffrey Hinton was an industry changing evolution in the computer vision field. Earlier we had discussed that CNN's were too computationally expensive back when LeCun created LeNet, Well these researchers instead of using CPUs used the power of GPUs in order to train the neural networks. This wasn't the first time it was done mind you but it was the first time it had been done with such high accuracy hitting a huge decrease in error rates of 15.3% After AlexNet's groundbreaking unveiling most computer vision research moved towards CNNs being powered by GPUs and a little bit later TPUs however more conventional techniques also still exist for less powerful devices. ##### Convolutions and Kernels In the history section of this page at the very beginning I mentioned that we discovered that the visual processing system our brains have detects more basic features such as lines and edges before moving onto more complex stages. You may be wondering how we can get machines to do the same thing in an efficient manner and the answer is by using convolutions. Convolution is the fundamental operation in almost all of computer vision, so it's crucial to develop an understanding of how convolutions and kernels work. Let's imagine we have a photograph and we want to detect all the edges in this photograph, the first thing we would do is convert the image into numbers which represent how bright each pixel is, sometimes this is referred to as the Y value of an image or the luminance. Do note that even though I am focusing on edges in this example, kernels exist that can detect a wide range of features from diagonals, spirals Next, let's introduce the concept of a “kernel”, these can also be referred to as “filters”; these are small grids (that can be a square or a rectangle) with numbers in it. These numbers are not random but are instead specifically picked to detect a certain feature. For example, a filter we could use to detect edges is the sobel edge detector. ![Image of a kernel](/images/blog/state-of-computer-vision/kernel.png) Now you have both the image as luminance values and this kernel perform the following steps: You will overlay the kernel over start of the image Next you will times each luminance pixel with the part of the grid it corresponds to in the kernel and then add up the sum of all these numbers Next you will slide this kernel one pixel to the side performing the same operation as the previous step. Each time you perform this operation you are creating a new pixel to create a new image Once you've slid the kernel across the entire image and you have all these new values, you can create a new image from these values. This new image is called the “feature map” and if you use this edge kernel it will highlight all the edges in the image. There are different kernels to compute loads of different features and they aren't always a square, sometimes they are rectangular and they can be as big or small as you want, but keep in mind that bigger kernels result in more computation. Convolutions aren't only used for things like edge detection but are extremely common in the image processing world, for tools you've probably used loads without even thinking about it, such as blurring images, sharpening and embossing. {% video src="/images/blog/state-of-computer-vision/convolution.mp4" /%} ### Applications of Computer Vision Now you have a good understanding of both the history and fundamentals of computer vision, you can dive into our other guides where we provide an even deeper dive into the different types of computer vision and how to implement them in your Appwrite application. {% cards %} {% cards_item href="/docs/products/ai/tutorials/image-classification" title="Image classification" %} Understand and label the contents of images {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/object-detection" title="Object detection" %} Detect and label objects in images {% /cards_item %} {% /cards %} --- ## State of natural language processing https://appwrite.io/blog/post/state-of-natural-language-processing Natural Language Processing (NLP) is a field that combines computer science, artificial intelligence, and linguistics to enable computers to understand, interpret, and generate human language. From chatbots to language translation, NLP powers many of the intelligent features we use every day. In this post, we'll explore the history of NLP and dive into the key concepts behind large language models (LLMs). ### A (not so) brief history of NLP The history of Natural Language Processing (NLP) began in the 1950s with attempts to automate language translation. These early experiments, such as the [Georgetown experiment](https://en.wikipedia.org/wiki/Georgetown-IBM_experiment) in 1954, showed promise but also highlighted the complexity of human language. Fast forward to 1966, and we have [ELIZA](https://web.stanford.edu/class/cs124/p36-weizenabaum.pdf), a program that simulated conversation by matching patterns in user input. ELIZA showed that machines could interact in a way that mimics human conversation, much like modern chatbots. Here's an example of what a chat with ELIZA might look like: ``` User: I'm feeling down today. ELIZA: I'm sorry to hear that. Can you tell me more about what's bothering you? User: I had a fight with my best friend. ELIZA: Fights with close friends can be tough. How did that make you feel? ``` ELIZA's responses aren't perfect, but they demonstrate the basic principle of pattern matching that laid the groundwork for more advanced NLP techniques. By the end of the 20th century, the use of statistical methods began to change NLP. Instead of using fixed rules, these methods allowed computers to learn from data. This was a big step forward and set the stage for the use of machine learning in NLP. The 2000s saw further advancements with algorithms that could learn from vast amounts of data, leading to significant improvements in tasks like language translation and speech recognition. The development of neural networks, especially word embeddings like [Word2Vec](https://arxiv.org/abs/1301.3781), marked another leap forward. These techniques allowed for more nuanced understanding of language by representing words in a space where the distance between words captured their semantic similarity. Picture a simple 2D word embedding space: ``` +------------+ | cat | | dog | | | +-----+------------+-----+ | | | | | | | | | | | | +-----+------------+-----+ | | | car | | bike | +------------+ ``` In this space, similar words like "cat" and "dog" are closer together, while unrelated words like "car" and "bike" are farther apart. This is a simplified view, but it illustrates how word embeddings capture semantic relationships. The introduction of transformer models in 2017, such as BERT and GPT, using [attention mechanisms](https://arxiv.org/abs/1706.03762), was another major milestone. These models could handle long pieces of text more effectively, leading to better performance on a wide range of NLP tasks. ### Traditional NLP models and embeddings Before diving into the intricacies of LLMs, let's take a step back and look at some traditional NLP techniques that paved the way for these advanced models. #### Naive Bayes classifiers Consider building a spam email detector. You could use a [Naive Bayes classifier](https://www.ibm.com/topics/naive-bayes), which learns from examples of spam and non-spam emails to predict whether a new email is spam. Here's a simple analogy: - Common spam words (e.g., "free", "winner", "viagra") are red flags. - Non-spam words (e.g., "meeting", "project", "dinner") indicate a safe email. The Naive Bayes classifier counts red flags, and safe words in an email and makes a prediction. It's a straightforward yet effective approach for many text classification tasks. #### Bag of Words (BoW) and TF-IDF Next up, we have the [Bag of Words (BoW) model](https://www.ibm.com/topics/bag-of-words). The BoW model cares about which words are present and how many times they appear, but it doesn't care about the order. ``` Document: "The quick brown fox jumps over the lazy dog" BoW: {"the": 2, "quick": 1, "brown": 1, "fox": 1, "jumps": 1, "over": 1, "lazy": 1, "dog": 1} ``` [TF-IDF](https://www.ibm.com/topics/bag-of-words#:~:text=polysemous%20words.8-,TF%2DIDF,-With%20standard%20bag) (Term Frequency-Inverse Document Frequency) takes this a step further by considering how important a word is in a document compared to the entire corpus. #### From rule-based to machine learning approaches The shift from rule-based systems to machine learning marked a turning point in NLP. Here are a few key developments: 1. **Long Short-Term Memory (LSTM) Networks**: LSTMs are like the elephants of the neural network world - they have a long memory and can remember important information over long sequences of text. 2. **Transformers and the Attention Mechanism**: Transformers are the superheroes of NLP. They can process text in parallel (like reading multiple books at once) and use attention to focus on the most important parts (like highlighting key passages). 3. **Advanced Embeddings (BERT and GloVe)**: These embeddings are like high-resolution maps of language. They capture fine-grained relationships between words and adapt to different contexts, helping models navigate the complex landscape of human language. ### Large Language Models (LLMs) Now that we've covered the foundations, let's dive into the world of Large Language Models (LLMs). These models are the powerhouses of modern NLP, capable of understanding, generating, and manipulating human language with remarkable accuracy. #### Neural networks At the heart of LLMs are neural networks - computational models inspired by the structure and function of the human brain. Picture a neural network as a team of interconnected workers (neurons) organized into different departments (layers). Each worker processes a piece of information and passes it along to the next department until the final output is produced. Here's a simple example of a neural network that predicts whether a tweet is positive or negative: ``` Input Layer (Tweet text) -> Hidden Layer 1 (Processes text features) -> Hidden Layer 2 (Detects patterns) -> Output Layer (Positive or Negative prediction) ``` #### Tokenization Before a neural network can process text, it needs to break it down into smaller, digestible pieces called tokens. This process is known as tokenization. ``` Sentence: "Appwrite is awesome!" Tokens: ["Appwrite", "is", "awesome", "!"] ``` Advanced tokenizers can even split words into subwords, which helps the model understand the meaning of complex or rare words. #### Word Embeddings Word embeddings are a way to represent words as numerical vectors. The representation captures the meaning and relationships between words, allowing the model to understand language more effectively. Picture a dictionary where each word is represented by a list of numbers: ``` "apple" -> [0.1, 0.5, -0.3, 0.2, ...] "banana" -> [0.2, 0.4, -0.1, 0.3, ...] "car" -> [-0.5, 0.1, 0.8, -0.2, ...] ``` Words with similar meanings (like "apple" and "banana") will have similar vectors, while unrelated words (like "apple" and "car") will have very different vectors. This helps the model understand the nuances of language and perform tasks like sentiment analysis, language translation, and text generation. #### Putting it all together Popular large language models like LLAMA and GPT-4 combine tokenization, word embeddings, and advanced neural network architectures like transformers and self-attention to achieve state-of-the-art performance on a wide range of NLP tasks. Here's a simplified overview of how these models work: 1. **Tokenization**: Break the text into tokens. 2. **Embeddings**: Convert each token into a high-dimensional vector. 3. **Transformer Layers**: Process the embeddings using transformer layers that capture complex relationships between words. Training these layers requires massive amounts of data and computational power. 4. **Output**: Generate predictions, generate text, or perform other NLP tasks. We've explored the fascinating world of Natural Language Processing, from its beginnings to the state-of-the-art large language models that are transforming the way we interact with technology. We've seen how techniques like tokenization, word embeddings, and neural networks come together to create models that can understand, generate, and manipulate human language with remarkable accuracy. ### Applications of NLP At Appwrite, we're really excited about the future of NLP. With our new cloud function runtime and example AI function templates, we're making it easier than ever to build intelligent applications that can understand and communicate with your users in natural language. {% cards %} {% cards_item href="/docs/products/ai/tutorials/text-generation" title="Text generation" %} Generate human-like text {% /cards_item %} {% cards_item href="/docs/products/ai/tutorials/language-translation" title="Language translation" %} Translate text between languages {% /cards_item %} {% /cards %} --- ## Storage previews vs SSR image optimization: when to use which https://appwrite.io/blog/post/storage-previews-vs-ssr-image-optimization Image optimization has become a standard feature in modern web frameworks. From Next.js to Nuxt, frameworks now include built-in tools for manipulating images at the server level. The [recent release of Nuxt Image v2](https://nuxt.com/blog/nuxt-image-v2#server-side-utilities) highlights this trend with new server-side utilities that let you transform images directly in your server endpoints. This raises an important question: if frameworks can now handle image processing, when should you use a service like Appwrite Storage previews instead? Both approaches have their place, but understanding when to use each can save you time, reduce costs, and improve your application's performance. In this post, we'll compare service-based image transformations with SSR image functions, examine their tradeoffs, and help you decide which approach fits your needs. ### What are service-based image transformations? Service-based image transformations handle image processing through a dedicated API, completely separate from your application server. With Appwrite Storage previews, you upload an image once, and the service generates transformed versions on demand using URL parameters. Here's how it works: ```js import { Client, Storage } from 'appwrite' const client = new Client() const storage = new Storage(client) client .setEndpoint('https://.cloud.appwrite.io/v1') .setProject('') // Get a transformed image const result = storage.getFilePreview({ bucketId: 'user-photos', fileId: 'profile-pic.jpg', width: 400, height: 400, gravity: 'center', quality: 90, borderRadius: 200, output: 'webp' }) ``` The original image remains unchanged in storage. When you request a preview with specific transformations, Appwrite processes the image and caches the result. Subsequent requests for the same transformation are served from cache. #### Key characteristics - **Centralized storage**: All images live in one place, accessible from any platform - **Built-in CDN**: Images are distributed globally and cached at the edge - **No server compute**: Your application server doesn't handle image processing - **Framework agnostic**: Works the same way in React, Vue, React Native, Flutter, or any other framework - **Automatic caching**: Transformed images are cached without additional configuration ### What is SSR image optimization? SSR image optimization processes images on your application server at runtime. Frameworks like Nuxt, Next.js, and SvelteKit include utilities that let you manipulate images in server-side code. With Nuxt Image v2's new server utilities, you can transform images directly in your API routes: ```ts // Server endpoint example export default defineEventHandler(() => { const img = useImage() return { url: img('/hero.jpg', { width: 1200, height: 630, fit: 'cover' }) } }) ``` This approach processes images where your application runs, using libraries like Sharp or IPX. The processed images can be cached at the CDN or browser level. #### Key characteristics - **Co-located processing**: Images are processed on the same infrastructure as your app - **Full control**: You can implement custom transformation logic - **Framework integration**: Deep integration with SSR frameworks and type systems - **Compute overhead**: Uses your server resources for image processing - **Setup required**: Need to configure caching and manage dependencies ### How do they compare? Let's look at the key differences between these approaches: #### Infrastructure and maintenance **Service-based** requires minimal setup. You connect to the API, upload images, and request transformations. The service handles processing, caching, and distribution. You don't manage image processing libraries or worry about binary dependencies across different deployment platforms. **SSR functions** require you to manage the image processing pipeline. This means handling Sharp binary installations, configuring caching strategies, and ensuring your server can handle the processing load. The tradeoff is complete control over the transformation logic. #### Performance and scaling **Service-based** offloads all image processing to dedicated infrastructure. Your application server just sends API requests. This means image processing never impacts your application's response time, even during traffic spikes. The service's global CDN ensures fast delivery worldwide. **SSR functions** use your server's CPU and memory to process images. For small-scale applications, this might not matter. But as traffic grows, image processing can consume significant resources. You'll need to scale your infrastructure accordingly or implement caching strategies. #### Flexibility and customization **Service-based** provides a fixed set of transformations. Appwrite Storage offers options like resize, crop, quality adjustment, borders, opacity, rotation, background color, and format conversion. These cover most use cases, but you can't implement custom transformation logic. **SSR functions** give you complete freedom. Need to add watermarks? Apply custom filters? Generate dynamic compositions? You can implement any transformation logic your application needs. This flexibility comes with the responsibility of maintaining that code. #### Cost considerations **Service-based** has predictable costs. Appwrite's [pricing](/pricing) includes 100 origin images per month on Pro and Scale plans, with additional images at $5 per 1,000. You pay for unique images, not transformations. So one image with 20 different variations only counts as one origin image. **SSR functions** cost depends on your infrastructure. You pay for compute time, bandwidth, and storage. Low traffic might mean minimal costs, but high traffic requires more server capacity. The actual cost varies based on your cloud provider and configuration. #### Multi-platform support **Service-based** shines in multi-platform scenarios. Whether you're building a web app, mobile app, or desktop application, the same API works everywhere. Your React Native app, Flutter app, and Next.js site all use the same image URLs with the same transformation parameters. **SSR functions** are web-focused. While your web app benefits from SSR image optimization, your mobile apps need a different solution. This often means implementing separate image handling logic for different platforms. ### When to use service-based transformations Choose Appwrite Storage previews when you need: #### Consistent cross-platform image delivery If you're building for web and mobile, using a service means the same image API works everywhere. Your Flutter app and your React website both fetch images from the same source with the same transformation syntax. #### Minimal infrastructure management When you don't want to manage image processing infrastructure, a service handles everything. No Sharp binaries to worry about, no caching configuration to debug, no scaling concerns for image processing workloads. #### User-generated content at scale For applications where users upload photos, profile pictures, or other media, a dedicated storage service simplifies permissions, transformations, and delivery. Appwrite handles the complexity of secure storage and on-demand transformations. #### Example: Social media application ```js // In your React Native app const ProfilePicture = ({ userId, fileId }) => { const storage = new Storage(client) const avatarUrl = storage.getFilePreview({ bucketId: 'avatars', fileId: fileId, width: 150, height: 150, gravity: 'center', borderRadius: 75, output: 'webp' }) return } // The same API works in your Next.js web app const ProfilePicture = ({ userId, fileId }) => { const storage = new Storage(client) const avatarUrl = storage.getFilePreview({ bucketId: 'avatars', fileId: fileId, width: 150, height: 150, gravity: 'center', borderRadius: 75, output: 'webp' }) return Profile } ``` ### When to use SSR image functions Choose SSR image optimization when you need: #### Custom transformation logic If you need transformations beyond standard resize, crop, and format conversion, SSR functions give you full control. Add watermarks, apply brand-specific filters, or implement complex image compositions. #### Dynamic social preview generation For generating Open Graph images or social media previews with dynamic text and layouts, SSR functions excel. You can use libraries to compose images programmatically in your server endpoints. #### Processing images from multiple sources When your images come from various sources (local files, external URLs, databases), SSR functions can normalize processing. You're not limited to images stored in a specific service. #### Example: Dynamic OG image generation ```ts // Nuxt server endpoint export default defineEventHandler(async (event) => { const { title, author } = getQuery(event) const img = useImage() // Generate dynamic OG image with custom layout // This is simplified - actual implementation would use // something like Satori or canvas-based composition const ogImageUrl = await generateOgImage({ title, author, backgroundImage: img('/og-background.jpg', { width: 1200, height: 630 }) }) return ogImageUrl }) ``` ### Can you use both? Yes, and this hybrid approach often makes the most sense. Use each tool for what it does best. Consider this setup: - **Appwrite Storage previews** for user-generated content (profile pictures, uploaded photos, documents) - **SSR functions** for dynamic, generated images (OG images, certificates, custom graphics) This gives you the benefits of both approaches: ```tsx // Next.js example combining both approaches // User avatar from Appwrite Storage export function UserAvatar({ fileId }: { fileId: string }) { const storage = new Storage(client) const avatarUrl = storage.getFilePreview({ bucketId: 'avatars', fileId, width: 200, height: 200, borderRadius: 100, output: 'webp' }) return User avatar } // Dynamic OG image from SSR function export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await getPost(params.slug) return { openGraph: { images: [ { url: `/api/og?title=${encodeURIComponent(post.title)}&author=${post.author}`, width: 1200, height: 630 } ] } } } ``` The user avatar comes from Appwrite Storage because it's user-uploaded content that needs to be displayed consistently across platforms. The OG image uses an SSR function because it requires custom composition with dynamic text. ### Making the decision Ask yourself these questions: 1. **Are you building for multiple platforms?** - Yes → Service-based handles cross-platform better - No → Either approach works 2. **Do you need custom transformation logic?** - Yes → SSR functions give you full control - No → Service-based covers standard transformations 3. **How much infrastructure do you want to manage?** - Minimal → Service-based requires less maintenance - Full control → SSR functions let you optimize everything 4. **What's your scaling strategy?** - Focus on application logic → Service-based handles image scaling separately - Unified infrastructure → SSR functions scale with your servers 5. **Are images user-generated or static?** - User-generated → Service-based simplifies storage and permissions - Static or generated → Either approach works, depending on other factors ### Final thoughts The trend of frameworks adding image manipulation features is a response to real developer needs. SSR image functions make sense when you're building primarily for web, need custom processing logic, or want complete control over your image pipeline. But service-based transformations still have a strong place in modern web development. They excel at multi-platform delivery, reduce infrastructure complexity, and provide predictable scaling and costs. The question is not which approach is better, it's which approach fits your specific needs. For many applications, using both in a hybrid setup makes the most sense. Let Appwrite Storage handle user-generated content and cross-platform image delivery, while using SSR functions for dynamic, custom-generated images. Start with understanding your requirements: platform coverage, transformation needs, infrastructure preferences, and scaling strategy. Then choose the tool that matches those needs. And remember, you're not locked into one approach. Start with what makes sense now, and adjust as your application grows and your needs change. ### Resources - [Appwrite Storage documentation](/docs/products/storage) - [Image transformations guide](/docs/products/storage/images) - [How to use AVIF in Appwrite Storage](/blog/post/avif-in-storage) - [Image transformation with Appwrite Storage](/blog/post/image-transformation-with-appwrite-storage) --- ## How to setup the SvelteKit starter template on Appwrite Sites https://appwrite.io/blog/post/sveltekit-starter-sites Building web applications requires both front-end expertise and back-end infrastructure. [Appwrite Sites](/products/sites) simplifies this process by providing a platform for deploying, hosting, and scaling web applications. To ease this process even further, Appwrite Sites offers a variety of starter kits for popular frameworks like Next.js, React, Vue, Nuxt, Angular, SvelteKit, and Flutter. In this blog, you will learn how to set up the SvelteKit starter template and deploy it to Appwrite Sites. ### Overview of the starter template [SvelteKit](https://kit.svelte.dev/) is a modern web framework built on top of Svelte that enables fast, server-rendered and client-enhanced web applications with powerful routing, data loading, and deployment features out of the box. Appwrite's SvelteKit starter template includes: - A clean, single-page UI - Integration with Appwrite's SDK - Pre-configured deployment settings for Appwrite Sites' SSR rendering strategy ![Deployed app](/images/blog/sveltekit-starter-sites/deployed.png) ### Deploy the starter template on Appwrite Firstly, you must head to Appwrite Cloud and [create an account](https://cloud.appwrite.io/console/register) if you haven't already (or [self-host Appwrite 1.7](/docs/advanced/self-hosting)). Next, create your first project, which will lead you to the project overview page. ![Add platform](/images/blog/sveltekit-starter-sites/add-platform.png) Head to the **Sites** page from the left sidebar, click on the **Create site** button, and select the **Clone a template** option. This will take you to the Appwrite Sites templates listing, where you should search `Svelte starter` and click on the template. ![Starter template](/images/blog/sveltekit-starter-sites/template.png) After selecting the template, you can choose to connect a GitHub repository now or at a later time. If you choose to connect a repository, ensure you select a production branch (leave the root directory as is). Then, review the preset environment variables, update the domain name if you want, and click on the **Deploy** button. You can watch the deployment logs as the site is built. ![Deployment logs](/images/blog/sveltekit-starter-sites/deployment-logs.png) {% info title="Alternative method to deploy starter template" %} As an alternative to the Appwrite console, you can create and deploy websites using the [Appwrite CLI](/docs/products/sites/deploy-manually#cli). Create your SvelteKit starter using the following shell command and configuration: ```bash appwrite init sites ? What would you like to name your site? Svelte starter ? What ID would you like to have for your site? unique() ? What framework would you like to use? SvelteKit (sveltekit) ? What specification would you like to use? 0.5 CPU, 512MB RAM ``` You can then make any edits to the website and deploy it using the following command: ```bash appwrite push sites ``` {% /info %} ### Test the starter template After your site has been successfully deployed, Appwrite will show you a **Congratulations** page. You can then either choose to view the site by clicking on the **Visit site** button or view the site configuration (deployments, logs, domains, usage, and settings) by clicking on the **Go to dashboard** button. ![Congratulations](/images/blog/sveltekit-starter-sites/congrats.png) ### Next steps And with that, the SvelteKit starter kit is deployed to Appwrite Sites. You can explore other templates or deploy any other websites you'd like. For more information about Appwrite Sites: - [Appwrite Sites product docs](/docs/products/sites) - [Quick start to deploy any SvelteKit app](/docs/products/sites/quick-start/sveltekit) - [Appwrite Discord server](/discord) --- ## Swift 101: how to build a library with Swift Package Manager https://appwrite.io/blog/post/swift-101-build-a-library-with-swift-package-manager The Swift Package Manager, or SwiftPM, has been part of Swift since version 3.0. Initially, it was just for server-side or command-line Swift projects. However, starting with Swift 5 and Xcode 11, SwiftPM now works across the entire Apple ecosystem for building apps. This is great because packages let you organize your code into reusable, logical groups that you can easily share between projects or even with the world. #### Modules Before looking at **packages**, we first need to understand **modules**. Swift organizes code into modules. Each module defines a namespace and which parts of the code can be used from outside the module. You can define all of your code in a single module or break it up into multiple modules that can depend on each other. Using modules lets you easily build on your own reusable code or others’ code. #### Packages So, what is a Swift package? A package is a collection of Swift source code files as well as a manifest file called `Package.swift`, that defines various properties about the package, such as its name, the products it produces, any dependencies it has, and the targets it is built up of. #### Anatomy of a package - **Products** define the libraries and executables produced by a package. A library is simply a collection of files for use as a dependency by other Swift code. An executable is a package that can be run, such as a web server. - **Dependencies** are other Swift Packages you want to use code from within your package. - **Targets** define the modules within a package. Each target specifies the code that makes up the module, and any dependencies. The dependencies can be other targets within the same package, or products from external packages. #### Creating a Swift package > This tutorial assumes you already have Swift installed. You can check by running swift-help from your terminal. > Creating a Swift Package from the command line is easy and can be completed with one simple command from the directory in which you want to create your package. For this example, we'll start with a directory named `FooPackage`. ``` $ mkdir FooPackage $ cd FooPackage FooPackage$ swift package init --type=library ``` That’s it! There’ll be some output detailing the files created for your new package. You should see: - 1 source file created inside a `Sources` directory - 1 test source file inside a `Tests` directory - A `Package.swift` manifest file at the root level - A README.md file at the root level - A .gitignore file at the root level Of these files, only the single file in the `Sources` directory and the manifest file are required for the package to build. This means you could easily create your own package by manually creating these two files as well. By default, the `Sources` directory must contain all source code for the package, but you can use sub-directories to define sub-modules if they are also defined as separate targets in your manifest file. Let's take a look at the generated `Package.swift` for the new package to see the pieces we've discussed so far: ```swift // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "TestPackage", products: [ .library( name: "FooPackage", targets: ["FooPackage"]), ], dependencies: [ ], targets: [ .target( name: "FooPackage", dependencies: [ ] ) .testTarget( name: "FooPackageTests", dependencies: [ "FooPackage" ] ) ] ) ``` Here, you can see that our package defines one **library**, `TestPackage`, one **target** of the same name, and one test target, which depends on the module target. #### The first build Now that the package has been created let’s build it for the first time with the build command: ``` $ swift build ``` Because the package has no dependencies or code yet, this should be completed almost instantly, displaying “Build Completed!” on success. #### Adding dependencies Let's add a dependency and some code. Adding dependencies with SwiftPM is easy as you can use git URL's directly. We can add the following to our `Package.swift` top-level dependencies block to allow us to the Appwrite Swift SDK in our library: ```swift .package(name: "Appwrite", url: "https://github.com/appwrite/sdk-for-swift", from: "0.1.0") ``` This declares that our package will pull in the code from the `Appwrite` module in the `sdk-for-swift` repository on GitHub, from the tag `0.1.0` and allow us to add it to our target dependencies as follows: ```swift .target( name: "FooPackage", dependencies: [ "Appwrite" ] ) ``` Here, we added `Appwrite`, as this is the name of the library we're using from the `sdk-for-swift` repository. Let's take a look at the full manifest file with the new dependency added: ```swift // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "FooPackage", products: [ .library( name: "FooPackage", targets: ["FooPackage"]), ], dependencies: [ .package(name: "Appwrite", url: "https://github.com/appwrite/sdk-for-swift", from: "0.1.0") ], targets: [ .target( name: "FooPackage", dependencies: [ "Appwrite" ] ) .testTarget( name: "FooPackageTests", dependencies: [ "FooPackage" ] ) ] ) ``` Since we've changed the dependencies of our package, we need to **resolve** them. This will happen automatically the first time you run `swift build` with a new dependency, but if you manually update a version, you'll need to manually resolve the new version. This can be done by running: ``` $ swift package resolve ``` This will update the `Package.resolved` to contain the version metadata about the `Appwrite` module we just added. > **What's going on here?** > > > Swift Package Manager uses a lockfile system similar to `package.lock` for NPM and `composer.lock` for Composer. This comes in the form of a file called `Package.resolved`, which contains metadata about the packages dependencies versions, as well as their transitive dependencies. When you run `swift build` and the dependencies are fetched, the versions from the `Package.resolved` file will be used if found. > Once resolved, we can build our package with `swift build` again. This time we'll see the `sdk-for-swift` repository pulled into the build checkouts, as well as built with the rest of the library. #### Adding library code Time to add some code. Let's open up the source file created earlier as `Sources/FooPackage/FooPackage.swift` and update with the following: ```swift import Appwrite struct FooPackage { static let client = Client() static let account = Account(client) public static func login( endpoint: String, projectId: String, email: String, password: String, completion: @escaping (Result) -> Void ) { client .setEndpoint(endpoint) .setProject(projectId) account.createSession( email: email, password: password, completion: completion ) } } ``` We now have a login function! We just need to deploy the package, and we'll be able to use the login function from any other package or Apple app. #### Deploying the package Fortunately deploying packages with Swift Package Manager is very easy. As the packages are Git based, all you need to do is push your changes to your default branch and create a tag for your release: ``` $ git init $ git add . $ git remote add origin [GitHub Repository URL] $ git commit -m "Initial Commit" $ git tag 1.0.0 $ git push origin main --tags ``` #### Using as a dependency Using the same method we used earlier to add the Appwrite Apple SDK as a dependency, we can now add the newly deployed package as a dependency of a second package: ```swift ... dependencies: [ .package(name: "FooPackage", url: "https://github.com/[YOUR GITHUB USERNAME]/[YOUR GITHUB REPOSITORY]", from: "1.0.0") ], targets: [ .target( name: "FooPackage", dependencies: [ "FooPackage" ] ) ] ... ) ``` #### Using the dependency With the package added as a dependency, we can now use the function we defined earlier anywhere in the second package: ```swift import FooPackage FooPackage.login( endpoint: "http://localhost/v1", projectId: "6bfgh45fng3", email: "test@test.test", password: "password" ) { result in ... } ``` #### Updating your package The process for updating your package is the same as deploying the initial version. You just need to push your changes to the default branch and add a new version tag. #### That's it! You've now created, deployed, used and updated your very own Swift Package! Packages are a great way to re-use code and share your creations with the world. #### Resources - [Swift](https://swift.org/) - [Swift Package Manager](https://swift.org/package-manager/) - [Creating Swift Packages with Xcode](https://developer.apple.com/documentation/xcode/creating_a_standalone_swift_package_with_xcode) --- ## Announcing TanStack Start support in Appwrite Sites https://appwrite.io/blog/post/tanstack-start-support-in-appwrite-sites The React world has a new favourite, and developers can’t stop talking about it. [TanStack Start](https://tanstack.com/start/latest) is quickly becoming the go-to framework for teams that want simplicity, type safety, control, and performance without the overhead. Built by the same team behind TanStack Query(React Query) and TanStack Router, it’s redefining how React apps are built and shipped: lighter, faster, and closer to how you want to code. And we know it’s not just another hype, when the creator of jQuery, [John Resig](https://x.com/jeresig), who helped shape modern JavaScript, [calls it *A+*](https://x.com/jeresig/status/1982046991132889191). Then you know it’s worth paying attention to. Today, we’re excited to announce that **Appwrite Sites now supports TanStack Start**, bringing **full server-side rendering (SSR)** capabilities directly to Appwrite Cloud. Developers can now build and deploy TanStack Start sites with zero configuration. ### Why TanStack Start is in the spotlight TanStack Start is getting attention because it takes React back to what developers loved about it: simplicity and control. While frameworks like Next.js have grown powerful, they’ve also grown complex. TanStack Start moves in the opposite direction: it feels familiar, predictable, and transparent. Instead of hiding logic behind conventions or “magic,” it gives you explicit control over routing, data loading, and rendering. Everything is type-safe and visible. It blends client-side interactivity with server-side rendering and is powered by Vite for fast builds. In short, it’s React the way it should be, a framework that’s fast, typed, and built for developers who care about how things actually work. ### TanStack Start support in Appwrite Sites With TanStack Start support now live in Appwrite Sites, developers can finally run server-side rendering (SSR) natively on Appwrite Cloud. Before this update, TanStack Start developers often had to deploy their projects as static sites under “other” frameworks, losing the SSR capabilities that make TanStack Start powerful. Others had to look for external platforms to get full functionality, adding unnecessary steps and complexity. Now, everything works right out of the box. You can build, test, and deploy your TanStack Start projects directly on Appwrite, with full server-side support and zero compromises. It’s a smoother experience that keeps all your logic, hosting, and backend under one open-source stack. Exactly how it should be. ### Immediate benefits This update means TanStack Start developers can finally go all-in on Appwrite Sites without giving up flexibility or server power. You get the full experience of TanStack’s SSR model, directly on Appwrite Cloud, with seamless setup and deployment. Here’s what this unlocks: - **Full SSR support:** TanStack Start apps, now running natively on Appwrite Cloud. - **First SSR framework for Solid:** Since TanStack Start also supports Solid, Appwrite now extends SSR hosting to additional JavaScript ecosystems like Solid. or is that redundant - **All-in-one setup:** Host your frontend, backend, and database in one place, without third-party dependencies. - **Open-source foundation:** TanStack Start is built on open, familiar web technologies. Nothing proprietary or platform-specific. Next.js works best on Vercel, where all its features are deeply integrated, but running it elsewhere often adds complexity. TanStack Start takes a different approach. Its open architecture means your apps work the same everywhere The best part? **TanStack Start and Appwrite fit together naturally.** TanStack covers both the frontend and backend logic. It just needs a platform to run on. **Appwrite is that platform.** With built-in services like databases, authentication, and APIs, Appwrite gives TanStack everything it needs to run end-to-end. ### Wrapping up Supporting TanStack Start is more than just adding another framework. It’s about giving developers the freedom to build the way they want. Since Appwrite owns its entire stack, we’re able to move fast, adapt quickly, and bring modern frameworks to Appwrite Sites without waiting on anyone else. TanStack Start support is now available on Appwrite Cloud. Whether you’re a React developer looking for a Next.js alternative, a Solid developer exploring SSR, or just someone who wants modern web tooling without platform lock-in, Appwrite Sites with TanStack Start gives you the flexibility, performance, and control to build your way. ### More resources - [Read the documentation to get started](/docs/products/sites/quick-start/tanstack-start) --- ## Announcing the Appwrite Network: Appwrite’s vision for a global cloud infrastructure https://appwrite.io/blog/post/the-appwrite-network We are happy to announce the launch of the Appwrite Network, a network of cloud regions and edge locations (edges) to improve Appwrite Cloud availability, performance, and compliance with local regulations. This will provide Appwrite developers and teams with the best tools and infrastructure to build, deploy, and scale your applications. This brings us closer to the Appwrite mission of making software development more accessible and enjoyable for all developers. Starting today, all Pro users have access to three regions of choice, including our two new regions in **New York City** ('NYC'), and **Sydney** ('SYD') that are joining our first region in **Frankfurt** ('FRA'). In the next few weeks, this will be available to all Cloud users, with more regions to come. ![Overview of all the regions](/images/blog/the-appwrite-network/cloud-regions.png) ### The plan The Appwrite Cloud beta journey began with a single region in Frankfurt, which allowed us to focus on delivering the highest quality and performance on a smaller scale. As we approach the General Availability (GA) of Appwrite Cloud, and with a growing number of developers, projects, and organizations on the platform, we are excited to share the plans to expand the network to include additional regions and edges across the globe. | Location | Status | Region | Edge | Timeline | |---------------------|-----------------------|--------|------|---------------| | Frankfurt (`FRA`) | Ready and operational | Yes | Yes | Available Now | | Sydney (`SYD`) | Ready and operational | Yes | Yes | Available Now | | New York City (`NYC`) | Ready and operational | Yes | Yes | Available Now | | Singapore (`SGP`) | Ready and operational | Yes | Yes | Available Now | | Toronto (`TOR`) | Ready and operational | Yes | Yes | Available Now | | San Francisco (`SFO`) | In work | Yes | Yes | Q4 2025 | | Bangalore (`BLR`) | In plans | Yes | Yes | TBD | | Amsterdam (`AMS`) | In plans | Yes | Yes | TBD | | London (`LON`) | In plans | Yes | Yes | TBD | ### Addressing the latency problem Latency is a critical factor in the performance of both web and mobile applications. High latency can lead to slow response times, poor user experience, and ultimately, loss of revenue. There are several primary causes of latency: - **Physical distance:** The further the data has to travel, the longer it takes. This is known as propagation delay. - **Network congestion:** Overloaded networks can slow down data transmission. - **Routing and switching:** Each hop a packet takes through the network can add delays. - **Server processing time:** The time it takes for a server to process a request and send a response. Establishing a global network of regions and edges can significantly reduce latency. For example, without a global network, a request from a user in Sydney to a server in New York could experience latency upwards of 250 milliseconds. With a global network, routing that same request through an edge in Singapore and a region in Frankfurt can reduce latency to under 100 milliseconds. A global network allows us to distribute data and processing power closer to the end-users, minimizing the distance that data needs to travel and thus reducing the time it takes for requests and responses to be processed. This results in faster load times, improved performance, and a better overall user experience. ### Why build the Appwrite Network? Building a fully fledged network from scratch is not a decision we take lightly. Here are the key reasons behind this approach: **1. Innovation and quality**: By owning the network, we can innovate and optimize in ways that would be impossible with off-the-shelf solutions. This control allows us to deliver a superior developer experience explicitly tailored to your needs. **2. Security and trust**: Maintaining control over the infrastructure to ensure the highest standards of security and privacy for Appwrite developers. This helps us ensure you always own and control your data. **3. Flexibility and customization**: Having a network allows us to be more agile and responsive to the evolving needs of Appwrite developers. We can quickly adapt and introduce new features and improvements without being constrained by third-party limitations. **4. Cost efficiency**: By building and managing the network, we can reduce costs and pass those savings on to Appwrite developers. By maintaining independence, we can ensure predictable costs for compute, and this helps us make Appwrite more affordable for teams of all sizes. **5. It’s fun**: At the core of Appwrite, we are geeks who love technology and building stuff from scratch, so you don’t have to. Building a network is not just a business decision but also a passion. It's an opportunity to push the boundaries of what's possible and share that excitement with the Appwrite community of developers. ### Regions vs Edges vs PoPs ![Regions vs Edges vs PoPs](/images/blog/the-appwrite-network/regions-edges-pops.png) In Appwrite, Regions are where all your core data and services live. This includes your databases, auth, functions, messaging, and storage. Regions are the source of truth, handling heavy workloads and ensuring your application runs reliably while keeping your data compliant with local regulations. Edges are about speed. They process requests closer to your users using smart geo-routing, reducing latency by handling compute tasks at the nearest edge location. Edges are perfect for serving cached content, executing lightweight computations, and optimizing user interactions. Our global CDN leverages strategically positioned Points of Presence (PoPs) to cache and deliver your content from locations nearest to your users, ensuring rapid load times and enhanced performance. In short, a region hosts your data, and the edge executes your functions close to your users. Both regions and edges take advantage of the Appwrite CDN to optimize delivery and security. ### Data storage and global availability One of the key features of the Appwrite Network is the ability to choose where your project's data is stored. You can select your preferred region, ensuring compliance with local data regulations and optimizing performance for your primary user base. However, thanks to the global network of edges, your data will be accessible worldwide on the Appwrite network, ensuring fast and reliable access for users regardless of their location. Each region will also function as an edge for other regions, enhancing network coverage and reducing latency. This interconnected approach ensures that we can deliver the best possible performance and reliability for all Appwrite developers and your end-users. ### Global CDN with integrated DDoS protection All Appwrite’s Cloud projects are served by Appwrite’s built-in CDN. Our new global CDN is designed to serve your content rapidly and reliably worldwide. By leveraging a network of strategically located points of presence (PoPs), the CDN ensures that every user request is delivered from the closest possible location, drastically reducing latency and enhancing the overall user experience. Moreover, the CDN includes integrated DDoS protection that combines standard traffic analysis, rate limiting, and filtering techniques to help mitigate potential threats in real time. This balanced approach helps maintain solid performance while providing a reliable level of security for your applications. ### Web application firewall (WAF) Our [Web Application Firewall](/docs/products/network/waf) (WAF) is now available exclusively to enterprise customers, providing a crucial protective layer for your applications. Operating at OSI Layer 7, the WAF inspects and filters all HTTP/HTTPS traffic in real time, effectively blocking common web vulnerabilities such as SQL injection, XSS, and CSRF, while also mitigating application-level DDoS attacks. Customizable rulesets allow you to tailor the WAF to your application's specific needs, with detailed analysis of request headers, payloads, and query strings to identify and neutralize threats before they reach your infrastructure. Setup and configuration are managed through your dedicated Appwrite success manager, ensuring that the WAF adapts to evolving security challenges and compliance requirements. To learn more about our enterprise plan, you can [contact us](/contact-us/enterprise). ### The road ahead We welcome you to visit the docs to learn more about the [Appwrite Network](/docs/products/network) and how Appwrite handle's other topics like [secure transportation](/docs/products/network/tls), [compression](/docs/products/network/compression), [caching](/docs/products/network/caching) and more. We are excited about the future of the Appwrite network and the endless possibilities it will unlock for developers worldwide. Stay tuned for more updates as we continue to build and expand the network. Thank you for being part of the Appwrite community and for your ongoing support. Together, we are building the future of cloud platforms designed for developers. --- ## Vibe coding guide 2025: Build smarter with the best AI coding assistants https://appwrite.io/blog/post/the-complete-vibe-coding-guide-2025 Something interesting is happening in how people build software. Devs are working alongside AI tools to plan, test, and write code in new ways, and non-devs are turning ideas into real projects without the need for deep technical knowledge. The gap between an idea and what it takes to build it is getting smaller. That's where **vibe coding** comes in. The idea behind it is simple, but getting it right takes practice. You still need solid fundamentals and need to learn how to communicate with these AI tools to get the most out of them. In this **vibe coding guide**, we'll look at what vibe coding actually means, how it differs from traditional coding, some of the best vibe coding tools, and best practices. ### Quick overview of vibe coding Vibe coding is using tools like Cursor or Lovable to generate software applications by basic prompting. Basically you guide the AI tool with context, give it clear goals, and refine its output until it fits your intent. A simple way to think about it is to imagine working with a junior developer. If you give vague instructions, you'll get vague results. But if you share a clear brief, define structure, and give feedback step by step, that developer quickly improves and starts producing useful work. AI tools behave the same way. They need direction, boundaries, and context. #### What vibe coding is - Translating intent into code. You guide AI with clear goals instead of typing everything yourself. - Testing ideas fast. Use AI to explore options and iterate quickly. - Building with structure. Move fast, but keep enough order to avoid a mess later. - Amplifying your skills. AI helps you work faster, but your judgment still drives the outcome. #### What vibe coding is not - Copy-pasting code. Random snippets without context don't make a product. - Letting AI "build an app" for you. It needs direction, not blind trust. - Outsourcing decisions. You still need to make the technical calls. - Magic without understanding. If you don't get what it's doing, you can't fix it when it breaks. ### Vibe coding example workflow Let's say you're building a small task management app. You start by explaining your goal to an AI coding tool: a simple web app where users can create, update, and delete tasks. You don't just ask it to "build the app". Instead, you describe the structure, maybe React for the frontend, Appwrite for the backend, and a clean UI with a form and task list. The AI generates a rough setup. You review it, make small edits, and ask follow-up questions like, like "Make the layout responsive". Each round of feedback gets it closer to what you want. Next, you use the same tool to write a few tests or refactor a messy function. You keep checking the logic, adding context, and fine-tuning the results. By the end, you've built a functional prototype faster than usual, and the code still makes sense to you because you stayed in charge throughout the process. ### Vibe coding vs traditional coding Traditional coding and vibe coding share the same goal: To build reliable software. But the way you get there feels very different. In traditional coding, you handle every step yourself, from setup to syntax. With vibe coding, you still write and review code, but much of the heavy lifting is done by AI tools. | **Vibe coding** | **Traditional coding** | | --- | --- | | Starts with intent and context | Starts with writing code from scratch | | Collaborates with AI to explore solutions | Relies fully on human input and iteration | | Iterative, fast feedback cycles | Linear, step-by-step development | | Focuses on guiding, reviewing, and refining | Focuses on implementing everything manually | | Great for prototyping and experimentation | Better suited for fully defined systems | | Requires basic technical know-how | Requires deep technical understanding | ### Vibe coding best practices While vibe coding tools are great, they're only as good as the person guiding them. It's still real software development, with real bugs, dependencies, and trade-offs. It's important to know where AI helps and where you need to step in. - **Know the basics:** AI can generate code, but it can't reason about your system the way you can. When something breaks, and it often will, your understanding of the basics is what helps you debug, adapt, and move forward instead of getting stuck. A simple understanding of the fundamentals of frontend, backend, and databases, and how they work, can go a long way. - **Stay aware of security:** AI doesn't naturally think about secure practices or edge cases. It might expose keys, skip validation, or leave gaps you'd never accept in production. Always review code with a security mindset, even if it looks clean on the surface. We have covered the vibe coding security best practices in this [blog](/blog/post/vibe-coding-security-best-practices). - **Write clear prompts:** The model only knows what you tell it. Vague prompts lead to vague results. Give it context, define structure, and explain what matters: style, dependencies, expected behavior. Clarity is half the work in vibe coding. Bottom line, you should be the **owner**. AI can generate, suggest, and review, but it doesn't understand trade-offs. You decide what's acceptable, what's scalable, and what's worth rewriting. Smart vibe coders treat AI as an assistant, not a replacement. ### Advantages and disadvantages of vibe coding Like any shift in how we build software, vibe coding comes with trade-offs. It can make you faster, sharper, and more experimental, but only if you know its limits. Here's what it gets right, and where it can slow you down. #### Advantages of vibe coding: - **Faster iteration:** You can go from idea to working code in minutes. Perfect for prototyping, testing, or exploring new directions. - **Less boilerplate:** Repetitive setup and syntax melt away, letting you focus on structure, flow, and user experience. - **Learning boost:** For developers, it's like you get a pair programmer with ideally infinite patience. Just that you have to bear with "You're absolutely right." once in a while. #### Disadvantages of vibe coding: - **Shallow understanding.** It's easy to skip over what's actually happening under the hood, which can make debugging harder later. - **Security blind spots.** AI won't always handle sensitive data or validation correctly. You need to review everything. - **Inconsistent quality.** Results can vary based on how well you prompt or how complex the task is. It's not a set-and-forget process. - **Over-reliance.** The more you depend on the model, the harder it becomes to think through problems independently. ### Popular vibe coding tools #### 1. Lovable [Lovable](https://lovable.dev/) is an AI app builder that leans on **Supabase** for backend services. It can generate UI and basic flows from prompts, but it does **not** automatically deliver a full app with database, auth, and APIs wired end-to-end. You'll need to connect your project to a Supabase database yourself, and that integration can be tricky depending on your schema, auth rules, and project setup. It's useful for quick front-to-back prototypes where speed matters, but because it depends on external infrastructure, you may run into limits when you try to customize deeply or scale. Expect to take more control of the stack as your app grows. #### 2. Bolt [Bolt](https://bolt.new/) is a lightweight environment for rapid iteration. It generates small web apps quickly and lets you adjust the code as you go. It's helpful for exploring ideas, building quick demos, or learning through experimentation. It's not a long-term workspace. For larger apps, you'll outgrow it fast. The setup is lightweight and smooth, though it depends on external services like Netlify for hosting. That makes it less suited for long-term or complex builds where you'd want deeper integration. #### 3. Replit [Replit](https://replit.com/) offers a browser-based coding environment with AI assistance. It can generate, explain, and refactor code across multiple languages. It's accessible. You can start building immediately without setup. It's solid for beginners or quick projects, but might not be a great option for performance-heavy codebases. #### 4. v0 by Vercel [v0](https://v0.app/) focuses on generating React-based UIs from text prompts. It's designed for front-end work: components, layouts, and interactions. The generated code is clean and integrates easily into Next.js projects. It's limited to UI, though, so you'll still need to handle backend logic elsewhere. ### Frequently asked questions (FAQs) **1. What is meant by vibe coding?** Vibe coding means building software with the help of AI coding assistants that understand natural language. Instead of typing every line yourself, you guide the AI through context, intent, and feedback, almost like mentoring a junior developer. **2. What are the benefits of vibe coding?** The biggest advantage is **speed**. You can move from an idea to working code much faster. It also helps reduce boilerplate work, improves iteration cycles, and acts as a learning companion that can explain or optimize code. **3. What is vibe coding vs prompt engineering?** Prompt engineering focuses on crafting precise inputs for AI models, mainly to get consistent or high-quality responses. Vibe coding goes beyond that. It's about using those prompts **within a live development workflow**: writing, testing, debugging, and shipping real software with AI collaboration. **4. Is vibe coding the future?** It's definitely shaping it. While traditional coding isn't going away, the tools we use and how we approach development are evolving. Vibe coding represents a shift toward **human-AI collaboration**, where developers act more as architects and reviewers, while AI handles repetitive or mechanical tasks. ### Conclusion While vibe coding tools are getting better, they still depend on how well you understand the problem, structure your ideas, and review the code written by AI. If you treat AI like a partner instead of a shortcut, you'll get real value from it. The speed, flexibility, and creative flow are all there, as long as you stay in control of the work. ### More resources - [Comparing the best vibe coding tools](/blog/post/comparing-vibe-coding-tools) - [20 vibe coding security best practices](/blog/post/vibe-coding-security-best-practices) - [Choosing the right AI database for your application in 2025](/blog/post/choosing-the-right-ai-database) --- ## Introducing the developers' cloud https://appwrite.io/blog/post/the-developers-cloud Technology continues to reshape the world, and developers remain at the center of that transformation. Their creativity and problem-solving are what turn ideas into products that matter. From the beginning, Appwrite was built to serve that creativity. Our mission has always been clear: to make software development accessible, developer-centric, and flexible by building with and for the open-source community. Over the past few years, Appwrite has become known as a reliable backend platform. We focused on empowering developers during the build stage. Auth, Databases, Functions, Messaging, and Storage formed the backbone of that experience. Together, they helped teams move from idea to working product faster, with less complexity and fewer decisions to manage. But every product journey continues beyond that first working build. Teams eventually ask the same questions: Where do we deploy? How do we go live? How do we scale? How do we secure and observe our systems in production? This is where overhead creeps in. It's where developers find themselves stitching together services, managing integration complexity, and maintaining mental models across multiple platforms that don't naturally speak to each other. And today, Appwrite has evolved to solve more of that journey. We're no longer just a backend. ### Evolving the platform Appwrite is becoming a more complete experience for developers, extending our capabilities across the full lifecycle of product development. This is not a change in mission. It's the natural next step in fulfilling it - giving developers everything they need to create and innovate without limits, all in one place. With the launch of Appwrite Sites, you can now deploy and host your applications directly through Appwrite. It's the first major milestone in supporting the journey beyond build. You can take your project live, scale it, and operate it with the same thoughtful developer experience and flexibility that have always guided Appwrite. We think about this journey as a continuous chain: **Imagine** - explore what you want to build **Build** - create it using Auth, Databases, Functions, Messaging, Storage **Deploy** - take it live, deliver it to users, and scale **Observe** - monitor and understand how it behaves in the real world **Protect** - operate with confidence, keep your data and users safe ![The cloud journey](/images/blog/the-developers-cloud/graph1.png) Our goal is to bring these stages together into one seamless flow. The result is a platform that reduces cognitive overhead, minimizes integration work, and lets developers focus on shipping. This expanding experience is what we call the developers' cloud. A BaaS stops at the API layer, leaving developers to assemble everything else. Hyperscalers sit much lower, offering enormous power but expecting teams to build every piece themselves. The space in between - the space where most real products live - has been fragmented for too long. The developers' cloud fills that gap. It brings together the capabilities developers need, at the right level of abstraction, with a coherent experience that carries them from idea to production and beyond. Not too high-level to lose control, not too low-level to slow you down - but intentionally designed for the realities of modern software. > Each of these stages feeds the other and creates a value loop. As you've learned to expect, there is always more coming, but for now, we'll let the work speak in its own time. As we look ahead, we're quietly working on expanding the edges of that lifecycle, especially around how developers begin new ideas and how they understand and operate their products as they grow. ### Why this matters Modern product development is cyclical. Teams build, deploy, observe, refine, and repeat. Each stage informs the next. The best platforms support that entire loop consistently and transparently. By evolving Appwrite into a complete end-to-end platform, we're creating an environment where developers can build, deploy, and grow without switching tools or losing context. It shortens feedback loops, encourages iteration, and keeps the entire product lifecycle close to the developer. This evolution still follows the same principles that shaped Appwrite from the beginning: flexibility, openness, and developer-centric design - now extended across the entire lifecycle. As this vision expands, we're also thinking about how to make the earliest steps of building to be more accessible. There's a lot of opportunity to help more people transform ideas into working products faster, while still keeping developers at the center of the platform. Some of that work is already underway. ### Integrating AI thoughtfully It's impossible to talk about the future of software without acknowledging AI. At the same time, we're aware there is real fatigue in the industry. Terms are overused. Promises are exaggerated. And too often, AI becomes a layer of hype rather than a layer of value. Our view is simple: AI is a tool that should make developers more productive, not replace the craft behind their work. We use AI internally to move faster, automate the repetitive parts, and help teams explore solutions more efficiently - always with humans in the loop. We apply it where it improves quality and reduces friction, not where it adds noise. That will continue to guide how we bring AI into the platform. From a product perspective, our focus is on flexibility and integration rather than hype. As part of our ongoing evolution of Appwrite's database capabilities, we're expanding the tools available to developers building AI-powered applications. This includes adding a built-in vector store to Appwrite, improving how AI models integrate with Appwrite Functions, and smoothing the path toward complete, reliable RAG workflows across different LLM providers. We're also working to make it even easier to build persistent, agent-driven applications using Appwrite Functions together with Appwrite Realtime. This pattern already powers long-running, event-aware systems built on Appwrite today. Our focus now is on making the experience more accessible, improving the knowledge and tooling around it, and refining the workflows so developers can build real-time, LLM-enabled applications with less friction. All of this work follows the principles that define Appwrite: predictable performance, strong defaults, and a developer experience that remains coherent as the platform grows. AI should enhance the way you build and operate your applications, not introduce new friction. That's the bar we're building for. ### How we build As our scope grows, so does our responsibility to build with depth and intention. To support this evolution, we operate through cross-functional product teams. Each team owns a vertical end-to-end, combining engineering, product, design, and operations to focus deeply on quality and developer experience. Cross-functional ownership gives teams autonomy and speed, but it also introduces new challenges. When multiple teams move in parallel, it becomes harder to maintain shared standards, align on design principles, and ensure a consistent developer experience across products. Without structure, surfaces can drift and lose coherence. To address these challenges, we balance vertical product teams with functional chapters. Chapters bring together experts across teams in engineering, product, and design to maintain strong communication, shared standards, and a unified perspective. They ensure that using one Appwrite product feels familiar when you move to another. APIs align. Workflows behave consistently. The platform feels cohesive and intentional. This structure lets us move with urgency while protecting the details that matter most: reliable scaling, predictable performance, thoughtful APIs, transparent pricing, secure defaults, and workflows that feel natural. Every stage of the developer journey requires specialization. We take that seriously. ### What comes next Appwrite will continue delivering the backend building blocks and deployment tools you rely on. At the same time, Sites and the upcoming stages of Imagine, Observe, and Protect will expand how developers interact with their projects in production, giving you more visibility, control, and confidence as you scale. You'll also see us continue to explore new layers of flexibility, especially in how developers connect and manage their data. We're working on something that will push our vision even further, but we'll share more when it's ready. This isn't a single release. It's a long-term evolution guided by the same mission that has always defined Appwrite: empowering developers to create, innovate, and succeed at scale with a platform that grows as they do. ![The cloud journey](/images/blog/the-developers-cloud/graph2.png) As these pieces come together, Appwrite becomes not just where you build, but where your product lives and scales. One platform. Consistent experience. Full lifecycle. This is the future we are building toward. And this is just the beginning. --- ## The evolution of team Appwrite https://appwrite.io/blog/post/the-evolution-of-team-appwrite 2024 has begun, and as it goes with a new year, many people search for a new job. Statistically speaking, January is the most popular month of the year for new job seekers. Reasons vary from New Year's resolutions and increased hiring activity to bonus payouts. At Appwrite, we notice this, too. That said, throughout the year the team gets numerous hiring requests in their DMs, so many we cannot respond to most, let alone do we have vacancies for all these people. So, in response, we decided to write this blog. We will give you insights into how many current team members joined Appwrite after helping out in the community and contributing. But please note they never did this with the intention of getting hired. This was just the outcome of their actions. ![The Appwrite team in New York](/images/blog/the-evolution-of-team-appwrite/team.jpg) ### The very first Appwrite employees To kick off, the very first Appwrite employee, [Eldad Fux](https://github.com/eldadfux), started his own open-source project and worked relentlessly to get Appwrite where it is today. Aka, the Founder & CEO of Appwrite, aka contributor zero. After the first funding round, there was financial power to hire the first engineers of Appwrite. Here is an overview of the very first engineers who joined the team and how they stood out. [Christy Jacob](https://github.com/christyjacob4) is currently the Lead Engineer building out Appwrite Cloud. But before that, he was one of the very first active contributors to Appwrite, and at only 23 years old, he joined the team as a Founding Engineer. After being hired, he went above and beyond to help Appwrite grow. Besides coding, he handled social channels and took care of most of the copywriting work, as well as other marketing and growth-related work. [Damodar Lohani](https://github.com/lohanidamodar) is a Flutter enthusiast, and in his search for a proper Backend-as-a-Service, he discovered Appwrite. Not much later, he dedicated an entire [YouTube channel](https://www.youtube.com/@appwriters) to videos on Appwrite and Flutter and contributed to Appwrite’s Flutter SDK. If you want to know what to do to get hired by Appwrite, or any other OSS company, he is a leading example. Making contributions in both code and content definitely gets you noticed. [Jake Barnby](https://github.com/abnegate) was a rather tricky one logistically. Being the only one in the southeast Pacific time zone, working together with him was an unforeseen challenge. When most of us log on, Jake logs off. However his contributions to the Android SDK and his wide knowledge of languages and platforms helped him land a job at Appwrite. He contributed to every single SDK and API, and even frontend work. He got promoted to Lead within a few months. It is a classic story of dedication and hard work that pays off. [Bradley Schofield](https://github.com/PineappleIOnic) dropped out of college because he wasn’t learning enough. We have heard that story quite a few times in the developer community, but did you know he was only 16 when we hired him? That’s a first for us. Now, he has been around for three years, and as a full-stack engineer with a love for data, he is the go-to engineer for anything growth-related. Like many products in a staring phase, Appwrite had some bugs. Our current front-end lead [Torsten Dittmann](https://github.com/TorstenDittmann) decided not to sit around and wait for a fix and started contributing by fixing bugs for Appwrite. Today, whenever you open your Appwrite Console, you can see Torsten and his team at work. ### As the team grew If you know Appwrite, you know [Steven Nguyen](https://github.com/stnguyen90). Not all heroes wear capes, but we should get Steven a cape. Not only does he work on code at Appwrite, but he also answers everyone’s questions on our Discord server. He started doing this a year-long before he joined Appwrite. As you can imagine, he is a welcome addition to the team. ![CS Dojo Tweet](/images/blog/the-evolution-of-team-appwrite/cs-dojo-tweets.png) [Khushboo Verma](https://github.com/vermakhushboo) is someone you’d love to have on the team. She creates content, does public speaking, is an engineer, and is a valuable addition to the Appwrite team. She discovered Appwrite through another Appwriter, [Aditya](https://github.com/adityaoberai), when she invited him to join a liveshow for GitHub’s Education channel. Appwrite’s community-driven culture drew her curiosity, and she started contributing to [Utopia-PHP.](https://github.com/utopia-php) She immediately joined the Discord server, where she connected with the rest of the team, which helped her expand her skillset and she contributed yet again by adding the PostgreSQL database adapter. She felt that this was a rewarding experience, and soon after, she joined Appwrite as she knew that Appwrite would be the right place for her. [Wess Cope](https://github.com/wess) is more than an engineer, a father, content creator, mentor, aspiring dev advocate, and Batman. He is probably the most experienced engineer we have on the team. Name a framework, and he will know how to code it. He joined the community and started asking many IoT questions, and while waiting for answers, he just started helping others. Again, like the others, he got noticed, especially with all of the knowledge he possessed. [Matej Bačo](https://github.com/meldiron) started tinkering with Appwrite as a freelancer and ran into some obstacles. Thanks to Appwrite's Discord server, he could learn from others and understand all the concepts rapidly. To show appreciation for all the support maintainers gave, he decided to help with all the community questions he possibly could. This led to him being a valuable member of the Appwrite team. ### Contributing in general Now, we don’t only hire people from our own community. We, and other tech companies, appreciate contributing in general. [Thomas G Lopes](https://github.com/TGlide/), a front-end dev, is one of the people who has made notable contributions within the Svelte community. He even built [Melt UI](https://melt-ui.com/docs/introduction), an open-source collection of accessible and customizable component builders for creating user interfaces with Svelte. His contributions earned him the notable title of an official Svelte Ambassador. If you have been around the Appwrite community, you must know [Aditya Oberai](https://github.com/adityaoberai) by now. He actively participates in the larger tech and hackathon community and stumbled across Appwrite by chance on X. Now, one thing you need to know about Aditya is his unfair advantage: he is a networker. So when he discovered Appwrite, he noticed that he already knew many other contributors, which drew him in further and helped him land a job at Appwrite. [Dennis Ivy](https://twitter.com/dennisivy11) is not just a class teammate but also an amazing tutor to so many devs, as he has produced hundreds of videos that have helped others in the community learn how to code, including [tutorials on Appwrite](https://www.youtube.com/watch?v=pGNxoIoLt_Y). Need we say more? Contributing is more than just code! [Sara Kaandorp](https://www.linkedin.com/in/sara-k-78468a116/) joined as the first non-technical hire. Now, She leads our Design team and, with it, brings our brand and visual identity to new heights every day. She might not have contributed to the community, but as a designer, she put the work in with her portfolio. Also, she understands development as she has a [background](https://appwrite.io/blog/post/appwrite-decoded-sara-kaandorp/) in it. Therefore, there is no better designer we could wish for to lead all of our efforts. ### How to stand out from the crowd Finding the right team members is crucial for tech start-ups. When you are still building your company, there is little room for error, especially when building your team. Hiring the right people determines future success. So it’s about finding the needle in the haystack for employers like Appwrite, and for you to be that needle, you need to shine brighter than the rest. Now, this is not an unhealthy testimony to working day and night, every day, no fun, or any of that. On the contrary, we stand for healthy life balance and will force you to take leave if you like it or not. To stand out is to deliver quality work consistently, not quantity, to be eager to learn, receptive to feedback, and supportive of others. And if you missed this point, one thing is for sure: contributing to open source increases the chances of getting hired into a tech job. ![Andrew Red Tweet](/images/blog/the-evolution-of-team-appwrite/andrew-red.png) ### Doing more with your skills So, how do you stand out? Be active in the community you want to work in. Contribute to open-source projects with code, content, support, or whatever works best for you. To learn more and get inspired, follow people like [Eddie Jaoude](https://twitter.com/eddiejaoude), [Danny Thompson](https://twitter.com/DThompsonDev), [Ruth Ikegah](https://twitter.com/IkegahRuth), [Santosh Yadav,](https://twitter.com/SantoshYadavDev) and our very own [Eldad Fux](https://twitter.com/eldadfux). However, if you decide to contribute, keep in mind: [keep it healthy.](https://appwrite.io/blog/post/make-open-source-healthier) ![Eddie Jaoude Tweet](/images/blog/the-evolution-of-team-appwrite/eddie-jaoude.png) --- ## The future of coding: Cursor, AI, and the rise of backend automation with Appwrite https://appwrite.io/blog/post/the-future-of-coding-cursor-ai-and-the-rise-of-backend-automation-with-appwrite The way we build software is evolving quickly. AI-driven tools like Cursor are changing how developers write code, while backend platforms like Appwrite are changing how infrastructure is managed. Together, they are shaping a new era of development focused on automation, speed, and intelligence. In this new ecosystem, Model Context Protocol (MCP) servers act as the missing bridge between these tools, allowing editors like Cursor to talk directly to platforms such as Appwrite. ### Cursor: Coding with context and AI Cursor is often described as “the best way to code with AI.” ([cursor.com](https://cursor.com)) It combines the familiarity of a code editor with the intelligence of an AI pair programmer. As Datacamp explains: > Cursor takes this a step further by directly integrating with the code editor, eliminating the need to switch between the editor and a chat interface. > — [Datacamp](https://www.datacamp.com/tutorial/cursor-ai-code-editor) With Cursor, developers can describe what they want instead of typing every line manually. Prompts like *“Create a REST API for user authentication”* or *“Add a search feature with pagination”* can generate entire code scaffolds. But while Cursor accelerates frontend and logic creation, developers still need a reliable backend to store data, manage users, and host their apps. That is where Appwrite and the MCP connection come in. ### Appwrite: Backend automation made simple [Appwrite](https://appwrite.io) is an open-source platform that simplifies backend setup by providing authentication, databases, storage, functions, and hosting all in one place. Appwrite’s philosophy aligns perfectly with the automation movement. It allows developers to focus on building their ideas instead of wiring infrastructure. ### MCP: The bridge between AI editors and backends Until recently, connecting AI-powered editors like Cursor to backend services required manual configuration or custom API work. The **Model Context Protocol (MCP)** changes that. MCP is an open standard that allows editors, agents, and other developer tools to securely communicate with external services. Appwrite’s MCP server enables direct interaction with your backend right inside Cursor. > "The Appwrite MCP server allows you to connect LLMs and tools like Claude Desktop, Cursor, Windsurf etc. to your Appwrite project and interact with the API using natural language." > — [Appwrite MCP Documentation](https://appwrite.io/docs/tooling/mcp/cursor) This means you can use plain English in your editor to create or modify backend resources: - “Create a new Appwrite project.” - “Add a database collection for products.” - “Generate a function to handle user registration.” All of this happens without leaving Cursor. The MCP layer handles the communication between your editor and Appwrite's API securely and consistently. MCP is a quiet but powerful innovation. It transforms how developers connect tools, eliminating friction between code editors, APIs, and backend platforms. Instead of writing connectors or managing tokens manually, developers simply *talk* to their backend. ### A unified development flow With Cursor, Appwrite, and MCP working together, the development process becomes seamless: * **Cursor** generates code and handles logic through AI-powered prompts * **MCP** acts as the translator, letting Cursor communicate with Appwrite's backend * **Appwrite** provides instant backend infrastructure without manual setup Here's how it works in practice: You describe your app idea to Cursor, which generates the frontend code. Meanwhile, through MCP, Cursor automatically creates the necessary Appwrite databases, sets up authentication, and configures storage—all through conversation. What used to take hours of configuration now happens in minutes. ### Real-world impact This combination delivers tangible benefits: **Speed**: What once required hours of backend setup can now be accomplished in minutes. Instead of configuring databases, writing API endpoints, and setting up authentication separately, developers describe their needs and watch the pieces come together. **Reduced complexity**: No more context-switching between documentation, API references, and your code editor. Everything happens in one place, with AI understanding the full context of your project. **Lower barrier to entry**: New developers can build production-ready apps without deep backend expertise. The AI handles the technical details while developers focus on solving problems. **Seamless integration**: MCP ensures that frontend code generated by Cursor automatically works with Appwrite's backend, reducing integration bugs and compatibility issues. ### The path forward We're witnessing a fundamental shift in how software gets built. Tools like Cursor make coding more accessible, while platforms like Appwrite eliminate infrastructure complexity. MCP connects these worlds, creating a development experience where ideas flow directly into working applications. This isn't just about faster development—it's about removing friction between thinking and building. Developers can experiment more freely, iterate faster, and focus on what matters: solving real problems for real users. Ready to experience this workflow yourself? [Set up the Appwrite MCP server in Cursor](https://appwrite.io/docs/tooling/mcp/cursor) and start building your next project with AI-powered backend automation. --- ## The journey and meaning behind our new logo https://appwrite.io/blog/post/the-journey-and-meaning-behind-our-new-logo In the fast-evolving world of technology and software development, change is not just inevitable; it's essential for growth and progress. One of Appwrite's recent transformations has been the evolution of our logo. Today, we'll take you on the creative journey that led to our new logo's design and meaning - a journey with our community at its core. ### How it started Our first logo was initially designed by our CEO, Eldad. Its design aimed to align with our first slogan: 'Let's make coding fun again' - featuring rounded elements reminiscent of a gaming controller and the HTML symbol, which stood for our product's original focus. Its vibrant pink color was inspired by a dress worn by our founder's partner, adding a personal and meaningful touch that ultimately set Appwrite apart from the crowd. During that time, this design perfectly encapsulated Appwrite's playful nature. ![Appwrite's old logo](/images/blog/the-journey-and-meaning-behind-our-new-logo/old_logo.png) However, as Appwrite grew and evolved, it became increasingly clear it was becoming less aligned with our vision to empower developers to adapt to any platform, wherever they are. The HTML symbol wasn't fully inclusive of the many coding languages we had started to support. Moreover, there were issues with scaling the logo down to smaller sizes, and a questionnaire revealed that the meaning behind our logo wasn't entirely clear. We realized it was time for a change, time for a logo that not only embodied the core essence of Appwrite but could also scale with us on our journey of growth and development. We transitioned from a playful approach to something a bit more serious, making Appwrite a platform that companies could rely on as they scaled. ### Our creative journey Crafting a new logo was no small task. It required meticulous planning, creativity, and a deep understanding of what Appwrite stood for. As the design process often starts with sketches, we first explored various concepts and ideas on paper. Once we had vectorized our ideas, we involved the broader team in feedback rounds to ensure we were on the right track. This provided us with invaluable insights and offered a fresh perspective on questionable aspects we might not have otherwise noticed. While we initially experimented with obvious hints related to the coding world (such as coding symbols and interface elements) we soon realized that these, although relevant to our work, were somewhat generic and didn't truly embody what makes Appwrite unique. ![Appwrite's new logo drafts](/images/blog/the-journey-and-meaning-behind-our-new-logo/drafts.png) ### Breakthrough moment After many reviews, we finally pinpointed the elements we actually aimed to capture. The aspects that make Appwrite unique: our global community and the essence of coding. Through numerous iterations, one design stood out. A design that symbolized both our global community and the world of code. With the design vectorized, it was time for final review rounds and tweaks. We fine-tuned the logo and conducted scaling tests to ensure it looked great at all sizes. ![Appwrite's new logo transition from draft to vector](/images/blog/the-journey-and-meaning-behind-our-new-logo/break.png) ### Our new logo The result of this creative journey is a logo that reflects the essence of Appwrite. It's not just a symbol; it's a representation of our commitment to building a global community united by a love for code. ![Appwrite's new logo](/images/blog/the-journey-and-meaning-behind-our-new-logo/new_logo.png) The new logo features lines that converge to form a globe. This represents our diverse and interconnected community spread across the world. It's a testament to the power of collaboration and inclusivity that drives Appwrite. The lines in the logo evoke the idea of code - lines of code coming together to build something meaningful. This symbolism reflects the essence of our platform, empowering developers to create with ease and efficiency. These lines also feature prominently in our imagery style. Finally, its overall look is reminiscent of the lowercase letter 'a,' standing for the first letter of appwrite. ![Decorative lines](/images/blog/the-journey-and-meaning-behind-our-new-logo/lines.png) Our new logo inspired the rest of our visual identity. Our [website](https://appwrite.io/), promotional materials, and even [documentation](https://appwrite.io/docs) are now harmonized with this new identity, giving Appwrite a fresh, meaningful, and cohesive look. ### The road ahead The evolution of our logo is not just a visual change; it's a reflection of our growth, our commitment to our community, and our dedication to empowering developers worldwide. As Appwrite continues to evolve, this new logo will serve as a symbol of our journey - a journey driven by innovation, inclusivity, and the limitless possibilities we're aiming to embrace in the world of software development. We are excited to embark on this new chapter with our brand new identity. Together, we'll continue to build, code, and shape the future of software development. The journey has just begun, and the possibilities are endless, as we build like a team of 100. --- ## The subtle art of hackathon ideation https://appwrite.io/blog/post/the-subtle-art-of-hackathon%20ideation Hackathons have been an immense part of my journey as a developer and a student, providing me with some of the best growth opportunities I could have hoped for. Participating in hackathons was immensely rewarding for me in terms of learning, work output, networking as well as the occasional prizes. Yes, I did have a few hackathon wins in my time, and one thing I did notice to be common in every single one of those instances was the quality of the idea I chose to work on. Ideating in a hackathon is critical; however, its significance can often be lost on hackers, especially since not enough people discuss the importance and impact of ideation. Therefore, in this blog, I will discuss why it is important to ideate in a hackathon and how you can make this process easier for yourself. ### What is a hackathon? Before discussing the importance of ideation in a hackathon, it is important to understand what a hackathon is in the first place. Here is how I define a hackathon: > A hackathon is essentially a development marathon designed to push developers past the limits of their creativity, imagination, and technical know-how to create innovative products and solutions. > Simply put, a hackathon is a race to build the best solution to an externally-provided or self-decided problem within a given timeframe. ### Flow of a hacker’s journey Typically, hackers go through a similar flow every time they participate in a hackathon. They form a **team**, **ideate** the problem and solution they want to focus on, **design** their solution, **develop** their project, **test** whether it works, and finish with a **demonstration** of the work. ### How does ideation fit in the puzzle? Ideation is a crucial part of the hackathon journey because the primary focus of a hackathon is to enable problem-solving. You aren’t there just to write the best code but first to solve a problem that impacts people. This includes choosing an important problem, creating humanly-usable UI/UX, developing functioning software, and demonstrating how all of these come together in the final pitch. In truth, this flow is just like dominoes; if an early domino (like ideation) falls, the rest of the dominos fall too. ### How to start ideating a hackathon project? Now that we have discussed the importance of ideation for a hackathon project, let’s understand how exactly you should ideate at a hackathon. Personally speaking, I like to treat ideation as a 4-step process. They are as follows: #### Step 1: Pick a problem you personally relate with The first step is to choose a problem that may have impacted you or is one you care about. The reason I say so is because emotional connections can be major motivators and keep us committed. This substantially improves your chances of seeing your project through till the end, as well as ensuring focus throughout. #### Step 2: Understand the scale of your problem in your surroundings Once you have picked a problem you care about, the next step is to evaluate how many more people this problem affects. This can be achieved by performing online research, discussing on social media, or more substantial user research. The higher the scale of your problem is, the more value your solution will hold. #### Step 3: Evaluate potential solutions to this problem Once your problem is picked, it is necessary to brainstorm how you will solve it. Each problem will have multiple ways to navigate. You want to ensure you place all possible ideas on the table before starting to build and discard ones that don’t make sense. If you don’t do this, you may discover halfway along the hackathon that your method to solve the problem is not ideal, which only leads to a lot of wasted time. #### Step 4: Choose the most self-sustainable solution Once you have sifted through your potential solutions, make sure to pick one that can sustain itself rather than be dependent on people or systems you don’t already have access. Remember, you are aiming to build a solution at the end of the day, not just infrastructure for a process or a system that may or may not be possible to execute. If you can pick a solution that can run on its own to a problem that has a wide scale, you pretty much hold a golden egg in your hands for the hackathon world. ### Conclusion One of the most important parts of that journey was working to solve problems that were important to me rather than focusing on problems imposed by other individuals or circumstances. Hackathons are very special spaces that let you create real solutions to real problems in a safe environment. As long as you spend that time learning and growing while working on problems you care about, you will go a lot further than you can possibly imagine! - [Appwrite GitHub](https://github.com/appwrite) - [Appwrite Docs](/docs) - [Discord Community](https://appwrite.io/discord) --- ## Three important steps you need to complete before setting up your first Appwrite project https://appwrite.io/blog/post/three-important-steps-you-need-to-complete-with-appwrite When creating a new project, there are three important things you need to do to ensure you have things set up properly and can connect to your Appwrite backend. Forgetting to complete one of these steps will result in errors later when trying to make your first request, so let’s jump in and see what they are so things are set up properly for your next project. ### 1 - Add a platform After creating a new project in the Appwrite console, you’ll need to add a platform in order to connect an SDK. You can add a platform from the “overview” tab under the “Add a platform” section. From here, you’ll need to choose between one of the four [client-side SDK’s](/docs/sdks#client) or generate an API key with a [server-side SDK](/docs/sdks#server). If you’re adding an API key to use one of the server SDK’s, you’ll be asked to set scopes for the key. This sets permissions, allowing us to dictate what permissions this API key has over certain parts of our application. ![Appwrite Console adding a platform](/images/blog/three-important-steps-you-need-to-complete-with-appwrite/add-platform.png) ### 2 - Add a hostname When using the Web SDK, you’ll need to set a hostname. This just tells Appwrite what origin you’ll be connecting from. Failing to set the right hostname will result in CORS error later, so be sure you pay attention when setting this. I just put out a full video and [an article about handling CORS errors](/blog/post/cors-error), so be sure to check that out if you have questions or face any problems. ![Hostname](/images/blog/three-important-steps-you-need-to-complete-with-appwrite/hostname.png) ### 3 - Permissions Before you can read and write data from an Appwrite database collection or storage bucket, you’ll need to ensure you have the proper permissions set. This can be set from the “Settings” tab under the “Permissions” section inside a collection or storage bucket. ![Permissions overview](/images/blog/three-important-steps-you-need-to-complete-with-appwrite/permissions.png) With these three steps to complete, you should be set to connect to an Appwrite backend from either a client or server SDK. ### Resources Visit our documentation to get started, join us on Discord to be part of the discussion, or visit our blog and YouTube channel to learn more! - [Docs](/docs) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) --- ## Top 25 vibe coding tools that will transform your development workflow in 2025 https://appwrite.io/blog/post/top-25-vibe-coding-tools Imagine coding with an AI partner that suggests improvements in real-time. This is the future with vibe coding. AI pair programmers are becoming indispensable in modern development environments. Coding agents automate repetitive tasks, freeing developers to focus on solving more complex problems. They improve development speed and streamline workflows. The best part, vibe coding tools adapt to individual styles, offering personalized coding experiences. In this blog, we’ll go in-depth of what is vibe coding, why they matter and top 25 vibe coding tools ### What is vibe coding? The evolution of AI-driven development Vibe coding is a programming style where developers rely heavily on AI models to generate and debug code. The AI tools handle most of the heavy lifting, allowing you to focus more on orchestrating the overall logic, structure, and flow of the application rather than getting bogged down in syntax or boilerplate. It’s less about writing every line and more about giving guiding the system, validating outputs, and steering the direction. In practice, it means you just have to articulate your requirements in plain English and let the AI tool come up with the code. You can then improve upon the code through quick iterations, by refining your prompts, giving feedback, or making light edits. This heavily boosts developer productivity, helps you ship faster, and frees up your time to focus on more complex, meaningful problems that typically get sidelined due to time or resource constraints. ### Why vibe coding tools matter in 2025 Vibe coding tools are more than just a trend. They are essential tools for developers aiming to stay competitive. This importance will only grow in 2025. The modern development landscape demands speed and precision. [AI integration](/integrations#ai) in coding tools provides these. It allows developers to work smarter, not harder. And the shift is already happening. According to a [JetBrains developer ecosystem survey](https://www.jetbrains.com/lp/devecosystem-2024/#), the most common answer to how much time AI tools save was **1–2 hours per week,** a clear indicator that even light AI assistance can translate to meaningful gains in productivity Not just productivity, vibe coding also fuels innovation. With AI taking care of the repetitive and boilerplate tasks, developers have more room to experiment with new ideas, rapidly build prototypes, and iterate on concepts. Mockups that used to take days can now come together in hours. This faster feedback loop changes how software is built, making the process more creative, agile, and focused on outcomes rather than effort. ### Key features to look for in vibe coding tools When choosing vibe coding tools, certain features stand out. These features ensure tools are not only effective but also future-proof. Developers need more than just code suggestions. They need tools that understand context, support multi-step workflows, and can assist across the entire development lifecycle. So here are some key features that you should look for: - Natural Language input: This is basic, pick a tool that allows you to express your coding needs in plain language or voice commands. - AI-generated code: The core of vibe coding. The tool should be able to turn prompts into functional, ready-to-run code that aligns with your intent. - Iterative refinement: Great output is all about iterations. Select a tool that allows you to refine the code, give feedback and request changes. - Code explanation: Some tools can simplify complex code by breaking it down into human-readable summaries. Super useful for learning, debugging, or collaborating. - Bug detection and code fixes: A tool that can scan for bugs could be of great help and can save your time and efforts. - Support for Model Context Protocol (MCP): Tools that support the [Model Context Protocol](/docs/tooling/mcp) can retain deeper project context, enabling smarter, more relevant suggestions across files and sessions. Lastly, pay attention to user interface design. A well-designed, intuitive interface improves user experience. It ensures that you spend more time coding and less time navigating the tool. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Open source" point2="Start for free" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/console/" /%} ### How we chose the top 25 vibe coding tools Selecting the top vibe coding tools for 2025 required a meticulous approach. We analyzed various factors to ensure each tool met the highest standards. Our goal was to identify tools that can truly transform workflows. Firstly, we focused on innovation and AI integration. Tools that leverage AI to enhance functionality stood out. We assessed their potential impact on productivity and ease of use. The ability to integrate smoothly with existing environments was also crucial. Here's what we considered: - Advanced AI capabilities and innovation. - Integration with popular development platforms. - User reviews and industry feedback. We also looked into user feedback. Tools with positive reviews and strong community support scored highly. This comprehensive evaluation ensured our list was both relevant and reliable for developers in 2025. ### The top 25 vibe coding tools of 2025 In 2025, vibe coding tools are set to redefine software development. These tools integrate advanced AI technologies, enhancing coding efficiency and creativity. Developers can expect powerful features that improve workflows and reduce errors. Here's a list of the top tools: - Apidog MCP Server - Cody by Sourcegraph - GitHub Copilot - Bolt.new by StackBlitz - Cursor by Anysphere Each tool offers unique capabilities, catering to a variety of development needs. #### 1. Apidog MCP Server [Apidog MCP Server](https://docs.apidog.com/apidog-mcp-server) is revolutionizing code prediction. It leverages machine code prediction to enhance performance. This tool is perfect for developers seeking optimization. The server has a user-friendly interface. It easily integrates with most development environments. Its real-time feedback and improvements reduce debugging time significantly. With Apidog MCP Server, developers can anticipate performance bottlenecks before they occur. #### 2. Cody by Sourcegraph [Cody](https://sourcegraph.com/cody) by Sourcegraph excels in code search and intelligence. It's a vital asset for large codebases. Cody's precision in finding code references and usages saves precious time. This tool enhances collaboration among development teams. It allows real-time code navigation and refactoring suggestions. By integrating seamlessly with popular IDEs, Cody boosts productivity and reduces code search fatigue. #### 3. GitHub Copilot [GitHub Copilot](https://github.com/features/copilot) is the quintessential AI pair programmer. It offers real-time code suggestions. Trained on massive open-source data, it excels at predictive coding. With Copilot, developers can drastically cut down on routine tasks. Its intuitive suggestions boost efficiency. By predicting code requirements, it helps maintain focus on solving complex problems. #### 4. Bolt.new by StackBlitz [Bolt.new](https://bolt.new/) takes development to the cloud effortlessly. It facilitates instant loading and execution of projects. This tool offers an isolated environment for safer, faster code execution. By using Bolt.new, developers can focus on problem-solving rather than setup. Its seamless cloud integration and robust support for various languages make it indispensable. Reducing initial setup time is its hallmark feature. #### 5. Cursor by Anysphere [Cursor](https://cursor.com/) by Anysphere offers an immersive coding experience. This editor is built to understand context like never before. It utilizes AI to adapt to coding style and preferences. Users appreciate Cursor's smart autocompletion and error detection. The tool optimizes workflows with adaptive learning. With its cross-platform support, Cursor is an excellent choice for diverse teams. #### 6. v0 by Vercel [v0](https://v0.dev/) by Vercel revolutionizes frontend development. It provides real-time updates and quick deployment options. v0 is designed for speed and efficiency. Developers can push updates with minimal disruption. The tool's preview capabilities ensure accuracy before deployment. v0 stands out for its seamless integration with frameworks like Next.js, offering an enhanced development experience. #### 7. GoCodeo [GoCodeo](https://www.gocodeo.com/) is a coding agent that enriches the development process. It brings AI-driven insights to software engineering. GoCodeo enhances code quality and automates repetitive tasks. Equipped with robust analytical tools, it improves resource management. GoCodeo's ability to predict code behaviors makes it an invaluable partner. This tool helps developers achieve higher levels of precision. #### 8. bolt.diy [bolt.diy](https://github.com/stackblitz-labs/bolt.diy) offers a flexible, customized development experience. This tool lets developers configure environments to fit specific project needs. It streamlines coding tasks with efficiency in mind. Users benefit from bolt.diy’s powerful automation features. The tool adapts to evolving requirements with ease. Its capability to handle complex workflows makes it a developer favorite. #### 9. Tempo by Tempo Labs [Tempo](https://www.tempo.new/) by Tempo Labs redefines project management in coding. It ensures streamlined workflows with time optimization features. Tempo helps developers maintain focus on priorities. With Tempo, collaboration becomes seamless. It offers progress tracking and team synchronization. The real-time communication functionality ensures all team members stay informed. #### 10. Goose by Block [Goose](https://github.com/block/goose) by Block focuses on security in coding. This tool performs real-time threat detection. Goose ensures code integrity through robust security measures. Teams appreciate its proactive security alerts. Goose integrates smoothly with existing systems. It continuously updates developers on potential vulnerabilities. #### 11. Devin by Cognition AI [Devin](https://devin.ai/) by Cognition AI offers advanced machine learning capabilities. It's designed to improve learning curves and enhance productivity. Devin fosters an intuitive development environment. By automating complex tasks, it allows focus on core processes. Devin's insights guide smarter decision-making, aligned with project goals. Developers commend its ability to adapt to shifting demands. #### 12. Softgen by Kortix AI [Softgen](https://softgen.ai/) is an adaptive tool focusing on intelligent coding solutions. Its AI capabilities bring innovation to mundane tasks. Softgen anticipates programmer needs in real-time. This tool offers predictive coding features that enhance creative thinking. Softgen improves code reliability through advanced analytics. It sets the standard for future AI-driven development tools. #### 13. WebSparks by WebSparks.AI [WebSparks](https://websparks.ai/) is known for dynamic web development support. It empowers developers with responsive design tools. WebSparks automates optimization for different platforms. By focusing on user experience, it ensures quality results. The tool enhances web application efficiency with real-time previews. WebSparks remains a top choice for modern developers. #### 14. Replit [Replit](https://replit.com/) redefines collaborative coding in the cloud. It supports real-time co-editing features. Replit lowers the barrier for developers to start projects quickly. Developers value its interactive platform. Replit’s support for numerous languages encourages experimentation. It's a versatile tool that adapts to various development needs. #### 15. Zed by Zed Industries [Zed](https://zed.dev/) by Zed Industries delivers a new standard in interface design. It offers smart design suggestions and enhancements. Zed reduces the time spent on tedious tasks. Its integration capabilities ensure smooth workflow transitions. Zed’s focus on accessibility helps it stand out. The tool is integral for developers prioritizing UI/UX excellence. #### 16. Fine by Fine.dev [Fine](https://www.fine.dev/) by Fine.dev focuses on precision and efficiency in coding. It refines processes with advanced editing capabilities. Fine is a tool for developers who value accuracy. Its real-time feedback enhances code quality. Fine’s customization options allow bespoke solutions. This tool keeps developers engaged and efficient. #### 17. The Windsurf Editor by Codeium [The Windsurf Editor](https://windsurf.com/) by Codeium is an immersive development environment. It adapts to user inputs and improves code readability. The Windsurf Editor enhances comprehension for complex codes. Developers appreciate its intuitive design and collaborative features. The Windsurf Editor offers flexibility in adjusting to various projects. It remains a preferred choice for developers aiming for clarity. #### 18. Cline by Cline AI Coding Agent [Cline](https://cline.bot/) by Cline AI Coding Agent simplifies coding processes. It provides intelligent assistance to streamline workflows. Cline adapts quickly to development environments. This tool integrates AI to address code efficiency. It supports diverse programming languages, broadening its utility. Cline stands out for its ease of use and versatility. #### 19. Codev by co.dev [Codev](https://www.co.dev/) by co.dev offers robust analytics for modern developers. It enhances productivity through data-driven insights. Codev ensures informed decision-making in software development. Its integration with analytics tools aids in code optimization. Codev’s predictive features streamline debugging. This makes it essential for projects demanding precision. #### 20. Devika [Devika](https://github.com/stitionai/devika) empowers developers with AI-driven support tools. It automates routine coding tasks efficiently. Devika prioritizes user-friendly interfaces and adaptability. Developers find its setup intuitive. Devika enhances productivity by reducing cognitive load. It stands as an innovative tool that fosters creativity. #### 21. Lovable [Lovable](https://lovable.dev/) sets a new standard in adaptive coding agents. It monitors patterns and suggests optimizations. Lovable aligns with developer workflows seamlessly. Its personalized environment adapts with usage. Lovable is praised for enhancing workflow consistency. The tool’s ability to evolve with user needs is remarkable. #### 22.Base44 [Base44](https://base44.com/) transforms app development through natural language. It lets users describe what they want, and instantly generates full-stack apps—no code, no setup required. With built-in backend, frontend, authentication, and hosting, Base44 simplifies the dev process end-to-end. It’s perfect for founders, PMs, and builders who want to go from idea to live app in minutes. #### 23. Augment Code [Augment Code](https://www.augmentcode.com/) integrates cutting-edge AI to facilitate coding efficiency. It delivers swift code assessments and improvements. Augment Code adapts rapidly to project demands. Developers appreciate its streamlined interface. The tool enhances precision and reduces unnecessary code bloat. Augment Code's support for various coding styles is invaluable. ### 24. Aider by Aider AI [Aider](https://aider.chat/) by Aider AI brings advanced problem-solving capabilities. Its analytical tools improve code quality. Aider offers personalized development insights. This tool helps in achieving systematic code refinement. Aider facilitates teamwork with its communication features. It remains a cornerstone for developers prioritizing quality. ### 25. Claude Code by Anthropic [Claude](https://claude.ai) Code by Anthropic excels in transformative AI capabilities. It emphasizes code automation and strategic insights. Claude Code allows developers to stay ahead of trends. Its real-time assistance enhances developer creativity. Claude Code is renowned for its flexibility and adaptability. This tool continues to drive innovation and efficiency in coding. {% call_to_action title="All-in-one development platform" description="Use built-in backend infrastructure and web hosting, all from a single place." point1="Open source" point2="Start for free" point3="Support for over 13 SDKs" point4="Managed cloud solution" cta="Start building for free" url="https://cloud.appwrite.io/console/" /%} ### Comparing the best vibe coding tools: Features & use cases When comparing vibe coding tools, it's crucial to consider features and use cases specific to your project's needs. These tools offer varied capabilities tailored to different aspects of the development process, enhancing productivity through innovative technologies. Key features to evaluate include AI-driven suggestions, automation, integration with development environments, and support for various programming languages. Look for tools that provide real-time collaboration and adaptive learning capabilities. Tailoring your choice to specific functions can enhance your workflow dramatically. Here’s a quick list of features and uses to consider: - AI-driven suggestions: Improves coding speed and accuracy. - Automation: Reduces repetitive tasks, increasing efficiency. - Integration: Ensures compatibility with existing tools. - Real-time collaboration: Supports teamwork and communication. - Cross-platform support: Provides flexibility across devices. Choosing the right tool can lead to significant time savings and error reduction, empowering teams to focus on creative problem-solving and higher-level tasks. Selecting a tool that aligns with your development goals ensures a seamless and productive coding experience. Platforms like [Hugging Face](https://huggingface.co/) are commonly used by developers working with AI tools. It’s a place where you can find and run open-source models, explore datasets, and try out a wide range of community-built AI apps, including ones for code generation, bug detection, and language understanding. ### Challenges and considerations when adopting vibe coding tools Adopting vibe coding tools presents several challenges that teams must address. Transitioning to new technology can disrupt existing workflows. This can lead to temporary productivity dips as developers adapt. Security is another critical consideration. AI-driven tools often require access to sensitive codebases. Ensuring data privacy and protecting intellectual property are paramount. Thorough vetting of tools is essential before integration. Check out this guide to learn [security best practices for vibe coding](/blog/post/vibe-coding-security-best-practices). Consider the following when choosing vibe coding tools: - Compatibility with existing infrastructure - Comprehensive user training and support - Scalability and future-proofing potential Additionally, evaluating the costs associated with these tools is crucial. Balancing investment with expected productivity gains is key. Adopting new tools should ultimately enhance, not hinder, your team’s development efficiency. ### The future of vibe coding: Trends to watch in 2025 and beyond The future of vibe coding is set to redefine the software development landscape. It's driven by emerging technologies and evolving developer needs. AI-driven solutions will play a pivotal role in this transformation. Several trends are expected to shape vibe coding: - Software engineering redefined: Developers will shift from writing every line of code to curating, guiding, and validating AI-generated output. Repetitive tasks will be handled by AI, allowing engineers to focus on higher-level responsibilities like system architecture, integration, security, and problem-solving. - Deeper AI integration: AI is expected to impact every aspect of software development, and not just code generation or suggestions. From handling bugs and writing tests to improving security and managing infrastructure, AI will become a core part of the development workflow, acting as an always-on assistant across the entire lifecycle. - New roles will emerge: With AI handling multiple aspects of development cycle, some skills might be less demanding. We’ll likely see a rise in new roles like prompt engineers, AI strategists, and tooling specialists, people who know how to work with AI effectively. At the same time, skills like critical thinking, problem-solving, and system design will become even more important, as coding alone won’t be the main value driver anymore. ### Conclusion: Choosing the right vibe coding tool for your needs In 2025, vibe coding tools will offer diverse functionalities. Selecting the right one depends on specific requirements and goals. Developers should consider factors like compatibility, AI capabilities, and ease of integration. When choosing, prioritize: - Alignment with your development environment - Support for your preferred programming languages - Features that boost productivity and collaboration By evaluating these factors, you ensure the tool enhances your workflow. The right choice will transform your development process, making it more efficient and innovative. ### More resources - [Building with Appwrite AI Function templates](/blog/post/building-with-ai-function-templates) - [Announcing new Appwrite AI integrations](/blog/post/announcing-appwrite-new-ai-integrations) - [Build an offline AI chatbot with WebLLM and WebGPU](/blog/post/chatbot-with-webllm-and-webgpu) --- ## The top 6 Vector Databases to use for AI applications in 2025 https://appwrite.io/blog/post/top-6-vector-databases-2025 Search has changed. Traditional databases work great when you’re querying exact matches or running structured lookups. But when you start dealing with embeddings, similarity search, or AI-generated data, those systems quickly hit their limits. That’s where **Vector Databases** come in. They’re the backbone of modern AI applications, from retrieval-augmented generation (RAG) systems to recommendation engines. Instead of looking for exact values, they help you find “similar meaning” across high-dimensional vectors. And in 2025, we’ve reached a point where the space is both mature and rapidly evolving, with new players optimizing for speed, scale, and integrations with large language models. In this blog post, we’ll look at 6 popular vector databases that you can use for AI applications. ### What is a Vector Database? A vector database is built to store and query data represented as numerical vectors. In simple terms, it takes unstructured data, like text, images, or audio, and turns it into embeddings: numerical representations that capture the semantic meaning of that data. Once you have these embeddings, you can use mathematical operations (typically cosine similarity or Euclidean distance) to find items that are most similar to a given query vector. Unlike relational databases that rely on indexes like B-trees or hash maps, vector databases use specialized indexing techniques such as Hierarchical Navigable Small World (HNSW), Inverted File(IVF), or Product Quantization(PQ). These structures allow for efficient approximate nearest neighbour (ANN) searches across millions or billions of vectors. In practice, this means a vector database doesn’t just tell you which document contains the word “cat”. It can tell you which document is conceptually about cats, even if the word never appears in it. **Vector database examples** Popular vector databases include **Pinecone**, **Weaviate**, **Milvus**, **Qdrant**, and **Chroma**, all built specifically for storing and querying embeddings. Traditional databases like **PostgreSQL** (with `pgvector`) and **Elasticsearch** have also added vector capabilities, letting teams extend existing systems for semantic or similarity search. Each takes a slightly different approach to indexing, performance, and integration, which we’ll explore in detail next. ### 6 popular Vector Databases you should consider in 2025 #### 1. MongoDB Atlas [MongoDB Atlas](https://www.mongodb.com/products/platform) includes **native vector search**, allowing developers to store and query embeddings right alongside operational data. This setup lets you run transactional, analytical, and semantic workloads in one place, ideal for teams that don’t want to juggle multiple databases. It’s particularly effective for RAG-based applications, where large language models need access to live, contextual data. Because Atlas is already a production-grade managed database, adding vector functionality fits naturally into existing architectures without extra operational overhead. #### Key features of MongoDB Atlas - **Native Vector Search:** Store and query vector embeddings directly within MongoDB collections using Atlas Vector Search. - **RAG integration:** Works smoothly with retrieval-augmented generation pipelines for AI-driven applications. - **Unified platform:** Handles transactional, analytical, and vector workloads under a single environment. - **Workload isolation:** Scale vector search workloads independently for better performance and reliability. - **Cloud flexibility:** Available across major cloud providers, supporting embeddings from most model providers. - **Developer ecosystem:** Backed by documentation, quick-start guides, and tutorials for fast implementation. #### 2. Chroma [Chroma](https://www.trychroma.com/) is an open-source search and retrieval database designed for AI applications. It supports **vector**, **full-text**, **regex**, and **metadata** search out of the box, making it a versatile option for developers who want to build and iterate locally, then scale seamlessly to production. Built on object storage and optimized for cost-efficiency, Chroma handles billions of records across multi-tenant environments with low latency. It’s lightweight, easy to spin up with a simple `pip install chromadb`, and integrates well with popular frameworks like LangChain and LlamaIndex. #### Key features of Chroma - **Multi-modal search:** Supports vector, full-text, regex, and metadata queries for flexible retrieval use cases. - **Open source and Cloud ready:** Apache 2.0 licensed and offers a managed, serverless cloud version that scales automatically. - **High performance:** Designed for low-latency queries across billions of records with a distributed, multi-tenant architecture. - **Cost-efficient:** Uses object storage with automatic data tiering, offering up to 10x lower costs compared to traditional setups. - **Easy setup:** Quick local installation with Python or JavaScript clients; same API across local and cloud deployments. - **Integrations:** Built-in support for embedding models from Hugging Face, OpenAI, Google, and more. #### 3. Pinecone [Pinecone](https://www.pinecone.io/) is a fully managed vector database built specifically for high-performance similarity search and retrieval. It’s one of the earliest and most mature players in the space, offering production-grade infrastructure for applications that rely on embeddings. Pinecone abstracts away the operational side completely. You don’t manage indexes, scaling, or infrastructure. You just send vectors and query them through a simple API. It’s known for consistent latency, strong indexing performance, and deep integration with frameworks like LangChain, LlamaIndex, and Hugging Face. #### Key features of Pinecone - **Managed Vector Database:** Fully serverless and managed, handling indexing, scaling, and replication automatically. - **High-performance retrieval:** Optimized for low-latency, high-accuracy nearest neighbour searches at scale. - **Namespace & Metadata filtering:** Allows fine-grained control over queries using metadata and logical grouping. - **Scalable infrastructure:** Supports billions of vectors with predictable performance as datasets grow. - **Integrations:** Works seamlessly with popular AI frameworks and model providers like OpenAI and Cohere. - **Security and reliability:** Enterprise-grade availability, SOC 2 Type II compliance, and automatic redundancy. - **Easy onboarding:** Simple REST and Python APIs with SDKs for quick integration into AI and search pipelines. #### 4. Weaviate [Weaviate](https://weaviate.io/) is an open-source, AI-native vector database built for modern applications that combine structured and unstructured data. It supports both **vector** and **keyword (BM25)** search, allowing developers to perform **hybrid queries** that balance semantic relevance with exact matching. Weaviate stands out for its modular architecture and strong integration with machine learning frameworks. You can plug in pre-trained models from OpenAI, Hugging Face, Cohere, or use Weaviate’s own vectorizers to generate embeddings with ease. #### Key features - **Hybrid search:** Combines vector and keyword (BM25) search for more accurate and context-aware retrieval. - **RAG support:** Out-of-the-box support for retrieval-augmented generation pipelines with secure data integration. - **Vectorizer modules:** Easily connect external embedding models or generate vectors natively within Weaviate. - **Advanced filtering:** Apply complex filters across large datasets in milliseconds. - **Multi-Tenancy:** Built-in isolation and horizontal scalability for multi-tenant workloads. - **Flexible deployment:** Run self-hosted, use the managed Weaviate Cloud, or deploy in your own VPC via Kubernetes. - **Ecosystem integrations:** 20+ integrations across the ML stack, including LangChain, LlamaIndex, and Databricks. #### 5. Milvus [Milvus](https://milvus.io/) is an open-source, high-performance vector database built for AI and similarity search workloads at scale. It’s designed to handle billions of high-dimensional vectors efficiently, making it suitable for production-grade applications in machine learning, computer vision, and recommendation systems. #### Key features of Milvus - **Scalable architecture:** Supports tens of billions of vectors through a distributed system optimized for horizontal scaling. - **Deployment flexibility:** Available as Milvus Lite (local and lightweight), Standalone (single-node), Distributed (enterprise-scale), and **Zilliz Cloud** (fully managed and serverless). - **High performance:** Utilizes optimized indexing techniques and the Global Index for low-latency, high-accuracy similarity searches. - **Rich querying:** Offers hybrid search, metadata filtering, and multi-vector querying capabilities. - **Production-ready:** Backed by a large open-source community and used in production by companies like NVIDIA, IBM, and Salesforce. - **Tooling and ecosystem:** Includes CLI tools, sizing utilities, backup systems, and SDKs for multiple languages. #### 6. Qdrant [Qdrant](https://qdrant.tech/) is an open-source vector database and similarity search engine built for performance and scale. It’s designed to handle high-dimensional vector data efficiently, supporting a wide range of AI-driven use cases like recommendation systems, RAG pipelines, anomaly detection, and multimodal search. Written in Rust, Qdrant is optimized for speed and reliability. It offers flexible deployment options. You can run it locally using Docker, deploy it in your own infrastructure, or use **Qdrant Cloud**, a fully managed, enterprise-grade service with zero-downtime scaling. #### Key features of Qdrant - **High performance:** Rust-based engine optimized for low-latency similarity search, capable of handling billions of vectors. - **Cloud-native scalability:** Horizontal and vertical scaling with high availability and rolling upgrades. - **Efficient storage:** Built-in quantization and compression options to reduce memory footprint and offload data to disk. - **Flexible deployment:** Run locally via Docker or use managed services through Qdrant Cloud and Hybrid Cloud. - **Rich querying:** Supports advanced vector search, metadata filtering, and multi-vector queries for complex use cases. - **Integrations:** Works with major AI frameworks and embedding providers including OpenAI, Hugging Face, and LangChain. - **Reliability:** Enterprise security and compliance (SOC2, GDPR, HIPAA) with strong fault tolerance and replication. ### How to choose the right Vector Database? If you’re deciding which vector database to start with, here’s a simple way to narrow it down based on your needs: - **For quick experimentation or local prototypes:** Go with Chroma or Milvus Lite. Both are easy to set up locally (`pip install` simple) and great for testing RAG pipelines or small-scale projects. - **For production-grade managed solutions:** MongoDB Atlas or Pinecone are ideal if you want reliable performance, scaling handled for you, and minimal maintenance. - **For open-source flexibility and hybrid search:** Weaviate and Qdrant strike a good balance between control and performance, with rich ecosystems and modular deployment options. - **For tight integration with existing data systems:** If your operational data already lives in MongoDB, extending it with **Atlas Vector Search** is often the simplest route. ### Conclusion Vector databases have become an essential part of the modern AI stack. Whether you’re building a RAG system, a recommendation engine, or a semantic search feature, choosing the right database depends on what you’re optimizing for: scale, speed, flexibility, or simplicity. At the end of the day, it’s less about which database is “best” and more about how well it fits your architecture and workflow. Most of these systems are maturing fast, and experimenting with a few locally is the easiest way to understand their trade-offs, especially as new models, frameworks, and retrieval techniques continue to evolve. ### More resources - [Choosing the right AI database for your application in 2025](/blog/post/choosing-the-right-ai-database) - [Announcing TanStack Start support in Appwrite Sites](/blog/post/tanstack-start-support-in-appwrite-sites) - [Appwrite Sites now offers unlimited sites on the free plan](/blog/post/unlimited-appwrite-sites-free-plan) --- ## Top 8 Auth0 alternatives in 2025 https://appwrite.io/blog/post/top-auth0-alternatives With AI tools, APIs, and open-source platforms accelerating development like never before, building full-stack applications is no longer reserved for large teams. Developers today can spin up powerful, production-ready apps in days or sometimes even hours. But with this ease and speed comes an often overlooked trade-off: security. As more teams move faster and ship quicker, secure authentication becomes non-negotiable. You need a solution that's reliable, flexible, and built to scale. For a long time, Auth0 has been a popular solution for handling authentication and user management. It simplified complex auth flows and helped teams get to market quickly. However, as products scale, many developers start to feel friction like rising costs, limited flexibility, and vendor lock-in. In this blog, we'll explore the top alternatives to Auth0 that offer more control, better pricing, and greater developer freedom. Top 8 Auth0 alternatives in 2025 ### 1. Appwrite ![Appwrite](/images/blog/top-auth0-alternatives/Appwrite.png) - **Deployment**: Fully managed cloud service and self-hosted options. - **Start for free** [Appwrite](/products/auth) is an open-source, all-in-one development platform with a full [authentication](/products/auth) system, alongside other backend tools and web hosting capabilities. You can authenticate users securely with [multiple login methods](/docs/products/auth), such as Email/Password, SMS, OAuth, Anonymous, Magic URLs, and more. Appwrite is also fully GDPR, SOC-2, HIPAA, and CCPA compliant. #### Features: - **Supports 30+ login methods:** Email/password and [OAuth](/docs/products/auth/oauth2) providers. - **Role-based access**: [User management features](/docs/products/auth/teams) like roles, teams, and account verification. - **Built-in security**: Session management, [MFA(Multi-Factor Authentication)](/docs/products/auth/mfa), Session limit, Password dictionary, and more. - **Server-side rendering:** Optimize your auth with Appwrite's server-side SDK, enhancing your application's performance without sacrificing functionality. - **Appwrite console**: UI to easily manage users and privacy settings. - **Serverless functions:** Custom cloud functions to run server-side code, allowing you to extend or customize authentication flows and other backend logic. #### Does Appwrite have a free plan? Appwrite offers a generous free plan with up to 75,000 monthly active users. You can also self-host for free. #### What is Appwrite's pricing? Appwrite Cloud's Pro plan, with higher limits and advanced features, starts at $15/month for 200,000 users. Appwrite follows a transparent pricing model, where you pay $3 per additional 1,000 monthly active users beyond the included quota. #### When to use? Use Appwrite when you want a fully managed, all-in-one cloud platform with built-in auth, database, storage, serverless functions, and hosting, perfect for teams that want to move fast without stitching together multiple services. And since it's open-source, you always have the option to self-host and retain full data control if needed. ### 2. Frontegg ![Frontegg](/images/blog/top-auth0-alternatives/Frontegg.png) - **Deployment**: Fully managed cloud service. - **Start for free** [Frontegg](https://frontegg.com/) is a user management platform that provides pre-built identity and user account features for modern applications. It offers a suite of plug-and-play components (login box, user profile management, admin portals, etc.) that developers can easily integrate, especially for B2B SaaS scenarios. It has out-of-the-box support for complex B2B requirements, multi-tenant user hierarchies, role-based access control, enterprise SSO, and self-service admin dashboards, so you don't have to build those from scratch. #### Features - **Pre-Built UI components:** Ready-made, customizable widgets for authentication and user self-service (e.g., registration forms, organization switchers, account settings). - **Broad auth capabilities:** Supports SSO with SAML/OIDC, social logins, multi-factor authentication, and passwordless login (magic links, OTP) out-of-the-box - **B2B Multi-Tenancy & RBAC:** Built specifically for B2B SaaS, it has native constructs for organizations (tenants), user groups, roles, and hierarchical permissions. #### Does Frontegg have a free plan? Frontegg offers a free tier with up to 7,500 monthly active users. #### What is Frontegg's pricing? Frontegg offers custom pricing for companies that want to power deep customer identity use cases. #### When to use? Use Frontegg if you're building a B2B SaaS application where you need to manage multiple tenants under a single organization instance. Also great for teams who need admin UIs out-of-the-box. ### 3. ForegeRock ![ForegeRock](/images/blog/top-auth0-alternatives/ForegeRock.png) - **Deployment**: Fully managed cloud service. [ForgeRock](https://www.pingidentity.com/en.html) is a comprehensive enterprise IAM platform designed for large organizations with complex identity and security requirements. It originated from open-source projects (OpenAM, etc.) but is now a commercial suite offering authentication, authorization, user identity management, and identity federation in one package. #### Features - **Full IAM functionality:** Provides SSO (single sign-on) and even single sign-off, multi-factor authentication, passwordless auth, and advanced access management features like contextual/adaptive authentication (risk-based MFA prompts, etc.) - **User Management & Self-Service:** Includes user provisioning and identity management. Users get self-service capabilities such as account registration, password resets, and profile updates, which are configurable to enforce corporate policies. - **Modular, scalable architecture:** The platform is composed of modules that allows adding capabilities as needed (for example, just an SSO module vs. the full suite) for performance and scalability efficiency. #### Does ForegeRock have a free plan? Information is not publicly available. #### What is ForgeRock pricing? Information is not publicly available. #### When to use? Use ForgeRock for large enterprises or government-scale projects where security, flexibility, and integration with legacy systems are top priorities. It's overkill for simple apps but good for complex, regulated environments. ### 4. Keycloak ![Keycloak](/images/blog/top-auth0-alternatives/Keycloak.png) - **Deployment**: Cloud and self-hosted options. - **Start for free** [Keycloak](https://www.keycloak.org/) is a free, open-source identity and access management solution (backed by Red Hat) that you can run on your own infrastructure. It provides all the core authentication features (login, logout, SSO, MFA, social login) and supports standard protocols like OAuth2, OpenID Connect, and SAM. #### Features - **Standard Authentication services:** Handles user login, registration, logout, and Single Sign-On across multiple apps. It supports username/password, social identity logins, and multi-factor authentication (e.g. OTP) out-of-the-box - **User/Role management:** Provides a web-based admin console to manage users, groups, and roles. You can define roles at the realm (global) or client (application) level and assign fine-grained permissions. - **Customizable UI and themes:** The look and feel of login pages, emails, and account console can be customized using theme templates. #### Does Keycloak have a free plan? Keycloak, being open-source software, is free to use. #### What is Keycloak pricing? While Keycloak is free to use, #### When to use? Using Keycloak can be great when building an in-house platform or internal tool where an open-source solution can be tailored to your needs. ### 5. FusionAuth ![FusionAuth](/images/blog/top-auth0-alternatives/FusionAuth.png) - **Deployment**: Cloud and self-hosted options. - **Start for free** [FusionAuth](https://fusionauth.io/) is a developer-focused IAM platform that can be deployed on-premises or used as a managed cloud service, offering a flexible alternative to Auth0. It provides all the typical authentication features along with extensive customization and integration options. #### Features - **Full Auth feature set:** Supports user registration & profile management, email/password logins, social logins (OAuth providers), single sign-on, passwordless login (magic links), and multi-factor authentication (TOTP, etc.). - **Event-driven and extensible:** FusionAuth has an event/pub-sub architecture that lets you hook into events (like user login or registration) to trigger custom workflows. - **Customization and theming:** Every email template and login or registration UI can be customized. You can host FusionAuth's login pages or embed your own. #### Does FusionAuth have a free plan? FusionAuth offers a free community plan where you need to self-host FusionAuth on your own infrastructure, and you get the core authentication features. #### What is FusionAuth pricing? Cloud plans for FusionAuth start at $37 per month. #### When to use it? Use FusionAuth when you want control and customization in an auth platform without building from scratch. ### 6. Firebase Authentication ![Firebase](/images/blog/top-auth0-alternatives/Firebase.png) - **Deployment**: Cloud only. - **Start for free** [Firebase Authentication](https://firebase.google.com/docs/auth) is Google's fully managed authentication service that is part of the Firebase app development platform. It enables developers to add user sign-up/login to mobile or web apps very easily using Firebase SDKs, with Google handling the backend and infrastructure. #### Features - **Multiple Auth methods:** Supports email/password accounts, phone number auth, and federated identity logins via providers like Google, Facebook, Twitter, Apple, GitHub, etc. - **Managed infrastructure and scalability:** Firebase Auth is serverless from the developer's perspective. Google manages scaling, uptime, and security patches. - **Token-Based Authentication & APIs:** Issues secure JWT (Firebase ID tokens) for authenticated users, which can be used to call Firebase or your own APIs. #### Does Firebase Authentication have a free plan? Firebase auth offers a free Spark plan with up to 50k monthly active users. #### What is Firebase Authentication pricing? After the free 50,000 MAUs, Firebase Authentication uses a pay-as-you-go model where you are charged based on usage, including API calls and additional fees for services like SMS-based authentication. #### When to use? Use Firebase Auth if you're already part Firebase ecosystem, using other services offered by Firebase. Good for small apps and startups to build MVPs. ### 7. AWS Cognito ![Cognito](/images/blog/top-auth0-alternatives/Cognito.png) - **Deployment**: Cloud only. - **Start for free** [Amazon Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html) is a fully managed CIAM (Customer Identity and Access Management) service by AWS that lets you add user sign-up, sign-in, and access control to your applications. It's designed to integrate deeply with the AWS ecosystem. #### Features - **User Pools for Authentication:** At the core, Cognito User Pools are managed user directories that handle registration, login, password recovery, and user profile storage. - **Identity Federation:** Cognito can federate users from external identity providers. Identity Pools supports social logins (Google, Facebook, Apple, etc.) and SAML/OIDC integration for enterprise identity providers. - **AWS Service Integration:** It natively integrates with other AWS services. For example, Cognito can trigger AWS Lambda functions on certain events (like post-registration) to implement custom logic. #### Does AWS Cognito have a free plan? AWS Cognito offers a free plan with up to 50k monthly active users. #### What is AWS Cognito pricing? Beyond 50k monthly active users, you must pay according to your resource usage. #### When to use? AWS Cognito might make more sense if you're already using AWS infrastructure as it tightly integrates with your AWS resources. But, if are not deeply tied into AWS, other alternatives may provide a smoother developer experience. ### 8. Stytch ![Stytch](/images/blog/top-auth0-alternatives/Stytch.png) - **Deployment**: Cloud only. - **Start for free** [Stytch](https://stytch.com/) is a developer-first authentication platform that emphasizes passwordless security and a rich set of modern auth features. It provides infrastructure for both authentication and fraud prevention, enabling companies to easily implement passkeys, OTPs, magic links, and other user-friendly login experiences via API and SDKs. #### Features - **Comprehensive Auth & Fraud suite:** Stytch offers standard auth (email/password, social logins, multi-factor) and advanced passwordless options (magic links, email OTP, SMS OTP, WebAuthn/passkeys) along with built-in fraud detection tools like device fingerprinting and behavioral checks. - **API-first and customizable SDKs:** It provides REST APIs and front-end SDKs (JavaScript, React, mobile, etc.) that allow you to build completely custom UI/flows. - **B2B capabilities:** Despite its focus on smooth consumer logins, Stytch also has a B2B offering. It supports organization-based logins with multi-tenancy, SSO integration for enterprises, and just-in-time provisioning of users. #### Does Stytch have a free plan? Stytch offers a free tier with only 10k monthly active users. #### What is Stytch pricing? Stytch offers pay-as-you-go pricing and enterprise plans. #### When to use? It's a good choice for consumer apps or fintech products that want to go passwordless or add strong fraud protection easily. ### Auth0 alternatives overview | Features | Appwrite | Frontegg | ForgeRock | FusionAuth | Keycloack | Firebase Auth | Cognito | Stytch | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Free tier | Yes | Yes | No | Yes | Yes | Yes | Yes | Yes | | MAU(Free tier) | 75k | 7.5k | NA | 10,000 | Unlimited | 50,000 (usage-based) | 50,000 (User Pools) | 10,000 | | Pricing start at | $15/month | Custom | Custom | $37/month (cloud) | Free | Pay-as-you-go | Pay-as-you-go | Pay-as-you-go | | MAU (Paid tier) | 200k | Custom | Custom | Custom | Unlimited | Usage-based | Usage-based | Usage-based | | Type of pricing | Pay-as-you-go | Custom | Custom | Pay-as-you-go | Self-hosted free, Cloud fixed/usage | Pay-as-you-go | Pay-as-you-go | Pay-as-you-go | | Pricing per extra MAU | $3 per 1,000 users | Custom | Custom | Custom | Cloud plan dependent | Per-request/API usage | Per MAU after 50k | Based on usage | | Deployment | Cloud, Self-hosted | Cloud, Hybrid | Cloud, Self-hosted | Cloud, Self-hosted | Cloud, Self-hosted | Cloud only(Google cloud) | Cloud only (AWS) | Cloud only | | Open source | Yes | No | Not purely | No | Yes | No | No | No | | Security | Built-in Auth, MFA, RBAC | MFA, RBAC, SSO | SSO, Adaptive MFA, RBAC | MFA, JWT, Webhooks | SSO, MFA, OIDC, SAML | OAuth2, MFA, Firebase Rules | MFA, Device Tracking, Federation | MFA, Passkeys, Device Fingerprinting | | Compliance | GDPR, HIPAA, CCPA, SOC2 | GDPR, CCPA, SOC2, ISO | GDPR, SOC 2, HIPAA | GDPR, SOC 2, HIPAA | GDPR | GDPR, CCPA | GDPR, HIPAA, CCPA | SOC 2, GDPR | | User management | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ### Conclusion Choosing an authentication provider is a critical decision that shapes how your application handles [security](/docs/products/auth/security), scalability, and user experience. Whether you're building a simple web app, a multi-tenant SaaS platform, or an enterprise-grade product, your auth stack needs to be reliable, flexible, and aligned with your architecture. While Firebase, Cognito, and Frontegg are solid-managed options, developers wanting more control and flexibility often prefer open-source tools. That's where Appwrite can be a great choice, as an all-in-one development platform that not only handles authentication, but also includes a database, cloud functions, storage, realtime APIs, and hosting. If you're looking for a platform that gives you modern authentication plus the building blocks to scale your product, check out Appwrite and start building for [free](https://cloud.appwrite.io/console). ### More resources - [Appwrite Authentication docs](/docs/products/auth) - [Appwrite Authentication quick start doc](/docs/products/auth/quick-start) - [Appwrite Authentication overview](/products/auth) --- ## Top 10 startup incubators and accelerators in Australia (2025) https://appwrite.io/blog/post/top-startup-accelerators-australia Australia has quietly grown into one of the most exciting regions for startup activity, fueled by strong government support, world-class universities, and a maturing investor ecosystem. From fintech hubs in Sydney to deep tech innovators in Melbourne, the country is producing global startups with increasing consistency. But behind many of these success stories lies a foundational catalyst: incubators and accelerators. In this blog, we’ll explore what incubators and accelerators actually do, how they differ, what they offer, and the top 15 Startup programs in Australia that you can check out. ### The purpose of a startup incubator or accelerator While they're often mentioned together, incubators and accelerators serve different but equally valuable functions in the startup lifecycle. - **Incubators** are built for the earliest stages. Think: turning [ideas](/blog/post/startups-ideas-for-developers-2024) into prototypes, finding co-founders, or validating if there's even a market. They provide a safe runway where experimentation is encouraged, and failure is part of the process. - **Accelerators**, by contrast, are like growth sprints. Typically cohort-based and time-bound, they take startups with some traction and help them scale fast, helping you refine your product, sharpen your business model, and often pitch to investors at the end. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### The difference between a startup incubator and an accelerator | **Aspect** | **Incubator** | **Accelerator** | | --- | --- | --- | | **Stage of Startup** | Idea-stage or early concept | Early-stage with MVP or some traction | | **Duration** | Flexible, often 6–24 months | Fixed, typically 3–6 months | | **Structure** | Open-ended, ongoing mentorship and support | Structured program with clear milestones and curriculum | | **Funding** | May or may not provide funding | Usually provides seed funding in exchange for equity | | **Equity Taken** | Often none or very low | Typically 5–10% equity | | **Focus** | Product validation, team formation, idea development | Rapid growth, scaling, fundraising | | **Outcome** | MVP or validated business model | Fundraising-ready, investor pitch-ready startup | | **Mentorship** | Light or as-needed mentorship | Intensive, hands-on mentorship and access to a network | | **Demo Day** | Rare or optional | Common—culminates in a pitch to investors and partners | | **Example Use Case** | A solo founder refining an early concept | A small team ready to raise pre-seed or seed capital | ### 15 best incubators and accelerators for tech startups in Australia #### 1. [Startmate](https://www.startmate.com/) A premier Australian accelerator launched in 2011 with backing from Atlassian’s founders. It provides seed funding (historically ~$75,000) and a mentor-driven 3-month program to scale early-stage tech startups. Startmate is known for its extensive mentor network and for catalyzing some of Australia’s biggest startup successes. - **Industries:** Technology (SaaS, fintech, deep tech, etc.) - **Notable alumni:** **UpGuard** – cybersecurity risk management platform (part of Startmate’s 2012 cohort, now operating globally), **Bugcrowd** – crowdsourced bug bounty platform (Startmate 2013 alum pioneering ethical hacking solutions). #### 2. [Muru-D](https://www.telstra.com.au/business-enterprise/muru-d) A Telstra-backed accelerator (est. 2013) that invested in 10 startups per cohort with $75k funding for 6% equity. Over 100 startups graduated from muru-D’s 6-month program, which emphasized global ambition and provided access to Telstra’s network. Muru-D has been pivotal in Sydney’s startup scene, fostering diverse founders and hosting programs in Sydney, Singapore, and beyond. - **Industries:** Emerging tech (agtech, IoT, fintech, etc.) - **Notable alumni:** **FluroSat** – AI-powered crop health and agronomy platform (muru-D alum known for its remote sensing of farm yields), **Pixc** – e-commerce image processing service (muru-D alum that streamlines product photo editing for online retailers). #### 3. [Melbourne Accelerator Program](https://www.themap.co/) The University of Melbourne’s accelerator (launched 2012) and is one of Australia’s top university-led programs. It was ranked the world’s #8 university business accelerator in 2015. MAP provides funding, office space, and mentorship via its five-month program and boasts a strong track record in turning research-born ideas into businesses. - **Industries:** Varied – software, hardware, MedTech, clean energy, social enterprises - **Notable alumni:** **Nura** – audio-tech startup behind adaptive headphones (MAP alum famous for its award-winning personalized sound headphones), **Phoria** – extended reality (XR) startup (MAP alum creating immersive VR/AR experiences, later securing global partnerships). #### 4. [Antler](https://www.antler.co/residency/australia) The local arm of Antler's global startup generator was launched in Sydney in 2019. Antler takes cohorts of individual companies and helps form teams to build startups from scratch, investing at day zero. In its first Australian fund, Antler backed 100+ early-stage companies and was recognized in 2023 as the world’s most active seed-stage investor. Its intensive program and worldwide network give founders a springboard to scale internationally. - **Industries:** Agnostic (fintech, SaaS, AI, e-commerce, etc.) - **Notable alumni:** **Airalo** – the world’s first eSIM marketplace for travelers (Antler alum now serving 10M+ users, turning the roaming market on its head with digital SIM tech), **Volopay** – a fintech platform for business expense management (Antler alum offering corporate cards & spend tracking, which has scaled to 700+ clients across Asia). #### 5. [Slingshot](https://www.slingshotters.com/accelerators-and-startups/) Founded in 2013, Slingshot became Australia’s leading corporate accelerator program. It runs sector-themed accelerators in partnership with large corporations, enabling startups to secure enterprise customers and pilots. Slingshot’s programs (like HCF Catalyst, Lion Unleashed, and Qantas Avro) helped bridge startups with industries from healthcare to travel. - **Industries:** Corporate innovation (fintech, health, agrifood, travel, etc.) - **Notable alumni:** **Car Next Door** – peer-to-peer car sharing platform (Slingshot alum through the NRMA Jumpstart program, later acquired by Uber in 2022 to become Uber Carshare), **OpenAgent** – real estate agent comparison platform (Slingshot alum that raised multiple funding rounds to help sellers find top agents). {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} #### 6. [H2 Ventures](https://h2.vc/) A fintech-focused accelerator and VC fund launched in Sydney in 2014 (evolving from AWI Ventures). H2’s intensive 20-week accelerator (run out of the Sydney Startup Hub) invests in 8–16 startups per intake, providing seed capital and hands-on mentoring. It is a pioneer in fintech and produces the annual Fintech100 report of top global fintech innovators. With backing from partners like First State Super and Investec, H2 expanded to support data and AI startups as well. - **Industries:** Fintech, Insurtech, Data & AI - **Notable alumni:** **HashChing** – an online mortgage marketplace (H2 alum that raised $1M and disrupted home lending by connecting borrowers directly with brokers), **Spriggy** – a popular kids’ banking app (H2 alum offering prepaid cards and a money education platform for children, now used by hundreds of thousands of Aussie families). #### 7. [SproutX](https://www.sproutx.com.au/) Australia’s first dedicated agtech and foodtech accelerator, founded in 2016 with support from the National Farmers’ Federation. SproutX runs a 6-month program plus a pre-accelerator, helping agrifood startups access industry mentors, test on farms, and secure seed funding. It aims to drive innovation toward Australia’s goal of a $100B agriculture sector by 2030. Over 40 startups have been supported, creating new solutions in farm management, supply chain, and sustainability. - **Industries:** Agriculture technology, Food technology, Sustainability (agri-biotech, farm IoT, alt-protein, etc.) - **Notable alumni:** **AgUnity** – a blockchain-based platform improving supply chain and financial access for small farmers (SproutX alum recognized for connecting thousands of farmers in developing countries), **TeleSense** – an IoT grain storage monitoring startup (SproutX portfolio company using sensors and AI to reduce spoilage in grain silos). #### 8. [Founder Institute](https://fi.co/) The local chapters of the world’s largest pre-seed startup accelerator (FI, founded in Silicon Valley in 2009). FI Australia runs a rigorous part-time four-month program that guides first-time founders from idea to product-market fit through weekly training and mentorship. The program is equity-based and has produced graduates in Sydney, Melbourne, and beyond, leveraging FI’s global network of mentors. - **Industries:** Technology startups (consumer internet, B2B SaaS, etc.) at ideation stage - **Notable alumni:** **Udemy** – global online learning marketplace (FI Silicon Valley alum now a NASDAQ-listed edtech company with 200,000 courses), **Oddbox** – UK-based “ugly produce” delivery service (FI London alum tackling food waste, raised £16M to expand its fruit & veg box subscriptions). #### 9. [Startupbootcamp Australia](https://www.startupbootcamp.com.au/) Part of the global Startupbootcamp network, this accelerator launched in Melbourne in 2018 with programs in fintech, smart energy, and digital health. It offers intensive 3-month mentorship-driven cohorts, $25K–$50K in seed funding, and industry partner support to rapidly scale startups. Startupbootcamp’s Australian programs have a strong corporate backing and an emphasis on preparing startups to land pilots and investments by Demo Day. - **Industries:** Fintech, Insurtech; Digital Health; Energy & Smart City tech - **Notable alumni:** **MedAdvisor** – medication management app that helps patients and pharmacies track prescriptions (SBC alumni that have grown to millions of users and listed on the ASX), **Deferit** – bill payments fintech allowing users to pay bills in installments. #### 10. [Cicada Innovations](https://www.cicadainnovations.com/) Australia’s leading deep tech incubator, founded in 2000 and owned by four universities. With a 20+ year track record, Cicada has nurtured science-based ventures in areas like biotech, medtech, agritech ,and advanced manufacturing. It offers laboratories, specialized equipment, and long-term incubation for R&D-intensive startups, plus training and commercialization support tailored to scientists and engineers. Cicada (formerly ATP Innovations) was recognized as the world’s top incubator in 2014 and continues to foster breakthroughs (over 700 patents from its residents). - **Industries:** Deep technology – biotechnology, medical devices, clean energy, aerospace, advanced materials - **Notable alumni:** **Morse Micro** – fabless semiconductor company developing long-range Wi-Fi chips (incubated at Cicada, now a $300M+ startup reinventing Wi-Fi for IoT), **Gelion** – battery technology company creating zinc-bromide gel batteries. ### Conclusion Accelerators and incubators play a crucial role in helping startups get off the ground. Whether you're refining an idea or gearing up for rapid growth, these programs offer the mentorship, structure, and community needed to navigate early challenges. In Australia’s thriving startup ecosystem, they’ve become key to turning local innovation into global success stories. If you’re building a startup, check out the Appwrite Startup Program. Appwrite gives you everything you need to build fast. Authentication, databases, functions, and hosting, all in one place. You also get exclusive discounts on paid plans, priority support, and access to a global developer community. [Apply now](/startups) --- ## Top 30 startup incubators and accelerators in the EU (2025) https://appwrite.io/blog/post/top-startup-accelerators-europe In the startup world, having a brilliant idea is only the beginning. The real challenge lies in execution, validation, and growth. This is where incubators and accelerators come in. These programs are designed to support early-stage startups with the tools, guidance, and networks they need to succeed faster and more efficiently. Whether you're a solo founder with a prototype or a team looking to scale, understanding the role of incubators and accelerators can be a game-changer. In this blog, we will go in-depth of what Incubators and Accelerators are, what they offer and top 30 Incubators and Accelerators in Europe region. ### The purpose of a startup incubator or accelerator Both incubators and accelerators exist to help startups grow, but they do so in slightly different ways: - **Incubators** are built to nurture early-stage [ideas](/blog/post/startups-ideas-for-developers-2024). Their goal is to help founders shape and refine their business models, build their MVPs, and gain traction. Incubators typically operate on an open-ended timeline and may not require startups to have a fully formed product. - **Accelerators** are for startups that already have a product or [MVP](/blog/post/how-can-you-rapidly-build-an-mvp-for-your-startup) and are ready to grow quickly. These programs usually run for a fixed term (e.g., 3-6 months) and culminate in a pitch or demo day. The goal is to accelerate progress, secure funding, and scale the business. ### The difference between a startup incubator and accelerator | **Feature** | **Incubator** | **Accelerator** | | --- | --- | --- | | Stage of Startup | Idea or pre-product | MVP or early revenue | | Duration | Flexible | Fixed (usually 3-6 months) | | Focus | Product development, business model | Rapid growth, fundraising | | Funding | Often none or minimal | Usually includes seed investment | | Structure | Ongoing mentorship | Cohort-based with curriculum and timeline | | Demo Day | Optional | Standard component | ### What incubators and accelerators offer Both incubators and accelerators provide critical resources to founders. Here's what startups typically get: - **Mentorship**: Access to experienced entrepreneurs, investors, and operators who can provide strategic and operational guidance. - **Funding**: Many accelerators offer seed funding in exchange for equity. Some incubators offer grants or stipends. - **Workspaces**: Physical or virtual co-working spaces, allowing founders to focus full-time on their startup. - **Workshops & Curriculum**: Structured learning on product, growth, fundraising, legal, hiring, and more. - **Community**: A cohort or alumni network of other founders, creating a support system and potential partnership opportunities. - **Investor Access**: Exposure to venture capitalists and angel investors, often through demo days or pitch events. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### 30 best incubators and accelerators for tech startups in Europe #### 1. [UnternehmerTUM](https://www.unternehmertum.de/en) One of Europe’s largest entrepreneurship centers, UnternehmerTUM is the startup incubator of TU Munich, founded in 2002 by Susanne Klatten as a non-profit. It runs **20+ programs** guiding founders from prototyping to fundraising. It was ranked the #1 EU startup hub in 2025 by FT/Sifted. - **Location**: Munich, Germany - **Focus industries:** Broad high-tech innovation (no specific sector focus)– from AI and mobility to biotech – often with an emphasis on deep tech and industrial tech. - **Top alumni:** **FlixMobility (FlixBus)** in transportation, **Celonis** in process mining, and **Isar Aerospace** in spacetech. #### 2. [Station F](https://stationf.co/) Station F is the world’s largest startup campus, a 34,000 m² facility opened in 2017 with backing from billionaire Xavier Niel. It provides office space for 1,000+ startups and hosts 30+ programs, from its in-house Founder/Fighters incubators to corporate accelerators by Facebook, Microsoft, Ubisoft, etc. - **Location:** Paris, France - **Focus industries:** Broad tech and digital innovation (no single sector focus) – Station F supports startups ranging from AI and fintech to biotech. - **Top alumni: Hugging Face** (AI platform, now a unicorn), and a new wave of buzzy AI startups like **Mistral AI** (genAI), **PhotoRoom** (AI imaging), and **Poolside** (AI infrastructure). #### 3. [Start2](https://www.start2.group/) Formerly known as German Entrepreneurship, Start2 Group operates the German Accelerator program, helping scale German startups internationally. Founded in 2008, it offers courses on fundraising, global expansion, and provides free office space and matchmaking for participants. It connects startups with corporates, investors, and government agencies to facilitate growth. - **Location:** Munich, Germany - **Focus industries:** No specific focus. Supports a range of tech startups (fintech, sustainability, B2B SaaS, etc.), emphasizing scaling “beyond Germany.” Notably, it is strong in helping later-stage startups prepare for global markets. - **Top alumni:** **Spreadly** (fintech platform) and **Footprint Intelligence** (sustainability management SaaS) #### 4. [HEC Paris Incubator & Accelerator](https://www.hec.edu/en) HEC Paris, a top business school, runs the Innovation & Entrepreneurship Institute which hosts accelerators and an incubator at Station F. Founded in 2017, it offers programs for both commercial startups and social ventures. For example, HEC’s 11-week Launchpad accelerator helps founders go from idea to launch, and their separate Inclusion accelerator focuses on women entrepreneurs and social impact businesses. - **Location:** Paris, France - **Focus industries:** Broad, with a tilt towards student- or alumni-led ventures. Sectors vary from fintech and AI to education and sustainability. - **Top alumni:** **PriceMatch** (yield management, acquired by Booking.com) and **Leetchi** (fintech, acquired by Crédit Mutuel Arkéa). #### 5. [Lanzadera](https://lanzadera.es/) Lanzadera is a major Spanish startup accelerator and incubator founded in 2013 by Juan Roig (owner of Mercadona) in Valencia. Part of the Marina de Empresas complex, it offers personalized in-person acceleration ****(“Shuttle” program), where startups get 6+ months of mentoring, access to corporates, and even zero-equity loans. - **Location:** Valencia, Spain - **Focus industries:** Broad. It supports startups in health, sports, fashion, food, SaaS, etc. - **Top alumni:** **Flywire** (payments, now NASDAQ-listed), which had early support in Spain, **Jeff** (on-demand services platform), and **Chronoexpert** (online luxury marketplace). #### 6. [SSE Business Lab](https://www.hhs.se/en/outreach/sse-initiatives/sse-business-lab) The Stockholm School of Economics Business Lab is an incubator for SSE students and alumni. Established in 2001, it offers three programs – Ideate, Activate, and Incubate – catering to entrepreneurs from the idea stage up to scaling startups. Participants get mentorship, access to SSE’s investor network, and free office space in central Stockholm - **Location:** Stockholm, Sweden - **Focus industries:** Broad tech and digital startups typically have a strong business model. Many teams are fintech, e-commerce, or mobility oriented - **Top alumni: Klarna** (the buy-now-pay-later fintech giant) and **Voi Technology** (e-scooter sharing). #### 7. [Startup Autobahn](https://group.mercedes-benz.com/innovation/venture/startup-autobahn/en/) Startup Autobahn is a corporate-driven accelerator launched in 2016 by Mercedes-Benz in partnership with Silicon Valley’s Plug and Play and other corporates. Based in Stuttgart, it connects startups with automotive and manufacturing giants. The program (named after the German “Autobahn”) runs themed batches focusing on mobility, Industry 4.0, and supply chain tech, providing pilots with partners like Mercedes, Porsche, ZF, and DHL. - **Location:** Stuttgart, Germany - **Focus industries:** **Transportation & mobility** tech is a core focus (connected cars, logistics, EV, etc.), along with deep tech solutions for manufacturing and enterprise (AI, IoT, materials). - **Top alumni:** **What3Words** (the location addressing startup) and **Gapless** (vehicle blockchain records) #### 8. [ESA BIC](https://commercialisation.esa.int/esa-business-incubation-centres/) The ESA Business Incubation Centre is Europe’s largest network of incubators for **space-related startups**. Founded in 2004, ESA BIC’s headquarters and flagship site is in Paris, and it coordinates **29 regional hubs** across Europe. Startups in the program get up to €50k non-equity funding, technical support from ESA and partner research institutes, and business mentorship to commercialize space tech. - **Location:** European Space Agency BIC, headquartered in Paris, France - **Focus industries:** Space and aerospace innovation – including satellite technology, Earth observation, space communications, navigation, rocket/aerospace engineering, and downstream applications. - **Top alumni:** **Spire Global** (nanosatellite constellation for data, initially supported by ESA BIC UK), **DTU Space (NanoRacks Europe)** in Denmark, and **Isar Aerospace** (Germany’s private rocket launcher, which benefited from ESA networks). #### 9. [InnovX](https://innovx.com/) InnovX is a leading Romanian accelerator (founded ~2019) that has quickly gained EU-wide recognition (ranked #16 startup hub in Europe in 2025). It runs intensive cohorts for startups and scale-ups, often in partnership with banks and corporates. InnovX provides training, mentoring, and investor pitching opportunities and even has programs for mid-size tech companies (MidCorp) and a pre-accelerator for students. - **Location**: Bucharest, Romania - **Focus industries:** **Technology and innovation** broadly – with an emphasis on fintech, cybersecurity, and emerging tech. - **Top alumni: TypingDNA** (AI-driven typing biometrics, later backed by Google), **Bunnyshell** (cloud devops automation, which raised multi-million rounds), and **Symphony** (fintech software acquired by an Austrian firm). #### 10. [Unicorn Factory Lisboa](https://unicornfactorylisboa.com/) Unicorn Factory (formerly “Startup Lisboa”) is an initiative by the city of Lisbon that was launched in 2012 to boost the local ecosystem. It serves as a combined incubator and accelerator hub, supporting founders from the idea stage through scaling. Programs include a general incubation track and a “Scaling Up” accelerator for later-stage startups, along with international soft-landing programs. - **Location:** Lisbon, Portugal - **Focus industries:** Technology startups across **various industries** – from fintech and e-commerce to tourism, mobility, and social impact. Given Lisbon’s strengths, many are web/mobile startups, but the Factory also supports biotech and creative industry ventures. - **Top alumni:** **Unbabel** (AI-powered translation platform, now a global company) and **Musiversal** (music-tech startup). #### 11. [Dogpatch Labs & NDRC](https://dogpatchlabs.com/ndrc/) Dogpatch Labs is a major startup hub in Dublin’s Docklands (the “Silicon Docks”), housed in the historic CHQ Building. Since 2015, it has provided co-working space and community events, and in 2020, it took over running the NDRC – Ireland’s national accelerator program. Through NDRC, Dogpatch runs equity investment accelerator cohorts and regional pre-accelerators, offering €100k investment for startups and intensive mentoring. - **Location**: Dublin, Ireland - **Focus industries:** General tech and digital startups (no specific vertical) – from SaaS and fintech to health tech and agri tech. As a national program, NDRC prioritizes strong teams with global potential across all sectors. - **Top alumni:** **Intercom**, Ireland’s first unicorn (customer messaging platform), launched out of Dogpatch’s community. NDRC alumni include **Nuritas** (AI-driven biotech), **SilverCloud Health** (digital health platform acquired by Amwell), and **Boxever** (travel tech, acquired by Sitecore. #### 12. [Accelerace](https://accelerace.io/) Accelerace, established in 2008, is Denmark’s top startup accelerator and one of the Nordics’ earliest. It is an accelerator VC that runs a **5-month program** offering mentorship, business development, and an optional €60k investment. Accelerace has both general tracks and vertical programs (e.g. in life sciences and cleantech) and has supported over **1,000 startups** to date. - **Location:** Copenhagen, Denmark - **Focus industries:** Diverse – including **fintech, SaaS, healthcare, food tech, and Energy**. - **Top alumni:** **Trustpilot** (consumer reviews platform turned unicorn) and **Templafy** (enterprise document management). #### 13. [Startup Wise Guys](https://startupwiseguys.com/) Startup Wise Guys (SWG) is a privately-funded accelerator founded in Estonia in 2012, now operating in multiple EU countries. It is one of Europe’s most active early-stage investors, focusing on high-growth tech startups. SWG runs intensive **5-month accelerators** offering ~€50k investment for ~9% equity and has programs in various cities (Tallinn, Riga, Vilnius, Copenhagen, Milan, etc.). They have accelerated over 350 startups across 9+ cohorts annually. - **Location:** Tallinn, Estonia – and Pan-Europe - **Focus industries:** Primarily B2B **SaaS, fintech, cybersecurity, and sustainability** startups. Wise Guys is known for its expertise in enterprise software and has also launched focused tracks (e.g., Fintech in collaboration with fintech hubs, CyberNorth for cybersecurity). - **Top alumni:** Wise Guys helped nurture **Pipedrive** in its very early days (the CRM unicorn had SWG mentors, as its co-founder is an SWG alum mentor), and **StepShot** (process documentation SaaS, acquired by UiPath). #### 14. [Rockstart](https://rockstart.com/) Rockstart is a prominent European accelerator founded in Amsterdam in 2011. It has since evolved into a domain-focused accelerator VC. Rockstart runs specialized programs in AgriFood (based in Copenhagen) and Energy (based in Amsterdam), and formerly ran programs in AI/Emerging Tech. Each program provides ~€100k in initial funding (convertible loan) and access to follow-on capital from Rockstart’s funds. The programs last ~5 months and combine mentoring with pilot opportunities. - **Location:** Amsterdam, Netherlands & Copenhagen, Denmark - **Focus industries**: AgriFood Tech (covering agriculture, food supply chain, sustainability) and Energy Tech (renewables, energy efficiency, smart grid) are current core focuses. - **Top alumni:** **Wercker** (Dutch DevOps startup acquired by Oracle) and **3D Hubs** (on-demand manufacturing marketplace acquired by Protolabs). {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} #### 15. [Wayra](https://www.wayra.com/) Wayra is the corporate accelerator of Telefónica, which was launched in 2011. With hubs in Madrid, Barcelona, and other cities (as well as Latin America), Wayra combines an **innovation program and a venture fund**. Startups get investment (typically €50–150k), co-working space, and access to Telefónica’s business units and customers. - **Location:** Madrid & Barcelona, Spain – plus European hubs - **Focus industries:** Telecom, internet and digital tech broadly – from IoT and networking to fintech, video, AI, and cybersecurity – especially where a startup’s product could integrate with Telefónica (Movistar/O2) services or corporate clients. - **Top alumni:** **Voicemod** (Valencia-based voice modulation app, now with millions of users) is a notable alum that Wayra invested in early. **SafeToNet** (UK, child online safety AI) and **Trustev** (fraud prevention, acquired by TransUnion) are other success stories from Wayra Europe. #### 16. [Startupbootcamp](https://www.startupbootcamp.org/) Startupbootcamp (SBC) is a global accelerator network founded in Copenhagen/Amsterdam in 2010. In Europe, it has run programs in cities like Amsterdam, London, Berlin, Barcelona, etc., often with a vertical focus. Programs are ~3 months and offer €15k seed funding for 6-8% equity plus perks, office space, and mentorship. SBC has an alumni network of over 700 startups across its programs. - **Location:** Amsterdam, Netherlands, and multiple EU cities - **Focus industries:** Various vertical accelerators – e.g., **FinTech & CyberSecurity (Amsterdam)**, **Smart Cities & IoT**, **Digital Health**, **FoodTech**, **Commerce**, etc., depending on location and corporate partners. - **Top alumni:** One flagship success is **Relayr**, an Industrial IoT startup from SBC Berlin that was acquired for $300M by Munich Re. Other alumni include **Funding Circle** (P2P lending, went public in the UK), **SensorFlow** (proptech, Asia expansion), **Kypto** (digital banking, acquired), and **CareMonkey** (edtech, rebranded as Operoo, expanded globally). #### 17. Sting – Stockholm Innovation & Growth Sting is Stockholm’s leading independent incubator and accelerator, and it has been operating since 2002. It offers both an incubator (longer-term support) and the Sting Accelerate program (a 4-month accelerator that includes a €30k investment). Sting provides startups with mentorship, free office space, and access to its business angel network. Over **330 startups** have been coached by Sting since its inception. - **Location:** Stockholm, Sweden - **Focus industries:** Broad tech, with strengths in **sustainability, deep tech, and digital innovation**. Many Sting companies are in **climate/cleantech, fintech, healthtech, and SaaS** – reflecting Stockholm’s diverse tech scene. - **Top alumni:** **Yubico**, the inventor of the YubiKey security device, was a Sting incubator company. **Karma** (food waste marketplace app) and **Sellpy** (second-hand fashion, later majority-acquired by H&M) are also Sting alumni. #### 18. [imec.istart](https://www.imecistart.com/en) imec.istart is Belgium’s leading tech startup accelerator, launched in 2011 by imec (a top European nanoelectronics R&D institute). It offers an 12-18 month incubation program with an initial €50k investment (for 6% equity) and comprehensive coaching. imec.istart focuses on very early-stage startups and helps with prototyping, business development, and raising follow-up funding. - **Location:** Leuven, Belgium - **Focus industries:** Pure **technology and deep-tech startups** – “the only accelerator in Belgium with a pure focus on technology”. This spans AI, IoT, enterprise software, hardware devices, and science-based spin-offs. - **Top alumni:** **DataCamp** (online data science learning platform) and **Ontoforce** (bioinformatics search engine. #### 19. [YES!Delft](https://yesdelft.com/) YES!Delft, founded in 2005, is the incubator and accelerator associated with the Delft University of Technology. It brands itself as “Europe’s leading tech incubator”. With locations in Delft and The Hague, YES!Delft runs programs for startups at various stages: validation, acceleration, and growth. It also provides specialized support in AI, blockchain, robotics, aviation, and medical tech via thematic labs. - **Location:** Delft, Netherlands - **Focus industries:** **Deep technology and engineering-driven startups**. This includes robotics, drones, aviation/aerospace, clean-tech, medtech, and AI/software – many leveraging TU Delft’s engineering prowess. - **Top alumni:** **Ampelmann** (offshore access engineering, now a global company), **Epyon** (fast-charging tech, acquired by ABB), and **Somnox** (the world’s first sleep robot). #### 20. [PoliHub](https://polihub.it/en/) PoliHub is the startup incubator of Politecnico di Milano (Italy’s leading technical university), managed by the university’s foundation. Launched in 2003, it has grown into a large innovation district hosting dozens of startups and scale-ups. PoliHub offers incubation (with mentorship, grants, and office space) and an accelerator program for high-tech startups, often leveraging the university’s research. - **Location:** Milan, Italy - **Focus industries:** **Deep tech, engineering, and science-based startups** – e.g., AI, IoT, advanced materials, aerospace, fintech, and industry 4.0. - **Top alumni:** PoliHub helped launch **Musement**, a travel activities marketplace (acquired by TUI Group), and **Empatica**, a wearable health-tech company whose devices are FDA-cleared (known for seizure-detecting wristbands). #### 21. [H-Farm](https://www.h-farm.com/en) H-Farm is a unique innovation center and accelerator located on a countryside campus near Venice. Founded in 2005, it was one of Europe’s first startup incubators and later became a publicly listed innovation platform. H-Farm combines a startup accelerator, a venture investment arm, and even an international coding school on its 50-hectare campus. It provides seed funding, mentorship, and a creative co-working environment for digital startups. - **Location:** Roncade, Italy - **Focus industries:** **Consumer internet, digital media, and tech-enabled services**. H-Farm’s early focus was on digital consumer startups (e-commerce, social media, mobile apps), and it has since expanded into edtech (running school programs) and offers corporate innovation labs. - **Top alumni:** **Treatwell (former Wahanda)** in beauty booking (expanded across Europe), **Cortilia** (online grocery, now a scale-up in Italy), and **Zooppa** (creative crowdsourcing platform). #### 22. [Eleven Ventures](https://www.11.vc/) Eleven started in 2012 as one of Central Eastern Europe’s first accelerators, based in Sofia. It initially ran an accelerator program (modeled on Techstars/Y Combinator) that invested €25k for equity and provided workspace and mentorship. Over time, Eleven evolved into a seed VC fund (Eleven Ventures) but still operates acceleration-style investments and a coworking space. It has backed 150+ startups in its accelerator/seed programs. - **Location:** Sofia, Bulgaria - **Focus industries:** Broad tech – including **software, digital media, marketplaces, and fintech** – with a slight emphasis on B2B and mobile solutions. - **Top alumni:** **Payhawk**, a Bulgaria-founded fintech (expense management) that became a unicorn in 2022, received early investment from Eleven. Another success is **Tiimo** (a Danish startup in Eleven’s portfolio, now a popular life-planner app). #### 23. [INiTS](https://www.inits.at/en/viennas-high-tech-incubator/) INiTS (Innovation into Business) is Vienna’s high-tech incubator, founded in 2002 as a joint initiative of the University of Vienna, TU Wien, and the Vienna Business Agency. It runs a competitive startup camp/accelerator program called Scaleup twice a year, offering intensive coaching, some seed capital, and office space. INiTS focuses heavily on turning academic research and innovative tech ideas into viable businesses. - **Location:** Vienna, Austria - **Focus industries:** **Life sciences, biotech, medtech, and IT/tech** startups. As a university incubator, it often takes on scientific innovations – whether a new medical device, a chemistry breakthrough, or software from a research lab. - **Top alumni:** INiTS helped incubate **mySugr**, a diabetes management app that gained over 1 million users and was acquired by Roche in 2017 (one of Austria’s biggest startup exits). Another alumnus is **Runtastic** (fitness app, acquired by Adidas for €220M), which got early support from INiTS. #### 24. [EIT Climate-KIC Accelerator](https://climaccelerator.climate-kic.org/) The Climate-KIC Accelerator is the EU’s flagship program for climate innovation startups, run by EIT (European Institute of Innovation & Technology). Launched in 2010, it operates through regional centers in countries like Germany, Netherlands, Scandinavia, etc., and provides a staged accelerator (usually three stages) with grants up to ~€95k (non-dilutive), coaching and access to a huge climate-focused network. - **Location:** Pan-European - **Focus industries:** **Climate and clean technology** – including renewable energy, energy efficiency, circular economy, sustainable agriculture, waste management, urban mobility, carbon capture, and climate fintech. - **Top alumni:** The portfolio includes climate tech stars like **Tado°** (smart thermostats for energy savings) and **Ŷnsect** (French insect farming for protein), as well as **Volocopter** (German electric air taxis)– all considered among Europe’s top high-growth climate startups. #### 25. [Katapult](https://katapult.si/) Katapult is a hardware startup accelerator and incubator in Trbovlje, Slovenia, launched in 2016 by Dewesoft (a successful Slovenian tech company). Katapult was created to revitalize the local economy by helping hardware and manufacturing-oriented startups. It provides office and industrial space, guidance in manufacturing processes, and seed investment to early-stage companies. - **Location:** Trbovlje, Slovenia - **Focus industries:** **Hardware, industrial tech, and physical product startups**. This includes electronics, IoT devices, machinery, robotics, and even consumer products – any venture that needs to prototype and produce a physical product at scale. - **Top alumni:** Katapult has already produced startups of note: **Ironate** (a specialty stovetop pizza oven that gained international sales), **Equa** (smart water bottles), and **Vibrate** (music analytics platform – software, but supported in the early stage by Katapult’s ecosystem). #### 26. [EGG – Enter•Grow•Go](https://www.theegg.gr/en/) EGG is a leading Greek startup incubator and accelerator, launched in 2013 by Eurobank in cooperation with Corallia (a Greek innovation cluster). Over a 12-month program, EGG offers startups mentoring, entrepreneurship training, networking, and coworking space. It has been a cornerstone of the nascent Greek startup ecosystem, having hosted around 1,600 young entrepreneurs over 13 years. - **Location:** Athens, Greece - **Focus industries:** **Wide-ranging**, from ICT, fintech, and e-commerce to tourism tech, gaming, and social innovation. - **Top alumni:** **Blueground** (proptech unicorn for furnished apartments) received early support and mentoring via EGG. **Pollfish** (survey platform acquired by Prodege) is another alum. **Truckbird** (logistics marketplace) and **Tenderio** (procurement platform) are examples of EGG graduates that scaled beyond Greece. #### 27. [ZICER – Zagreb Innovation Centre](https://www.zicer.hr/?lang=en) ZICER is a technology business incubator in Zagreb, established back in 1994 (originally as a entrepreneurship center – BICRO). Now city-supported, ZICER provides modern coworking and lab facilities at Zagreb’s Fair compound and runs incubation and pre-acceleration programs. - **Location:** Zagreb, Croatia - **Focus industries:** **Technology startups of all kinds** – ICT, electronics, biotech, robotics, and more. There’s no strict vertical limitation; ZICER has housed startups from an AI-driven marketplace to a company making electric bicycles. - **Top alumni:** Early on, ZICER supported companies like **Infobip** in its ideation stage – Infobip is now Croatia’s first unicorn (global cloud communications platform), and while it didn’t go through a formal accelerator, ZICER provided ecosystem support in its formative years. #### 28. [House of Startups](https://www.host.lu/) The House of Startups (HoST) is a startup campus and incubator in Luxembourg City, created in 2018 by the Luxembourg Chamber of Commerce. It houses several incubation programs under one roof – including the Luxembourg-City Incubator (for early-stage startups), Luxembourg House of Financial Technology (LHoFT, for fintech), and others like the ICFA (for climate finance). In total, HoST spans 4,000 m² of space for startups and innovation initiatives. - **Location:** Luxembourg City, Luxembourg - **Focus industries:** **Finance & fintech** is a major focus (befitting Luxembourg’s financial hub status). - **Top alumni:** **Tokeny** (blockchain platform for tokenizing assets) is a notable alum from LHoFT that raised funding from Euronext. **Seqvoia** (regtech for fund management) and **Next Gate Tech** (fund operations automation) are fintech alumni that have grown significantly. #### 29. [BIC Euronova](https://www.bic.es/eng/what-is-bic) BIC Euronova is a European Business Innovation Centre in Málaga, Spain, founded in 1991 as part of the EU’s BIC network. Located in Malaga TechPark (Parque Tecnológico de Andalucía), it offers incubation for innovative startups and SMEs via offices, mentoring, and access to funding. BIC Euronova has been a hub for tech entrepreneurship on the Costa del Sol for over three decades. - **Location:** Málaga, Spain - **Focus industries:** **Technology and innovative SMEs** broadly – including ICT, electronics, engineering, and environmental tech. - **Top alumni:** Recent alumni include **Freepik** (online graphic resources, which became one of Spain’s top web platforms) and **Tiendeo** (digital catalog platform acquired by ShopFully). These successes underscore Málaga’s rise as a tech hub with BIC Euronova at its center. #### 30. [Seedcamp](https://seedcamp.com/) A London-based seed fund and accelerator founded in 2007, known for its strong track record in nurturing early-stage tech startups. Notably, its portfolio includes over 460, startups with nine reaching unicorn status (billion-dollar valuation). - **Location**: London, United Kingdom - **Industries:** Sector-agnostic, with an emphasis on software-driven startups in areas like artificial intelligence, cybersecurity, health tech, and fintech - **Top alumni: Wise (formerly TransferWise), Revolut, and UiPath** ### Conclusion If you’re a founder in the EU (or planning to expand here), exploring the right incubator or accelerator could be the launchpad your startup needs. Take your time, evaluate your fit, and make sure the support matches the stage and ambition of your startup journey. And no matter which accelerator you choose, the Appwrite Startup Program can complement your journey. As an all-in-one development platform, Appwrite helps you build, deploy, and host your application faster. Startups accepted into the program also get cloud credits and discounts on paid plans. [Apply to the program.](/startups) --- ## Top 15 startup incubators and accelerators in Singapore (2025) https://appwrite.io/blog/post/top-startup-accelerators-singapore Startups operate under extreme pressure, limited time, limited resources, and no margin for error. In this environment, execution speed and access to the right support can define outcomes. That’s why many founders turn to incubators and accelerators not just for help, but for leverage. These programs aren’t about hand-holding. They’re about compressing the messy early stages into focused sprints, cutting months of trial-and-error into weeks of progress. Whether you're shaping a raw idea or scaling early traction, the right program can provide expert mentorship, structured playbooks, and direct access to capital and talent. In this guide, we’ll explore how these programs function, what sets them apart, and the top 15 startup programs in Singapore. ### The purpose of a startup incubator or accelerator The core function of both incubators and accelerators is to **de-risk the early stages** of building a startup. - Incubators are built to help startups go from zero to one. They offer a longer runway to develop [ideas](/blog/post/startups-ideas-for-developers-2024), test assumptions, build [MVPs](/blog/post/how-can-you-rapidly-build-an-mvp-for-your-startup), and establish a product-market fit. The goal is to create a solid foundation before seeking external funding or scaling. - Accelerators are for startups that already have traction, typically a working product, early users, and some market validation. These programs are time-bound and focus heavily on growth, fundraising, and market entry. They help startups compress what would usually take 12–18 months into 3–4 months. In short: - Incubators = Early idea → Viable business. - Accelerators = Viable business → High-growth company. ### The difference between a startup incubator and accelerator Incubators and accelerators are often used interchangeably, but they serve different functions at different stages of the startup lifecycle. | **Aspect** | **Incubator** | **Accelerator** | | --- | --- | --- | | **Startup Stage** | Pre-product, idea stage | MVP, early traction | | **Entry Requirements** | Often open to idea-stage founders | Typically require a live product and metrics | | **Program Duration** | Flexible or rolling | Fixed (usually 3–6 months) | | **Program Goal** | Build a working MVP, validate model | Drive traction, prepare for fundraising | | **Funding Provided** | Rare, often grant-based or optional | Usually provides pre-seed/seed capital for equity | | **Structure** | Loose, ongoing mentorship and support | Structured, curriculum-driven, cohort-based | | **Exit Point** | Product-market validation | Demo day, pitch to investors, raise capital | **Key insight**: If you're still building or refining your idea, an incubator offers room to explore. If you're already building momentum and need to scale quickly, an accelerator will push you toward investor readiness and market fit. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### 15 best incubators and accelerators for tech startups in Singapore #### 1. [BLOCK71(NUS Enterprise)](https://enterprise.nus.edu.sg/supporting-entrepreneurs/nus-start-up-runway/block71-global-incubation/) A pioneering incubator hub by the National University of Singapore, known as “the world’s most tightly packed entrepreneurial ecosystem.” It provides co-working space, mentorship, and market access through NUS’s global network (with hubs in the US, China, Indonesia, etc.). Startups from BLOCK71 contributed nearly 25% of Singapore’s startup ecosystem valuation. - **Industries:** Broad tech and innovation (sector-agnostic, with emphasis on scalable tech ventures). - **Notable alumni:** **Carousell** (online marketplace unicorn) and **PatSnap** (patent analytics unicorn). #### 2. [Antler Singapore](https://www.antler.co/residency/singapore) A private global startup generator and early-stage VC that “invests in people” from day zero. Antler brings individuals together to find co-founders, build teams, and fund the newly formed startups (e.g. ~SGD 100k for 10% equity). Antler’s model helps founders without teams – it has a rigorous program to match co-founders and validate ideas, providing funding and mentorship from day one. - **Industries:** Sector-agnostic (with strengths in AI/ML, SaaS, fintech, sustainability, etc.) - **Notable alumni:** **Reebelo** (eCommerce marketplace for refurbished tech) and **Airalo** (world’s first eSIM marketplace) ### 3. [SGInnovate](https://www.sginnovate.com/) A government-backed incubator-investor focusing on deep tech (frontier science and technology). SGInnovate backs early-stage startups in areas like AI, quantum computing, MedTech and more, providing equity funding, talent recruitment, and research partnerships. It has backed multiple science-based startups that became major successes – its portfolio boasts at least **two unicorns** and several acquisitions. - **Industries:** Deep technology and scientific innovation (AI, biotech, quantum, advanced manufacturing, etc.). - **Notable alumni:** **Biofourmis** (digital therapeutics platform, now a unicorn and **SensorFlow** (IoT energy management startup) are among the standout companies SGInnovate supported. ### 4. [**Enterprise Singapore’s Startup SG Accelerator**](https://www.enterprisesg.gov.sg/grow-your-business/partner-with-singapore/innovation-and-startups/join-startup-sg) A government initiative that **funds and supports incubators/accelerators** in strategic sectors rather than running a cohort itself. It provides up to ~SGD 30k funding per startup for operating expenses via partner accelerators, and connects founders to industry mentors and investors. Instead of taking in startups directly, Startup SG Accelerator empowers over a dozen partner programs (deep tech, fintech, sustainability, etc.) to nurture Singapore-based startups, effectively amplifying the ecosystem’s reach. - **Industries:** Sector-agnostic (partners cover fintech, cybersecurity, health tech, agri tech, and more – any high-growth sector of national interest). - **Notable alumni:** N/A (Startup SG Accelerator is a funding scheme for accelerators rather than a direct startup program)*.* Its impact is indirect – for example, government-supported accelerators like **Tribe (Blockchain)** and **Trendlines (Medtech)** have been able to incubate new ventures under this scheme. ### 5. [Tribe Accelerator](https://tribex.co/accelerator/) Singapore’s first government-supported blockchain accelerator. Tribe, backed by EnterpriseSG and corporate partners, focuses on blockchain/Web3 startups across industries (finance, supply chain, gaming, etc.). It provides a 4-month program with technical resources, mentorship from industry giants (IBM, Intel, etc.), and piloting opportunities with large enterprises. Tribe’s portfolio companies have collectively raised over **US$70 million** in funding. - **Industries:** Blockchain and decentralized technologies (Web3, crypto, NFTs, enterprise DLT applications). - **Notable alumni:** **Quantstamp** (blockchain security firm) and **Sentient.io** (AI and data platform) – both participated in Tribe and went on to global success. ### 6. SPH Plug and Play A media and digital tech accelerator formed by Singapore Press Holdings and Silicon Valley’s Plug and Play. It ran several cohorts focusing on media, advertising, and consumer internet startups. Participants received investment, media exposure through SPH’s channels, and access to Plug & Play’s global network. It was one of Singapore’s earliest corporate accelerators (launched in 2015) targeting **media-tech innovation**, leveraging SPH’s dominance in publishing to test and scale new digital content ideas. - **Industries:** Media, advertising tech, marketing tech, and consumer mobile/web applications. - **Notable alumni:** **Fashory** (a fashion curation and e-commerce app) and **YellowElevator** (an employee referral recruitment platform) both graduated from SPH Plug and Play. ### 7. [ICE71](https://tig.cybersg.sg/) The first cybersecurity-focused incubator in Singapore, launched by Singtel Innov8 and NUS Enterprise with government support. ICE71 runs acceleration and bootcamp programs for early-stage cyber startups, offering specialized mentorship, access to cyber-range facilities, and introductions to government agencies and corporations in need of security solutions. Since its 2018 inception, 16 cyber startups from ICE71 cohorts raised ~S$18 million in funding (including backing from ICE71’s own partners)enterprise.nus.edu.sg, reflecting the demand for cyber innovations. - **Industries:** Cybersecurity (from network security and cloud security to cyber insurance and critical infrastructure protection). - **Notable alumni:** **Scantist** (a Singapore-based app security scanner firm) and **GamaSec** (an Israeli cyber-insurance tech startup) both went through ICE71. ### 8. [HealthTech Hub](https://www.healthtec.sg/) A collection of health and biomedical startup support programs driven by Enterprise Singapore and partners. Often referred to as a HealthTech Hub, it includes initiatives like the MedTech Actuator – a 12-month accelerator for medical technology startups – and grants for biotech innovation. Startups get clinical trial facilitation, regulatory guidance, hospital partnerships, and mentoring by healthcare experts. Singapore’s health incubators tap the country’s strong healthcare system – offering startups the chance to test solutions in hospitals and navigate complex medical regulations with government support. - **Industries:** Healthcare & biomedical – from medical devices and digital health to biotech and health services. - **Notable alumni:** **Biofourmis** – a digital therapeutics startup for remote patient monitoring that became Singapore’s first health tech unicorn, received early support in Singapore’s health ecosystem. ### 9. [PIER71](https://pier71.sg/) A maritime and marine-tech accelerator co-founded by NUS and the Maritime Port Authority (MPA). Startups typically join via the annual **Smart Port Challenge**, and finalists enter PIER71 Accelerate – an intensive 6- to 8-week program focusing on pilot projects with industry partners. PIER71 offers mentorship from maritime corporates and seed grants (MPA provides up to S$100k for PoC deployments). It is a **maritime-first accelerator** – a rare specialization – and to qualify, startups must prove themselves in the Smart Port Challenge, ensuring a high bar and strong industry alignment. - **Industries:** Maritime technology – including shipping logistics, port operations, maritime cybersecurity, autonomous vessels, and decarbonization of shipping. - **Notable alumni:** **Portcast** (AI-driven cargo logistics platform optimizing vessel and container operations) and **Performance Rotors** (drone inspection startup for maritime assets acquired by an international firm) both emerged from PIER71. ### 10. [The FinLab](https://thefinlab.com/) A fintech and digital innovation accelerator jointly run by United Overseas Bank (UOB) and SGInnovate. FinLab’s 100-day accelerator (launched in 2015) provided selected startups with seed funding, regulatory and banking mentorship, and the opportunity for pilot integration with UOB or its clients. FinLab’s backing by a major bank meant startups could directly work with a bank’s expertise and datasets – e.g., participants often got to *be UOB’s first customer or partner*, a huge leg up in credibility and product validation. - **Industries:** Fintech and adjacent tech (payments, lending, regtech, wealth management, SME tech solutions, etc.). - **Notable alumni:** **Turnkey Lender** (AI-driven lending software) expanded regionally during FinLab and attracted a US$2M investment from Vertex Ventures while in the program. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### 11. [500 Startups (500 Global)](https://500.co/) The local presence of the renowned Silicon Valley accelerator-cum-VC. 500’s Singapore programs and micro-funds (e.g., the 500 Durians fund) have invested in and mentored early-stage teams across Southeast Asia. The accelerator provides a typical 4-month program with **seed funding, growth hacking mentorship, and a global investor network.** 500 Startups was among the first global accelerators to actively invest in Southeast Asia – it famously wrote one of the first checks for Grab (then GrabTaxi) and Carousell long before they became unicorns. - **Industries:** Broad-based (internet and mobile startups of all kinds), with strength in scalable B2C and B2B tech. - **Notable alumni:** **Carousell** (the classifieds marketplace, now a unicorn) and **Grab** (ride-hailing & super-app giant) both counted 500 Startups as an early investor and mentor. ### 12. [**NTUitive**](https://www.ntuitive.sg/) NTUitive is NTU’s venture incubation arm that supports the **commercialization of research innovations**. It runs incubation programs for NTU students, faculty, and even external deep-tech founders, providing lab access, IP licensing support, prototyping grants, and industry mentors. NTUitive has helped spin off multiple deep-tech startups from university labs – for example, it supported **Nanofilm Technologies**, a materials science startup founded by an NTU professor that IPOed at a S$1.9 billion valuation (one of Singapore’s largest tech IPOs in recent years). - **Industries:** Deep tech and research-driven sectors – including advanced materials, cleantech, MedTech, AI, and engineering solutions, often originating from NTU’s R&D. - **Notable alumni:** **Nanofilm Technologies** (nano-coatings company spun out of NTU in 1999, now a public company and “tech unicorn” and **Transcelestial** (space laser communications startup co-founded by an NTU alumnus, which has raised significant funding). ### 13. [Entrepreneur First Singapore](https://www.joinef.com/) A unique **“talent investor”** that accepts individuals (pre-team, pre-idea) and helps them form co-founding teams to build startups from scratch. EF Singapore was launched in 2016 with SGInnovate’s backing and has since produced dozens of deep-tech startups. The program provides a stipend to accepted individuals for the first 3 months and then pre-seed funding (~US$25k) to form teams, who then pitch to global investors. EF’s model has been transformative – its first Singapore cohort alone created 12 deep-tech companies in 2017, with over half the founders holding PhDs. - **Industries:** Deep tech and software (the program emphasizes technical, defensible innovation – from AI/ML and robotics to biotech and enterprise software). - **Notable alumni:** **Transcelestial** (developing laser-based wireless communications for ultra-fast internet – born out of EF SG’s first cohort, known for its “space laser” tech) ### 14. [Iterative](https://www.iterative.vc/) A Singapore-based accelerator modeled after Y Combinator but tailored for Southeast Asian startups. Iterative runs two cohorts per year, investing ~US$150k in each startup for a small equity stake, and provides a structured 3-month program with weekly mentorship and growth sessions. Its mission is to build a YC-level support system in SEA. - **Industries:** Industry-agnostic (open to all tech startups) – alumni span fintech, proptech, e-commerce, logistics, SaaS, etc., reflecting broad demand in SEA. - **Notable alumni:** **Spenmo** (Singapore fintech startup for B2B payments), which went through Iterative in 2021 and later raised a **US$85M Series B led by Tiger Global**, and **Propseller** (Singapore proptech marketplace for real estate services), which raised a **US$12M Series A in 2022.** ### 15. [Startup Autobahn Singapore](https://group.mercedes-benz.com/innovation/venture/startup-autobahn/en/) An Asia-Pacific node of the global Startup Autobahn (initiated by Daimler and Plug & Play), this is an open innovation platform linking startups with industry leaders (especially in automotive and manufacturing). In Singapore, it has Mercedes-Benz, Daimler and Jardine C&C as core partners. The program runs focused “deep dives” where selected startups co-develop prototypes and pilot projects with corporate partners over ~100 days, culminating in an Expo Day to showcase solutions. - **Industries:** Automotive and mobility tech, Industry 4.0, IoT, AI, and related areas (e.g., solutions for smart vehicles, logistics, production automation, enterprise IT for automotive use cases). - **Notable alumni:** **Apvera** (cybersecurity analytics startup) and **Botbot.AI** (conversational AI platform) – both Singapore-based – graduated in Startup Autobahn’s early cohorts and secured deployments with Mercedes-Benz. ### Conclusion No matter which incubator or accelerator you join, your infrastructure choices will define how quickly you can ship, iterate, and scale. That’s where the Appwrite Startup Program fits in. It acts as your technical foundation, giving your team access to secure, scalable backend and web hosting so you can focus on building your product. From authentication to databases, storage, and functions, Appwrite’s open-source stack equips you to go from MVP to production fast. Being part of the program, you’ll also get exclusive perks, cloud credits, and priority support built for speed and scale. [Apply now](/startups) --- ## Top 30 startup incubators and accelerators in the USA (2025) https://appwrite.io/blog/post/top-startup-accelerators-usa Startups are built on bold ideas, but ideas alone aren’t enough. You need structure, speed, and support. That’s where incubators and accelerators step in. They help startups reduce guesswork, avoid early pitfalls, and get to market faster. Whether you’re validating a concept or chasing growth, these programs offer the infrastructure to move forward with confidence. In this blog, we will cover everything you need to know about Incubators and Accelerators and a curated list of the top 30 programs in the United States. ### The purpose of an incubator or accelerator At their core, both incubators and accelerators exist to increase a startup's odds of survival and success: - **Incubators** help founders develop an early [idea](/blog/post/startups-ideas-for-developers-2024) into a viable business. Think of them as the "pre-seed lab" where you test, learn, and iterate. - **Accelerators** are for startups ready to grow fast. They compress years of learning into weeks, with a focus on traction, funding, and scaling. ### The difference between an incubator and an accelerator | **Feature** | **Incubator** | **Accelerator** | | --- | --- | --- | | Startup Stage | Early idea | MVP to early traction | | Program Duration | Flexible | Fixed (typically 3–6 months) | | Objective | Validation, MVP development | Growth, fundraising | | Equity/Funding | Low or no investment | Usually seed investment for equity | | Format | Fluid, less structured | Structured, cohort-based | | Outcome | Foundation to start scaling | Demo Day, investor readiness | ### What incubators and accelerators offer Both types of programs typically include: - **Mentorship** from seasoned founders and experts. - **Initial funding** or grant support. - **Curriculum and workshops** tailored to startup needs. - **Legal, hiring, and technical resources**. - **Co-working space** or virtual infrastructure. - **Investor introductions** and demo days. - **Credibility boost** that helps with fundraising and hiring. {% call_to_action title="Build your startup with Appwrite" description="Join the Startups program and benefit from Appwrite’s all-in-one development platform to build, deploy, and scale your products." point1="Ship faster" point2="Cloud credits and discounts" point3="Priority support" point4="Managed cloud solution" cta="Apply now" url="https://appwrite.io/startups" /%} ### 30 best incubators and accelerators for tech startups in USA #### 1. [Y Combinator](https://www.ycombinator.com/) Pioneering seed accelerator known for its intensive 3-month cohort program and massive alumni network, it has launched over 4,000 startups since 2005. It provides an upfront investment of $500k in two SAFE notes and ongoing mentorship and networking for founders. - **Location**: Mountain View, CA - **Industries:** Broad tech focus (software, internet, fintech, biotech, etc.). - **Notable Alumni:** Airbnb, Stripe, Coinbase, and Twitch (each now a multi-billion dollar company). #### 2. [Techstars](https://www.techstars.com/) Global accelerator network with programs in dozens of cities; known for its mentor-driven approach and extensive corporate partnerships. Since 2006 it has helped over 3,500 startups raise funding and scale, offering ~$120k for 6% equity and a 12-week program ending in a demo day. - **Location:** Boulder, Colorado - **Industries:** General tech and sector-specific programs (fintech, healthcare, AI, etc.). - **Notable Alumni:** Uber, SendGrid, Twilio, ClassPass (illustrating its role in producing industry leaders). ### 3. [**500 Startups (500 Global)**](https://500.co/) Prolific seed accelerator and VC fund known for its diverse, international founder community and “growth hacking” expertise. It has backed over 2,700 companies worldwide, investing ~$150k for 6% equity in its batch startups. - **Location:** San Francisco, CA - **Industries:** Wide-ranging (software, e-commerce, consumer internet, fintech, etc.). - **Notable Alumni:** Udemy, Talkdesk, Canva– all of which grew into major tech successes after 500’s early support. ### 4. [**MassChallenge**](https://masschallenge.org/) Zero-equity startup accelerator recognized for its “competition” model and global reach. Since 2010, it has accelerated over 3,000 startups without taking equity, instead offering mentorship and the chance to win non-dilutive grants. - **Location**: Boston, MA - **Industries:** Broad (technology, healthcare, biotech, social impact, etc.). - **Notable Alumni:** Ginkgo Bioworks (bioengineering unicorn), Ginger (mental health app), Thinx (innovative apparel)– collectively, MassChallenge alumni have raised billions in follow-on funding. ### 5. [SOSV](https://sosv.com/) A multi-program venture fund that runs several top accelerators focused on deep technology. SOSV has helped launch over 2,300 startups via its specialized accelerators, for example, HAX (hardware/IOT), IndieBio (biotech), and others in hard-tech and blockchain. It typically invests $150k–$250k in exchange for equity and boasts a global mentor network. - **Location**: Princeton, NJ - **Industries:** Life sciences, biotech, hardware, IoT, blockchain, and other deep tech sectors. - **Notable Alumni:** Perfect Day (animal-free dairy protein) and Roadie (music collaboration platform). ### 6. [Plug and Play Tech Center](https://www.plugandplaytechcenter.com/) Large startup incubator and open innovation platform famed for connecting startups with corporate partners. Founded in 2006, it has accelerated over 1,300 companies and offers investments of $50k–$250k with an option for follow-on funding. Plug and Play’s model emphasizes business development via its 500+ corporate partners and venture network. - **Location:** Sunnyvale, CA - **Industries:** Very broad – runs vertical programs in FinTech, InsurTech, Health, Mobility, IoT, etc., alongside general tech. - **Notable Alumni:** Dropbox, CourseHero, LendingClub – all of which benefited from Plug and Play’s introductions to investors and corporate clients. ### 7. [Innovation Works](https://www.innovationworks.org/) One of the nation’s most active seed-stage investors/incubators (part of the Pittsburgh startup ecosystem). Since 1999, it has fostered over 500 companies through programs like AlphaLab and AlphaLab Gear, providing $100k–$150k convertible notes and mentorship. It focuses on early product development and customer acquisition for regional tech startups. - **Location:** Pittsburgh, PA - **Industries:** Software, IT, robotics, healthcare devices – especially those leveraging Pittsburgh’s R&D strengths. - **Notable Alumni:** RE2 Robotics (autonomous robotics, acquired by Sarcos); NoWait (restaurant app acquired by Yelp); Cognition Therapeutics (PA-based biotech that went public). ### 8. [Alchemist Accelerator](https://www.alchemistaccelerator.com/) Elite accelerator specializing in enterprise (B2B) startups. Alchemist is known for a rigorous program geared toward securing customers and follow-on investment for enterprise software companies. It invests ~$25k for a small equity stake and has a strong network of corporate venture and VC backers. - **Location:** San Francisco, CA - **Industries:** Enterprise software, cloud services, AI, and other B2B technologies. - **Notable Alumni:** Over 35 Alchemist startups have been acquired by major tech firms (e.g. Cisco’s acquisition of Assemblage; Dropbox’s acquisition of MobileSpan; Box’s acquisition of Airpost). Alumni have collectively raised over $1.2B, demonstrating Alchemist’s success in scaling enterprise startups. ### 9. [StartX](https://web.startx.com/) Stanford-affiliated, non-profit accelerator known for its zero-equity model and access to Stanford’s resources. StartX has helped launch 330+ startups since 2009, providing mentorship, education, and over $1M worth of free services without taking equity. It leverages a powerful network of Stanford alumni, faculty, and investors to support founders. - **Location:** Stanford University – Palo Alto, CA - **Industries:** Diverse – software, AI, medtech, edtech, etc., often tied to Stanford research innovations. - **Notable Alumni:** EdCast (enterprise learning, acquired by Cornerstone); Nearpod (edtech, acquired by Renaissance); Eero (mesh Wi-Fi, acquired by Amazon). ### 10. [AngelPad](https://angelpad.com/) Boutique seed-stage accelerator consistently ranked among the top programs for its hands-on mentorship. AngelPad intentionally keeps classes small (around 15 startups per cohort) to provide in-depth guidance. Founded in 2010, it has helped over 180 startups and is extremely selective (≈1% acceptance). Each startup gets $120k investment plus cloud credits and intensive 1-on-1 coaching. - **Location: San Francisco, CA & NYC** - **Industries:** Web and mobile technology, SaaS, marketplaces – generally software and internet-focused startups. - **Notable Alumni:** Postmates (on-demand delivery, acquired by Uber); Buffer (social media tool); AllTrails (outdoor app). These well-known companies underscore AngelPad’s track record despite its small batch size. ### 11. [Google for Startups Accelerator](https://startup.google.com/programs/accelerator/) Tech giant Google’s global accelerator program offers equity-free support to growth-stage startups. Google’s accelerators (formerly Launchpad) provide mentorship by Google engineers, product credits, and expertise in AI/ML, cloud, and Android development. The program typically runs for 3 months and targets startups that align with Google’s technologies or focus areas (such as AI, healthcare, fintech, etc.). - **Location:** Mountain View, CA - **Industries:** Varies by cohort – includes AI/ML, fintech, gaming, voice/AI assistants, and region-specific programs. - **Notable Alumni:** Collectively, over 100 startups graduating from Google’s accelerator have reached a combined valuation above $100 billion and raised $25 billion in capital. (Google does not name individual portfolio companies publicly, but alumni include multiple later-stage “unicorn” startups across the globe.) ### 12. [Village Capital](https://vilcap.com/) Impact-oriented accelerator and seed fund famous for its **peer-selected investment** model. Since 2009, Village Capital (VilCap) has supported nearly 1,800 mission-driven startups and made 150+ investments. In each cohort, entrepreneurs themselves evaluate and rank each other, and VilCap invests in the top peer-ranked companies – a unique approach to bias reduction. - **Location:** Washington, D.C - **Industries:** Fintech, edtech, health, agriculture, cleantech – often focusing on solutions for underserved communities or global challenges. - **Notable Alumni:** Village Capital’s portfolio has collectively raised over $7 billion. Examples include fintech startups like Coinverted and educational platforms like EduBridge (many of its alumni operate in emerging markets or social impact sectors, achieving scale with the help of VilCap’s network). ### 13. [MIT’s The Engine](https://ewg.mit.edu/) Tough-tech incubator/accelerator founded by MIT to support science-heavy startups solving big societal problems. The Engine provides lab space, equipment, and patient capital to ventures in sectors like advanced materials, energy, biotech, and robotics – areas that often require longer R&D. It typically invests in seed/Series A rounds and offers a 12+ month program of mentorship and resources. - **Location:** Cambridge, Massachusetts. - **Industries:** Renewable energy, biotech, and healthcare, robotics, AgTech – “tough tech” innovations that arise from academic research. - **Notable Alumni:** Companies such as Commonwealth Fusion Systems (fusion energy startup), Form Energy (grid-scale batteries), and Cambridge Crops/Mori (food preservation tech) have been nurtured through MIT’s ecosystem. ### 14. [Harvard Innovation Labs](https://innovationlabs.harvard.edu/) Harvard University’s incubator ecosystem (i-Lab, Launch Lab X, and Life Lab) for student and alumni ventures. Since its launch in 2011, the Harvard iLab has supported over 5,000 ventures, which have collectively raised more than $7 billion in funding. The i-Lab provides co-working space, mentorship, and an array of programs/competitions (like the President’s Innovation Challenge) to help entrepreneurs from Harvard’s schools. (Launch Lab X is a separate accelerator for alumni-led startups.) - **Location:** Allston, MA - **Industries:** Varied – software, biotech, consumer, social enterprises – reflecting Harvard’s multi-disciplinary talent. - **Notable Alumni:** **Handy** (on-demand home services, acquired by ANGI Homeservices), **Whoop** (wearable fitness tech unicorn), **Shield AI** (AI defense tech) ### 15. [Capital Factory](https://capitalfactory.com/) Austin’s premier startup accelerator, incubator, and fund – often called the “center of gravity” for Texas entrepreneurs. Capital Factory has been Texas’s most active early-stage investor since 2010. It runs accelerator programs, hackathons, and an extensive mentor network and also operates a co-working space in downtown Austin. Startups receive mentorship and introductions to Texas investors and corporations and can get $100k of investment (for a small equity stake or via special funds). - **Location:** Austin, TX - **Industries:** Enterprise software, SaaS, and a broad range of tech sectors (Capital Factory’s portfolio spans everything from AI and cybersecurity to consumer apps). - **Notable Alumni:** The accelerator has produced several Austin success stories – for example, **The Zebra** (insurtech unicorn) and **Aceable** (edtech driver’s training, acquired by private equity). ### 16. [HAX](https://hax.co/) World-renowned accelerator for hardware and robotics startups, run by SOSV. HAX provides a hands-on program split between its prototyping lab (recently in Newark, NJ, previously in Shenzhen, China) and an office in Silicon Valley. Startups get $250k or more in investment and intensive engineering support to quickly iterate on physical prototypes. HAX’s global team helps with manufacturing, supply chain, and distribution partnerships, which are critical for hardware ventures. - **Location:** Newark, NJ & San Francisco, CA - **Industries:** Hardware, IoT devices, robotics, consumer electronics, and frontier tech (anything that combines software + physical components). - **Notable Alumni:** Luminopia (FDA-approved VR therapy device), Makeblock (education robots), and Opentrons (lab robotics) are examples of HAX graduates. ### 17. [LAUNCH Accelerator](https://launchaccelerator.co/) Seed accelerator was founded by angel investor Jason Calacanis, known for its “founder university” approach and extensive investor demo days. The LAUNCH Accelerator runs a 12–14 week program for 6–8 startups per batch, investing $100K for 6% and focusing on refining metrics and pitches. It’s unique in that Jason’s team helps startups directly with growth (often featuring them on the popular “This Week in Startups” podcast and events). - **Location**: San Francisco, CA - **Industries:** Primarily software – enterprise SaaS, marketplaces, consumer apps, and emerging tech. - **Notable Alumni:** **Calm** (meditation app unicorn) and **Uber** (Jason was an early investor) are often associated with LAUNCH’s ecosystem. ### 18. [IndieBio](https://indiebio.co/) The leading biotech startup accelerator (part of SOSV) that pioneered the “lab in an accelerator” model. IndieBio provides $275k in funding plus lab space for cutting-edge life science companies to develop prototypes (from cultured meat to novel therapeutics). The program is 4 months and culminates in a demo day attended by top biotech investors. - **Location**: San Francisco, CA & New York, NY - **Industries:** Biotechnology, life sciences, health, and medical, “deep science” ventures – including areas like cellular agriculture, bio-manufacturing, pharma, and synthetic biology. - **Notable Alumni:** **Memphis Meats** (now Upside Foods – lab-grown meat pioneer) went through IndieBio and made headlines by creating the first cultured meatball; **NotCo** (AI-driven plant-based food unicorn) is another graduate. ### 19. [**Entrepreneurs Roundtable Accelerator**](https://www.eranyc.com/) - NYC’s largest and longest-running accelerator program, known simply as “ERA”. Founded in 2011, ERA runs two cohorts per year (winter and summer), investing ~$100K on a post-money SAFE in each company for ~6% equity. The four-month program offers co-working space in Manhattan and access to an enormous mentor network of 500+ experts in NYC’s tech scene. ERA is a generalist accelerator but seeks startups that can leverage New York’s industries (media, fintech, real estate, fashion, etc.). - **Location:** New York, NY - **Industries:** Industry-agnostic, with strengths in fintech, SaaS, e-commerce, media, edtech, property tech, and other areas where NYC has a foothold. - **Notable Alumni:** ERA has graduated 240+ startups – examples include **SquareFoot** (proptech, acquired by Knotel) and **Cups** (coffee shop app). ### 20. [MuckerLab](https://mucker.com/) An accelerator-VC hybrid in Los Angeles that deliberately works with only a handful of companies per year in a bespoke, long-term program. MuckerLab eschews the typical 3-month model – instead, it “does whatever is necessary for as long as necessary” to get companies to their next milestones. With only ~10–12 startups accepted annually, founders get highly personalized mentorship. Mucker provides initial capital (up to ~$250K) and can continue to fund at seed and Series A. - **Location:** Santa Monica, CA - **Industries:** Focus on software, internet, and enterprise tech startups, often those outside Silicon Valley or addressing untapped markets. - **Notable Alumni:** **Honey** (a shopping plugin acquired by PayPal for $4B) was a breakout success from Mucker’s portfolio. Other alumni include **ServiceTitan** (unicorn in home- services software) and **Emailage** (fraud prevention, acquired by LexisNexis). ### 21. [Gener8tor](https://www.gener8tor.com/) A nationally expanding accelerator founded in Wisconsin, notable for its award-winning mentor network and broad program offerings. gener8tor runs accelerator cohorts in multiple midwestern cities (and beyond) and also offers gBETA (free pre-accelerators). It invests $100K (plus $20K stipend) for 6–7% equity and provides a 12-week program that emphasizes individualized mentorship for each startup. gener8tor is distinguished by its consistently high follow-on funding rate – nearly two-thirds of alumni raise >$250K after the program. - **Location:** Madison, WI - **Industries:** General tech (software, IT, SaaS) as well as specialty verticals like advanced manufacturing, healthcare, and music/arts tech in some cohorts. - **Notable Alumni:** Gen8tor has invested in 180+ startups that have collectively raised over **$1 billion**. Examples include **EatStreet** (food delivery platform) and **Bright Cellars** (data-driven wine subscription). ### 22. [Dreamit Ventures](https://www.dreamit.com/) An early accelerator (founded in 2008) that has evolved into a venture fund and dual-track program focusing on mature startups in **HealthTech**, **SecureTech**, and **UrbanTech**. Dreamit typically accepts post-seed companies with product and revenue, then fast-tracks their customer and investor pipelines through a 14-week program without requiring relocation. Rather than a demo day, Dreamit runs “Customer Sprints” and “Investor Sprints” – intensive roadshows to pitch dozens of clients and VCs. - **Location:** Philadelphia, PA - **Industries:** Healthcare technology, cybersecurity, smart cities/proptech – as well as edtech and other B2B verticals historically. - **Notable Alumni:** Dreamit has accelerated over 300 startups, including **SeatGeek** (ticketing marketplace, now a $1B+ company), **Houseparty** (video social app acquired by Epic Games), **LevelUp** (mobile payments, acquired by Grubhub), and **Adaptly** (adtech, acquired by Accenture). ### 23. [Berkeley SkyDeck](https://skydeck.berkeley.edu/) The accelerator of UC Berkeley is notable for its unique partnership with the university and its dedicated venture fund. SkyDeck hosts around 20–25 startups per batch (global and UC-affiliated founders) in a 6-month program and invests $200K in each via the SkyDeck Fund. In addition to mentorship from faculty and alums, startups get access to 300+ advisors and $750K worth of in-kind resources. This model – combining a top public research university’s resources with venture capital – has proven “hugely successful” (SkyDeck portfolio companies have raised over **$1.7 billion** in aggregate) - **Location:** Berkeley, CA - **Industries:** All industries (open to global startups as well as UC Berkeley founders), with special tracks in biotech, deep tech (semiconductors), aerospace, and blockchain/crypto. - **Notable Alumni:** **DeepScribe** (AI medical transcription, raised $30M), **SuperAnnotate** (AI data platform, raised $14M), **Coreshell** (battery tech, raised $12M) – all recent SkyDeck graduates that secured sizable Series A rounds. ### 24. [MetaProp](https://www.metaprop.com/) The top accelerator and venture fund in the burgeoning **PropTech (real estate tech)** sector. MetaProp runs a 22-week accelerator in NYC for early-stage startups innovating in real estate, housing, and construction. Startups receive up to $250K in funding and benefit from MetaProp’s deep industry connections – including partnerships with major real estate development firms and REITs that serve as pilot customers and investors. MetaProp also provides strategic education on the complexities of the real estate industry. - **Location: New York, NY** - **Industries:** Property technology – e.g., real estate fintech, smart building IoT, construction tech, home services, and urban planning solutions. - **Notable Alumni:** MetaProp’s portfolio includes one unicorn and numerous high-growth startups. For instance, **Bowery Valuation** (AI-driven property appraisal), **Flip** (residential lease marketplace), and **Enertiv** (building energy management) all went through MetaProp. ### 25. [Elemental Excelerator](https://elementalimpact.com/elemental-impact/) A nonprofit accelerator/investor dedicated to climate tech and sustainability startups with a unique focus on community impact. Elemental Excelerator (founded in 2012 in Hawaii) provides funding of up to $500K–$1M per company for climate-related pilots and projects. The program connects startups with municipalities, corporations, and community organizations to deploy their solutions in real-world testbeds. Elemental’s approach emphasizes “impact infrastructure” – ensuring technologies not only reduce emissions but also benefit local communities. - **Location:** Honolulu, HI & East Palo Alto, CA - **Industries:** Clean energy, water, agriculture, mobility, circular economy, and other climate tech domains. - **Notable Alumni:** **BlocPower** (Greentech startup retrofitting buildings, backed by major VC funding) and **ChargerHelp!** (EV charging maintenance platform) are Elemental alumni[.](https://techcrunch.com/2023/03/17/elemental-aims-to-pump-43m-into-climate-startups-with-deep-community-impact/#:~:text=Elemental%20Excelerator%2C%20a%20nonprofit%20investor,of%20Silicon%20Valley%20Bank%E2%80%98s%20collapse) ### 26. [Boomtown Accelerators](https://btinnovation.com/) A Colorado-based accelerator known for its work with corporate partners and its focus on media, sports, and health tech. Boomtown runs multiple programs, including the Comcast NBCUniversal SportsTech Accelerator and other industry-specific cohorts, alongside its general accelerator. Founded in 2013, Boomtown typically invests $35K–$50K for around 6% equity. The 12-week program in Boulder provides startups with access to university resources (CU Boulder), a prototype lab, and a demo day at Boulder’s renowned startup week. - **Location:** Boulder, CO - **Industries:** Software and IoT broadly, with special tracks for Sports Tech, Media, 5G connectivity, and HealthTech (often in collaboration with corporate sponsors). - **Notable Alumni:** Through the SportsTech program, Boomtown has accelerated companies like **Uru Sports** (athlete network) and **Satisfi Labs** (AI fan engagement, later part of Techstars). ### 27. [Forum Ventures](https://www.forumvc.com/) Formerly known as Acceleprise, this accelerator is devoted exclusively to **B2B SaaS** startups. Forum Ventures runs cohort programs in NYC, SF, and Toronto, acting as a “fractional co-founder” for enterprise software startups. It invests around $100K and provides a 3-month program focusing on SaaS sales, growth metrics, and fundraising. Uniquely, Forum also has a venture studio and fund, enabling it to continue supporting companies through the seed stage. - **Location:** New York, NY & San Francisco, CA - **Industries:** B2B SaaS (across all verticals – fintech SaaS, martech, HR tech, dev tools, etc.) with an increasing focus on AI-driven SaaS solutions. - **Notable Alumni:** The accelerator (founded in 2014) has helped over 400 SaaS startups. Examples include **Buffer** (social media SaaS – an AngelPad alum that Forum’s partners invested in) and **Retool** (internal tools development platform, later valued over at $1B). ### 28. [TechNexus Venture Collaborative](https://technexus.com/) A hybrid incubator and venture fund that connects startups with large corporations to co-create new innovations. TechNexus (founded in 2007) positions itself as a “venture collaborative” – it sources or incubates startups in strategic areas (audio technology, media, manufacturing IoT, etc.) and partners them with Fortune 500 companies for pilot programs and investment. This model allows startups to secure enterprise customers early and allows corporates to adopt startup agility. TechNexus often provides undisclosed seed funding and workspace in Chicago’s Merchandise Mart. - **Location:** Chicago, IL - **Industries:** Enterprise technology with emphasis on emerging sectors like Audio/Music tech, Machine Learning applications, IIoT (industrial IoT), media, and logistics. - **Notable Alumni:** One example is **Cognition** (AI audio analytics), which partnered with Bose via TechNexus. Another is **Flywheel Exchange** (media management software partnered with Gray Television). ### 29. [Capital Innovators](https://capitalinnovators.com/) A respected regional accelerator and seed fund that has been a cornerstone of the St. Louis startup scene. Since 2011, Capital Innovators has invested $50K in over 190 early-stage companies and helped them raise more than $1 billion in follow-on funding. The program provides a 12-week curriculum, mentoring, and perks (cloud credits, etc.), plus office space in St. Louis’s T-REX innovation center. It has been consistently ranked among the top U.S. accelerators for its results. - **Location:** St. Louis, MO - **Industries:** Software, SaaS, healthcare, fintech, and other high-growth tech sectors. (It also runs occasional corporate accelerators in energy and financial services.) - **Notable Alumni:** **LockerDome** (a digital advertising platform), **Nitrogen** (financial advising SaaS), and **Aisle411** (indoor navigation, acquired by Stitch Networks) all emerged from Capital Innovators. Alumni have created over 3,000 jobs, and several have achieved exits via acquisition or IPO, validating Capital Innovators’ impact on scaling startups in the Midwest. ### 30. [BioGenerator](https://www.biogeneratorventures.com/) A life-sciences incubator and seed fund affiliated with BioSTL focused on nurturing biotech startups in the Midwest. BioGenerator provides very early funding (often $10K–$30K grants or convertible notes) and laboratory support to bioscience entrepreneurs, then continues to invest in promising companies through larger follow-on rounds. It also offers BioGenerator Labs – shared lab facilities and hands-on coaching for therapies, diagnostics, agtech, and other bio startups. - **Location: St. Louis, MO** - **Industries:** Biotechnology, pharmaceuticals, medical devices, healthcare IT, and agtech – with an emphasis on scientific breakthroughs from local universities and institutes. - **Notable Alumni:** **Wugen** (immuno-oncology startup, which has raised over $200M) and **Geneoscopy** (noninvasive colorectal cancer test, raised $105M) are among BioGenerator’s standout portfolio companies. ### Conclusion Startups face a lot of uncertainty in the early stages. What often makes the difference is having the right support. Incubators and accelerators help by providing structure, guidance, and access to a valuable network. They make it easier to avoid mistakes, learn faster, and move forward with confidence. Did you know Appwrite has a special program just for startups like yours? With Appwrite, you get an all-in-one development platform to build, deploy, and host your apps in one place. As part of the program, you’ll also get exclusive discounts on paid plans and priority support from our team. [Apply now](/startups) and start building with Appwrite. --- ## Using $sequence to track document order in Appwrite https://appwrite.io/blog/post/track-document-order-with-sequence Some systems need to reflect the order in which actions happen. A ticketing system, for example, should assign "Ticket #41" before "Ticket #42". But in a user interface, it often makes sense to display the latest tickets first, so "Ticket #42" may appear above #41. Relying on timestamps to get this right is often not enough. Two documents can be created almost simultaneously, and the sort order might vary. What is needed is a consistent, backend-assigned number that increases with each insert and cannot be modified or skipped. Appwrite's new `$sequence` attribute provides exactly this. Every time a document is added to a collection, the system assigns it a unique, auto-incrementing integer. This value reflects the insert history of the collection and can be used for sorting, display, filtering, and pagination. In this tutorial, we will build a simple web-based support ticket tracker using plain HTML and JavaScript. Each submitted ticket will be stored in Appwrite with a title and description, and each will receive a `$sequence` number automatically. We will use that number to display and order the tickets. {% section #step-1 step=1 title="Set up your Appwrite project" %} Start by opening the [Appwrite console](https://cloud.appwrite.io/). If you do not have a project yet, create one now. #### Create a new project Give it a name like **Support Tracker**. After creation, note the **Project ID**, as you will need it later in your frontend code. #### Create a database and collection Inside your project: - Go to the **Database** section - Create a new database named "Support DB" - Inside that database, create a new collection named "Tickets" #### Add document attributes In the **Tickets** collection, define the following attributes: - `title` - type: `string`, required: `yes`, size: 256 - `body` - type: `string`, required: `yes`, size: 1000 Appwrite will automatically include system attributes such as `$id` and `$sequence`. You do not need to create `$sequence` manually. Confirm the schema once all attributes are added. #### Set permissions for testing For now, allow anyone to create and read documents: - In the collection's **Settings**, under **Permissions** - Add `role:any` to **Create** and **Read** You can change this later when adding [authentication](https://appwrite.io/docs/products/auth). Also take note of your **Database ID** and **Collection ID**. You will use them in your frontend. #### Configure web platform To avoid CORS errors when testing your application, you need to add your domain to the allowed platforms: - In your project **Overview**, under **Integrations**, go to the **Platforms** tab - Click **Add Platform** - Select **Web** - In the web platform type, select **JavaScript** - For hostname, add your domain or `localhost`, depending on your development environment. If running with something like VS Code Live Server, add `http://127.0.0.1:5500` (or whatever port you're using) - Click **Create Platform** This tells Appwrite to allow requests from your domain or local development environment. {% /section %} {% section #step-2 step=2 title="Create the frontend structure" %} In your project directory, create two files: - `index.html` - `script.js` You can open them in any code editor. We'll build the interface in `index.html` and put all the JavaScript logic into `script.js`. #### Set up the HTML structure Start with a clean HTML layout. In `index.html`, first add the document structure and dependencies: ```html Support Tracker
    ``` This sets up the basic HTML structure and includes the Appwrite SDK and [Pink design system](https://pink.appwrite.io/) for styling. Next, add the ticket submission form inside the container: ```html

    Create New Ticket

    ``` This form captures the ticket title and description. Note the `id` attributes on the form and inputs. We'll use these in our JavaScript. Finally, add the tickets display section and close the document: ```html

    Tickets

    0 tickets
    ``` This page includes a form to submit a ticket and a container to display submitted tickets. The Appwrite SDK is loaded from a CDN, and the logic will live in `script.js`. {% /section %} {% section #step-3 step=3 title="Connect to Appwrite from JavaScript" %} Now open `script.js`. You will begin by initializing the Appwrite client and pointing it to your project. Paste the following into `script.js`: ```javascript const client = new Appwrite.Client() client .setEndpoint('') .setProject('') const databases = new Appwrite.Databases(client) const databaseId = '' const collectionId = '' ``` Replace the placeholders with your actual values from the Appwrite console. This code sets up the SDK so that you can call `databases.createDocument()` and `databases.listDocuments()` in the rest of the script. {% /section %} {% section #step-4 step=4 title="Handle ticket submission" %} Add logic to read the form data and send it to Appwrite. Still inside `script.js`, add this: ```javascript const form = document.getElementById('ticket-form') form.addEventListener('submit', async (e) => { e.preventDefault() const title = document.getElementById('title').value.trim() const body = document.getElementById('body').value.trim() if (!title || !body) return try { await databases.createDocument({ databaseId, collectionId, documentId: ID.unique(), data: { title, body, } }) form.reset() loadTickets() } catch (error) { console.error(error) } }) ``` This code creates a new document in your collection when the form is submitted. It uses `'unique()'` to generate a unique ID. After submission, the form is reset and the list of tickets is reloaded to show the new entry. {% /section %} {% section #step-5 step=5 title="Load and display tickets using $sequence" %} Now add a function that retrieves tickets and shows them in order, newest first: ```javascript async function loadTickets() { try { const response = await databases.listDocuments({ databaseId, collectionId, queries: [ Appwrite.Query.orderDesc('$sequence'), ] }) const ticketList = document.getElementById('ticket-list') const ticketCount = document.getElementById('ticket-count') const emptyState = document.getElementById('empty-state') ticketList.innerHTML = '' if (response.documents.length === 0) { emptyState.style.display = 'block' ticketCount.textContent = '0 tickets' } else { emptyState.style.display = 'none' ticketCount.textContent = `${response.documents.length} ticket${ response.documents.length === 1 ? '' : 's' }` response.documents.forEach((ticket, index) => { const ticketElement = document.createElement('div') ticketElement.className = 'card u-padding-24' ticketElement.innerHTML = `
    #${String(ticket.$sequence).padStart(3, '0')}

    ${ticket.title}

    ${ticket.body}

    ` ticketList.appendChild(ticketElement) }) } } catch (error) { console.error(error) } } loadTickets() ``` This function does several things: - Queries tickets sorted by `$sequence` in descending order (newest first) - Updates the ticket counter to show the current number of tickets - Shows or hides the empty state message appropriately - Displays each ticket as a styled card with a formatted sequence number (e.g., #001, #042) - Uses proper typography classes from the Pink design system - Calls itself immediately to load tickets when the page first loads The `padStart(3, '0')` method formats the sequence number with leading zeros. {% /section %} #### What you have built You now have a working support ticket tracker that looks like this: ![Support tracker demo](/images/blog/track-document-order-with-sequence/support-tracker-demo.png) - Each submitted ticket is stored as a document in Appwrite - Every document receives a `$sequence` number, guaranteed to be unique and increasing - The interface displays each ticket using that number - The ticket list is reliably sorted by creation order There was no need to write any logic to generate numbers, track counters, or manage collisions. Appwrite's `$sequence` attribute handled the sequence internally, which was great for our use case. #### Conclusion This tutorial introduced the auto-incrementing `$sequence` attribute and demonstrated how to use it in a small, working app. It offers a solution to a common need: preserving the order of inserts in a predictable, integer-based way. Because the value is assigned by your database, `$sequence` remains stable even as your application grows. You can filter by it, paginate through it, or display it directly in user interfaces. And since it is read-only and immutable, we avoid the risk of errors that often come with custom counters or timestamp sorting. This pattern can extend far beyond tickets. You can use `$sequence` for invoices, order numbers, log entries, approval requests, or anything where sequence matters. Here's a link to the GitHub repository for this tutorial: [Simple Support Tracker](https://github.com/appwrite-community/simple-support-tracker). Looking forward to seeing what you build with this! #### Resources - [Appwrite Database Documentation](/docs/products/databases) - [Pink Design System](https://pink.appwrite.io/) - [Secure sensitive database fields with encrypted string attributes](https://appwrite.io/blog/post/encrypted-attributes-for-sensitive-fields) --- ## Announcing Turbopack support for Appwrite Sites https://appwrite.io/blog/post/turbopack-support-appwrite-sites Appwrite Sites now supports Next.js applications built with Turbopack, Vercel's bundler. This update fixes build compatibility issues and makes your Next.js deployments faster. ### Why Turbopack matters Turbopack is built in Rust to fix webpack's performance issues. It delivers faster updates, up to 700x faster than webpack and up to 10x faster than Vite for large applications. Previously, deploying Next.js applications that used Turbopack on Appwrite Sites required manual intervention. Developers had to remove the `--turbopack` flag from their build commands, forcing their applications back to webpack. This created friction for teams using Turbopack in their development workflow. ### What this means for developers With native Turbopack support, you can now: - **Deploy without modification**: Applications using Turbopack now work on Appwrite Sites without build command changes - **Experience faster builds**: Turbopack's speed improvements reduce deployment times - **Maintain consistency**: Your local development environment matches your deployment environment This change benefits teams using Next.js 13+ where Turbopack is the recommended bundler for development mode and often used in production builds. ### Improved Next.js compatibility This update shows our commitment to Next.js compatibility. By supporting the tools and workflows that Next.js developers expect, we're removing barriers that prevented seamless deployment. The compatibility improvements go beyond just Turbopack. They ensure that modern Next.js features work as expected, reducing friction between local development and production deployment. ### Getting started For new projects: 1. Create your Next.js application with Turbopack enabled 2. Connect your repository to Appwrite Sites 3. Deploy. No build configuration changes needed **For existing broken sites:** If your site shows 404 errors due to missing Turbopack support, go to your site settings, select **Server side rendering** in the **Build settings** section, and redeploy. ![Build settings in Appwrite Sites](/images/blog/turbopack-support-appwrite-sites/build-settings.png) This feature is live on Appwrite Cloud. Self-hosted support will be included in an upcoming release. We're working to make Appwrite Sites the best platform for deploying modern web applications. Turbopack support is one step in that direction. --- ## TypeScript 7.0 will be 10x faster with Go https://appwrite.io/blog/post/typescript-7-faster-with-go Over the past decade, TypeScript has earned the trust of developers building large-scale JavaScript applications. It offers the safety net of a type system, reliable tooling, and codebases that are *arguably* easier to maintain. Yet, if you ask developers working with large projects, they'll likely express one common frustration: the TypeScript compiler can feel painfully slow. To address this challenge, Microsoft recently confirmed a significant shift under the hood. They are rewriting TypeScript's compiler (tsc) in Go. Officially codenamed "Project Corsa," this transition aims to dramatically improve compilation performance. Early benchmarks and public demonstrations from Microsoft suggest compilation speeds could improve roughly tenfold. Let's break down what this transition means, what's officially confirmed, and how it'll affect you as a developer. ### What does this mean for TypeScript? Here's what we know so far: - **Compiler rewrite:** The TypeScript compiler, currently written in TypeScript and running on Node.js, is being rewritten from scratch in Go. Microsoft announced this under the internal name "Project Corsa." - **Motivation:** The primary motivation is performance. TypeScript compilation times and type-checking responsiveness are known to degrade as projects scale. - **Language stability:** The rewrite will not affect TypeScript's syntax or language design. It will continue emitting standard JavaScript as before. Developers will still write TypeScript exactly as they currently do. - **Standalone binary:** The new Go-based compiler will run as a standalone binary, removing the Node.js runtime dependency currently required for compilation. Microsoft has not announced any changes to TypeScript's language features, syntax, or output because of this rewrite. The transition to Go is an engineering effort to improve performance and maintainability. ### Why Go specifically? Not Rust or C++? One question naturally arises: why Go specifically? While Microsoft hasn't provided a comprehensive breakdown comparing Go to alternatives like Rust or C++, they've [publicly shared](https://github.com/microsoft/typescript-go/discussions/411) some key factors: **Structural similarity:** Go's programming style closely resembles the existing TypeScript codebase, making it much easier to port TypeScript's logic while keeping behavior and optimizations intact. This is critical since Microsoft plans to maintain both the JavaScript and Go-based compilers for some time. **Memory management tradeoffs:** Unlike Rust, which requires manual memory handling, Go provides fine-grained control over memory allocation without forcing constant memory management decisions throughout the codebase. TypeScript's compiler doesn't suffer from latency-sensitive GC pauses, so Go's garbage collection works well for this use case. **Graph processing & AST traversal:** Go provides an ergonomic way to traverse complex trees and graphs, a key part of TypeScript's type-checking and compilation process. **Long-term API design considerations:** The TypeScript team wants more control over internal compiler APIs. While Go's interop with JavaScript isn't as strong as some alternatives, moving to a more intentional API design will help modernize the ecosystem. ### Benchmark numbers: early results, still preliminary Perhaps the most attention-grabbing details involve benchmark numbers shared by Microsoft and industry publications. For instance, compiling the Visual Studio Code codebase (about 1.5 million lines of TypeScript) reportedly went from around 78 seconds with the current compiler down to roughly 7.5 seconds with the new Go-based compiler; a 10x improvement. Other large projects, such as Playwright and TypeORM, reportedly saw similar performance improvements (approximately 10x-13x faster). These numbers come directly from early demonstrations and internal testing shared by Microsoft. But benchmarks at this early stage often represent idealized or carefully selected scenarios. Real-world improvements for diverse TypeScript projects may vary, and final figures might differ as the implementation matures. ### How will this affect tooling and ecosystem compatibility? A main concern among developers centers around compatibility with existing tools and integrations. Currently, TypeScript integrates smoothly with bundlers like Webpack, Vite, and esbuild, plus countless IDE plugins and language-server implementations. Microsoft has publicly committed to maintaining compatibility and minimizing disruption throughout the compiler transition. Still, it's realistic to expect that tooling relying heavily on internal compiler APIs or deeply integrated TypeScript behaviors may require some adjustments. It's been [confirmed](https://github.com/microsoft/typescript-go/discussions/411) that the TypeScript team will initially support both the existing JavaScript-based compiler and the new Go-based compiler concurrently. This dual-compiler support would allow tool authors and ecosystem maintainers ample time to adapt without immediate disruption. While Microsoft is prioritizing a smooth transition, developers and tool authors should prepare for minor integration adjustments, especially if their workflows rely directly on internal TypeScript compiler APIs. ### Timeline and versioning: what we know Microsoft has confirmed that **TypeScript 7.0 will ship with the Go-based compiler** once it reaches feature parity with the current JavaScript-based version. Meanwhile, the existing compiler will continue under the **TypeScript 6.x** series, which will receive updates and breaking changes to align with the upcoming transition. Since some projects may depend on specific APIs or legacy configurations, **Microsoft will maintain TypeScript 6.x alongside TypeScript 7+** until the Go-based compiler reaches full stability and adoption. The rough timeline is: - **Mid-2025** – Preview release with basic type-checking. - **Late 2025** – Feature-complete version for project builds and language services. For now, both compilers will coexist, allowing developers to upgrade at their own pace. ### What does this mean for you? This transition changes how TypeScript is compiled but not how developers use it. The language itself remains the same, and the compiler will continue outputting JavaScript as before. The main difference is that builds should be significantly faster, with better performance in large projects. That said, the new compiler is still in development, and benchmarks are early-stage. While Microsoft is working to maintain compatibility, some tools may need updates, and TypeScript 6.x will remain available for projects that aren't ready to switch immediately. The long-term goal is for the Go-based compiler to fully replace the current one, but until it reaches full stability, both will be maintained in parallel. We'll continue monitoring official announcements and detailed benchmarks as Microsoft progresses through this transition. Until then, you can remain cautiously optimistic: the future of TypeScript looks promising, and notably faster. --- ## Understanding data queries in database management https://appwrite.io/blog/post/understand-data-queries For any developer working with databases in any capacity, knowledge of queries is fundamental. These queries, executed through languages like SQL (Structured Query Language), are the backbone of database interaction, allowing you to retrieve, update, insert, or delete data. In this blog, we'll delve into the different types of data queries, shedding light on their functions and best practices. ### Different types of queries #### 1. Retrieval queries (SELECT) The most common and fundamental type of query is the retrieval query. Using the `SELECT` statement in SQL, you can fetch data from one or more tables. This query can range from simple commands fetching all columns from a table to more complex ones involving conditions (`WHERE` clause), joining multiple tables, and aggregating data. ##### Key points: - **Basic syntax**: `SELECT column1, column2 FROM table_name;` - **With conditions**: Use `WHERE` to filter data. - **Joining tables**: Combine data from multiple tables using `JOIN`. - **Aggregation**: Functions like `SUM`, `AVG`, and `COUNT` help in summarizing data. #### 2. Insertion queries (INSERT) When you need to add new records to a table, insertion queries come into play. These are straightforward yet powerful, allowing you to populate your tables with new data. ##### Key points: - **Basic syntax**: `INSERT INTO table_name (column1, column2) VALUES (value1, value2);` - **Bulk insert**: Insert multiple rows in a single query for efficiency. - **Data integrity**: Ensure that inserted data adheres to the table's constraints and data types. #### 3. Update queries (UPDATE) Update queries modify existing data. They are essential for maintaining the relevance and accuracy of the information stored in your database. ##### Key points: - **Basic syntax**: `UPDATE table_name SET column1 = value1, column2 = value2 WHERE condition;` - **Conditional updates**: Use `WHERE` to specify which rows should be updated. - **Caution**: Unconditional updates without a `WHERE` clause will modify all rows in the table. #### 4. Deletion queries (DELETE) To remove records from a table, you use deletion queries. While powerful, they should be used judiciously to avoid unintended data loss. ##### Key points: - **Basic syntax**: `DELETE FROM table_name WHERE condition;` - **Conditional deletion**: The `WHERE` clause specifies which rows to delete. - **Irreversible action**: Unlike `UPDATE` or `INSERT`, `DELETE` actions can't be reversed. Always back up data before bulk deletions. ### Best practices - **Optimize performance**: Use indexes and optimize your queries for faster execution. - **Ensure security**: Protect against SQL injection by using prepared statements or stored procedures. - **Maintain data integrity**: Understand and respect the database schema and constraints. - **Test queries**: Especially with UPDATE and DELETE, it's crucial to test your queries in a development (safe) environment before applying them to the production database. - **Documentation**: Comment your queries, especially the complex ones, for future reference and for other team members. ### Querying the Appwrite Database A lot of developers today don’t perform raw SQL queries but prefer to use an ORM such as Prima or a managed database provider such as Appwrite. While these tools enable the same end goal, a managed service can provide an easy-to-use wrapper and helper methods that make these queries easier to write and don’t require you to have a deep knowledge of SQL syntax. Appwrite offers the aforementioned data queries as a part of our Database product, which you can discover in our [product documentation](/docs/products/databases). One of the data retrieval APIs the Appwrite Database offers is a list documents API to get multiple documents from any collection. The endpoint also allows you to filter, sort, and paginate results, for which Appwrite provides a common set of syntax to build queries, which you can build manually or using our SDKs. With our latest release, we’re adding support for database operators such as `OR`, `AND`, and `CONTAINS` to allow further flexibility. - `AND` operation: This operator allows nesting queries in an AND condition. - `OR` operation: This operator allows nesting queries in an OR condition. - `CONTAINS` operation: The contains operator allows filtering by values that are contained in an array. ```client-web import { Client, Databases, Query } from "appwrite"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') .setProject(''); const databases = new Databases(client); // OR operator example const movieData1 = databases.listDocuments( '', '', Query.or([ Query.equal('title', ['Back To The Future', 'Top Gun']), Query.greaterThan('year', 2017) ]) ); // AND operator example const movieData2 = databases.listDocuments( '', '', Query.and([ Query.startsWith("title", "Once"), Query.greaterThan('year', 1995) ]) ); // CONTAINS operator example const movieData3 = databases.listDocuments( '', '', Query.contains('director', ["Christopher Nolan"]) ); ``` Mastering the art of data querying is a continuous process. As a developer, your aim should be to write efficient, secure, and maintainable queries. Remember, the power of a database is harnessed through the effectiveness of its queries. ### Resources Visit our documentation to learn more about Appwrite, join us on Discord to be part of the discussion, view our blog and YouTube channel, or visit our GitHub repository to see our open-source code. - [Docs](/docs/products/databases/queries) - [Discord](https://appwrite.io/discord) - [Blog](/blog) - [YouTube](https://www.youtube.com/channel/UCtBJ1v69gm8NgbCju_03Fiw) - [GitHub](https://github.com/appwrite/appwrite) --- ## Understanding OAuth2: The backbone of modern authorization https://appwrite.io/blog/post/understand-oauth2 Modern applications rarely operate in isolation. Whether it's logging in with Google or sharing data with a third-party service, users demand interoperability and security. That’s where OAuth2 steps in: a powerful protocol designed to delegate access without compromising user credentials. This guide explains OAuth2, how it works, the different flows available, and when to use each one, helping you build secure, scalable authorization experiences. ### What is OAuth2? OAuth2 is an open standard for authorization. It allows users to grant limited access to their resources on one service to another service without sharing credentials. Rather than handing out a username and password, users authorize apps to act on their behalf using access tokens. OAuth2 ensures that: - Apps never directly handle user credentials. - Users retain control over what permissions they grant. - Access can be easily revoked. ### Core components of OAuth2 Before diving into the flows, it's important to understand the key players: - **Resource owner**: The user who authorizes access to their data. - **Client**: The application requesting access. - **Authorization server**: Issues access tokens after authenticating the user. - **Resource server**: Hosts the protected resources. These components work together to ensure secure authorization across systems. Refer to the OAuth2 [documentation](/docs/product/auth/oauth2) for complete technical details. ### How OAuth2 works: A simple flow 1. **Authorization request**: The client asks the resource owner for permission. 2. **Authorization grant**: If the user consents, the server issues a grant (authorization code, token, etc.). 3. **Token request**: The client exchanges the grant for an access [token](/docs/products/auth/tokens). 4. **Resource access**: The client uses the token to access protected resources. Tokens are typically short-lived and scoped, meaning they only allow the operations the user approved. ### Major OAuth2 flows OAuth2 offers different "flows" to accommodate various scenarios. Here's a breakdown of the major ones: #### 1. Authorization code flow **Best for**: Server-side applications - User authenticates via browser. - Client receives an authorization code. - Server exchanges the code for an access token. **Advantages**: - Highly secure (authorization code exchanged server-side). - Supports refresh tokens. **Typical use cases**: - Web apps with secure backend servers. {% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Multiple OAuth providers" point2="Built-in security" point3="Custom roles and permissions" point4="Integrates with your favourite SDK" cta="Request a demo" url="https://appwrite.io/contact-us/enterprise" /%} #### 2. Authorization code flow with PKCE (Proof Key for Code Exchange) **Best for**: Mobile and SPA (Single Page Applications) - Similar to Authorization Code Flow, but with an added security layer (PKCE). - Prevents interception attacks. **Advantages**: - Stronger protection for public clients. **Typical use cases**: - Mobile apps, SPAs. #### 3. Client credentials flow **Best for**: Machine-to-machine (M2M) communication - No user interaction. - Client authenticates itself to obtain an access token. **Advantages**: - Efficient for service-to-service communication. **Typical use cases**: - APIs accessed by backend services. #### 4. Implicit Flow (Legacy) **Best for**: SPAs (historically) - Tokens returned directly in browser URL. - Faster but less secure. **Note**: Now largely replaced by Authorization Code Flow with PKCE due to security risks. #### 5. Device authorization flow **Best for**: Devices without browsers/keyboards - User authenticates on a separate device. - Device polls authorization server for approval. **Typical use cases**: - Smart TVs, IoT devices. [Appwrite Auth](/products/auth) supports all major OAuth2 flows, making it easy to integrate secure authentication into any app ### OAuth2 Tokens: Access and refresh OAuth2 commonly uses two types of tokens: - **Access Token**: Grants access to protected resources. - **Refresh Token**: Used to obtain new access tokens without re-authenticating the user. Tokens are often JWTs (JSON Web Tokens) containing claims about the user and the permissions granted. ### When to Use OAuth2 - **Third-party integrations**: Allowing users to connect external services securely. - **APIs**: Protecting APIs from unauthorized access. - **Mobile and web Apps**: Enabling secure login and data access without managing credentials. - **B2B applications**: Secure service-to-service communication. ### Common OAuth2 pitfalls - **Over-scoped tokens**: Granting too many permissions. - **Insecure storage**: Storing tokens in insecure locations (e.g., localStorage without encryption). - **Ignoring token expiration**: Failing to handle token refresh flows. - **Misusing Implicit Flow**: Using legacy flows where better options (PKCE) are available. ### OAuth2: A key enabler of modern security OAuth2 powers secure, flexible authorization across the modern internet. Understanding its core flows and best practices helps developers build safer, more user-friendly apps. Choosing the proper OAuth2 flow based on your application's architecture and user needs is critical to balancing security, usability, and scalability. Ready to explore OAuth2 more deeply? Check - [Appwrite Authentication docs](/docs/products/auth) - [Overview of all the OAuth providers](/integrations#auth) - [Appwrite Authentication overview](/products/auth) --- ## Understanding IdP vs SP-Initiated SSO https://appwrite.io/blog/post/understanding-idp-vs-sp-initiated-sso Managing authentication across multiple applications is a growing challenge for developers, especially with users expecting more convenience and security. Single Sign-On (SSO) offers a practical solution to that problem, allowing users to access multiple services with one login. Although the experience is almost always seamless for users, developers have multiple options for implementing SSO in their applications. This guide breaks down the differences between **Identity Provider (IdP)-initiated** and **Service Provider (SP)-initiated** SSO, their advantages and trade-offs, and how to choose the best fit for your setup. ### What is IdP-Initiated SSO? First, a quick refresher: an **Identity Provider (IdP)** manages user identities, validating who a user is before granting access to different applications. Here’s a quick [overview](/docs/products/auth/identities) of how Appwrite handles identity and access. In an IdP-initiated SSO flow, the user’s journey starts at the IdP itself: ### How it works 1. User logs in to the IdP. 2. The IdP displays a dashboard of connected applications. 3. The user selects a service to access. 4. The IdP sends a secure authentication token (such as a SAML assertion) to the Service Provider (SP). 5. The SP grants access based on the [token](/docs/products/auth/tokens). ### Advantages - **Streamlined access**: Launch multiple services from a single dashboard. - **Reduced credential reuse**: Minimizes repeated logins, lowering the risk of compromised credentials. - **Centralized control**: Simplifies user monitoring and access management. ### Trade-offs - **Extra navigation step**: Users must first visit the IdP portal. - **Single point of failure**: If the IdP is compromised, multiple services could be at risk. - **Integration challenges**: Some services may not fully support IdP-initiated workflows. {% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="GDPR, HIPAA and SOC 2 compliant" point2="Built-in security" point3="Multi-factor authentication" point4="Integrates with your favourite SDK" cta="Contact sales" url="/contact-us/enterprise" /%} ### What is SP-Initiated SSO? **Service Providers (SPs)** are the applications or services users want to access. In SP-initiated SSO, the process begins when a user attempts to log into an application directly: ### How it works 1. User tries to access the service. 2. The service detects no active session and redirects the user to the IdP. 3. The user authenticates at the IdP. 4. The IdP sends an authentication token back to the service. 5. The service grants access. ### Advantages - **Direct access**: Users can go straight to the service they want. - **Seamless integration**: Fits naturally into user-driven workflows. - **Flexibility**: Useful for both internal and external users. ### Trade-offs - **Redirect dependency**: Requires smooth coordination between service and IdP. - **Increased setup complexity**: Proper configuration is critical to avoid login issues. ### IdP- vs SP-Initiated SSO: Quick Comparison | Feature | IdP-Initiated SSO | SP-Initiated SSO | | --- | --- | --- | | **Starting Point** | Identity Provider portal | Service Provider login page | | **User Flow** | Login at IdP, then select services | Attempt service access, then authenticate via IdP | | **User Experience** | Best for environments with multiple services | Best for quick, direct service access | | **Security Considerations** | Central control but single point of vulnerability | Stronger per-service session security | | **Typical Use Cases** | Corporate portals, education hubs | SaaS apps, customer-facing platforms | ### When to choose IdP-Initiated SSO - **Organizations with many internal services**: Ideal for centralized portals. - **Formal environments**: Where users are accustomed to navigating through a unified dashboard. - **Legacy system compatibility**: Easier integration with older systems. ### When to Choose SP-Initiated SSO - **User-first services**: Where users need to quickly access a single app. - **B2B and B2C platforms**: Especially when users might come in via bookmarks, emails, or direct links. - **Dynamic environments**: Where new apps are frequently added or removed. Pro tip: SP-initiated flows are often complemented by [adaptive MFA](/docs/products/auth/mfa) to enhance security without compromising the user experience. ### When to use both approaches Many organizations implement both IdP- and SP-initiated SSO to serve different user needs: - **Employee and partner ecosystems**: Employees might use IdP dashboards while partners or customers prefer direct access. - **Hybrid cloud setups**: Supporting a mix of legacy and modern applications. - **Adaptive security strategies**: Choosing the flow based on device, location, or user profile. Choosing the right SSO initiation method,or blending both, can dramatically impact [security](/docs/products/auth/security), user satisfaction, and scalability. Evaluate your platform's user behavior, security posture, and integration needs to pick the best approach for your environment. ### Futher reading - [Appwrite Authentication docs](/docs/products/auth) - [Developer's guide to user authentication](/blog/post/guide-to-user-authentication) - [Appwrite Authentication overview](/products/auth) --- ## Appwrite Sites now offers unlimited sites on the free plan https://appwrite.io/blog/post/unlimited-appwrite-sites-free-plan When we launched Appwrite Sites in early access, our goal was clear: make deploying modern web projects as easy and integrated as building them. From the start, we intended for developers to have full flexibility, to deploy, test, and scale without limits. But before opening things up, we needed to ensure that the platform was stable, fast, and reliable for everyone. That’s why, during the initial rollout, we limited Sites to one per project. This gave us the room to validate performance, harden our infrastructure, and refine the developer experience. Now that Appwrite Sites has matured, it's time to lift that constraint. On the free plan, you can deploy unlimited sites per project. ### Build, test, and deploy without limits With this update, you can create as many sites as needed under a single Appwrite project. Whether you’re: - Spinning up **test environments** for new features, - Running **staging sites** for QA and review, or - Deploying **production-ready apps** to the world You can do it all without worrying about limits or separate configurations. Appwrite Sites automatically handles builds, SSL, DDoS, and CDN distribution, so you can move quickly from commit to deployment across as many environments as you need. ### Designed for scale, ready for anything This update aligns with the broader Appwrite vision: give developers the tools to build and ship software without barriers. Unlimited sites mean you can manage everything from micro frontends and documentation pages to landing pages and experimental prototypes within a single, unified Appwrite project. Whether you’re an individual developer or part of a growing team, your Sites setup can scale naturally with your workflow, and no plan upgrades are required. Learn more about Appwrite sites: - [Sites docs](/docs/products/sites) - [Product page](/products/sites) - [Appwrite Sites templates](/docs/products/sites/templates) --- ## Error: User (role: guests) missing scope (account) - What it means and how to fix it https://appwrite.io/blog/post/user-role-guests-missing-scope-account If you've been working with Appwrite, you've likely encountered the error message **"Error: User (role: guests) missing scope (account)"** at some point. This error is one of the most common questions we see from developers, especially those new to Appwrite. In this post, we'll break down what this error means and walk through the various scenarios where it might appear, along with their solutions. ### Understanding the error message Before diving into specific scenarios, let's understand what the error message is telling us: - **User (role: guests)**: This indicates that the request is coming from an unauthenticated user (a guest) rather than a logged-in user. - **missing scope (account)**: This means the operation you're trying to perform requires access to an account. In essence, this error occurs when you're trying to perform an operation that requires authentication, but Appwrite doesn't detect a valid authenticated session. There are a handful of reasons you may be receiving this error, which we'll explore in detail below. ### No active session The most common cause of this error is attempting to access user account information when there's no active session. If you're not logged in yet and you try to get information about the user with something like `account.get()`, you'll see this error. ```jsx // This will fail if no user is logged in try { const user = await account.get(); console.log(user); } catch (error) { console.error(error); // Error: User (role: guests) missing scope (account) } ``` This error is actually expected and useful in certain scenarios, such as when your app first starts and you need to check if a user is logged in. You can catch this error to determine that there's no active session and redirect users to a login page. However, if you're getting this error when you actually need an authenticated session, simply create a session before calling `account.get()`: ```jsx // First create a session await account.createEmailPasswordSession({ email: 'user@example.com', password: 'password' }); // Now this will work const user = await account.get(); console.log(user); ``` ### OAuth2 sessions in web applications A more complex scenario occurs when using OAuth2 authentication in web applications. When you use `account.createOAuth2Session()` in a web app, a cookie is set for the session. If your app and Appwrite endpoint are on different domains (for example, your app is on `localhost:3000` and you're using `https://cloud.appwrite.io/v1` as your Appwrite endpoint), the cookie may not be included in the `account.get()` request because the cookie is considered a 3rd party cookie. There are two approaches to solve this issue: #### Option 1: Browser settings and custom domains For local development, you'll need to allow 3rd party cookies in your browser settings. Many browsers have specific development settings to allow cross-domain cookies during local development. When moving to production, configure a [custom domain](https://appwrite.io/docs/advanced/platform/custom-domains) for your Appwrite project so that cookies are sent from the same domain as your application. This ensures that session cookies are properly sent with each request. #### Option 2: Use OAuth2 tokens Another approach is to use Appwrite's OAuth2 token-based authentication, which bypasses third-party cookie restrictions entirely. Instead of `createOAuth2Session()`, you would use `createOAuth2Token()` which uses local storage as a fallback where cookies won't work. For a detailed implementation guide, check out this article on [Fixing OAuth2 authentication issues in Appwrite Cloud](https://appwrite.io/blog/post/fixing-oauth2-issues-in-appwrite-cloud?doFollow=true), which covers this approach in detail. Keep in mind that non-public files still require session cookies. If your app needs to handle such files, you'll need to configure a custom domain to ensure cookies are treated as first-party cookies. ### Creating sessions server side In client-side applications, Appwrite automatically persists the session by using cookies. Server-side, however, different frameworks handle sessions differently so the session is not persisted. Since `account.createEmailPasswordSession()` doesn't persist a session, calling `account.get()` right after will result in an unauthenticated request: ```jsx // Server-side code - this approach doesn't work as expected await account.createEmailPasswordSession({ email: 'user@example.com', password: 'password' }); // In a different request or function call: const user = await account.get(); // Error: missing scope (account) ``` For server-side environments, you need to handle session persistence manually: 1. Use an API key when authenticating so that the response from Appwrite includes a secret you can use for subsequent requests 2. Store and reuse the session information between requests For detailed implementation examples and best practices for server-side rendering with Appwrite, check out the [SSR documentation](https://appwrite.io/docs/products/auth/server-side-rendering?doFollow=true). ### Conclusion Whenever you see "User (role: guests) missing scope (account)", remember: Appwrite expected an authenticated user but received a guest request instead. This typically happens because: 1. No session exists yet 2. The session cookie is being blocked by browser privacy settings 3. Server-side code isn't properly managing session persistence Each scenario has a straightforward solution as we've covered above. Test your authentication flow across multiple browsers and environments to ensure it works consistently. If you're still running into issues, the Appwrite [Discord community](https://appwrite.io/discord) is always available to help. ### Further reading - [Building custom authentication flows with Appwrite](https://appwrite.io/blog/post/building-custom-auth-flows?doFollow=true) - [Fixing OAuth2 authentication issues in Appwrite Cloud](https://appwrite.io/blog/post/fixing-oauth2-issues-in-appwrite-cloud?doFollow=true) - [A modern developer's guide to user authentication](https://appwrite.io/blog/post/guide-to-user-authentication?doFollow=true) --- ## You're probably using Next.js wrong https://appwrite.io/blog/post/using-nextjs-wrong Next.js has become the default choice for new React projects. But here's the problem: most developers treat it like React with a fancy router. They spin up a Next.js app, slap `'use client'` on every component, fetch data in `useEffect`, and wonder why when they face problems. If that sounds familiar, you're shipping bloat for no reason. ### Next.js is not React React is a UI library. It renders components and manages state. Routing, data fetching, and server logic are your problems. Next.js is a full-stack framework that uses React for its UI layer. It gives you server-side rendering, static generation, API routes, and server components out of the box. These features exist for specific reasons, and if you're not using them, you're just adding complexity without benefit. ### Does SEO matter? This is the deciding factor. If search engines need to index your content, you need server-side rendering. Client-rendered pages ship JavaScript that builds the DOM after load. Search crawlers can technically execute JavaScript, but they're inconsistent at it. Your e-commerce product pages, blog posts, and landing pages should render on the server or should be statically pre-rendered. If you're building an internal dashboard or admin panel that lives behind a login, SEO is irrelevant. Client-side rendering is fine. You could use plain React with Vite and skip the Next.js overhead entirely. ### What server components actually solve Server components aren't just about SEO. They solve three problems: #### Initial page load Client components ship JavaScript to the browser, which then fetches data and renders. Users see a loading spinner. Server components fetch data and render HTML before anything reaches the browser. Users see content immediately. #### Bundle size Every library you import in a client component ends up in your JavaScript bundle. Server components run on the server only. That heavy markdown parser or date library never touches the browser. #### Security Server components can access databases and secrets directly. They are also capable of accessing environment variables without the `NEXT_PUBLIC_` prefix, since these components are run exclusively on the server. ### Converting a client component to a server component Here's a typical client-side pattern: ```jsx "use client"; import { useState, useEffect } from "react"; import { tablesDB } from "@/lib/appwrite"; export default function Products() { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { tablesDB .listRows({ databaseId: DATABASE_ID, tableId: TABLE_ID, queries: [Query.equal("status", "active")], }) .then((res) => setProducts(res.rows)) .finally(() => setLoading(false)); }, []); if (loading) return
    Loading...
    ; return ; } ``` Here's the server component equivalent (without `'use client'`): ```jsx import { createAdminClient } from "@/lib/appwrite/server"; export default async function Products() { const { tablesDB } = await createAdminClient(); const { rows } = await tablesDB.listRows({ databaseId: DATABASE_ID, tableId: TABLE_ID, queries: [Query.equal("status", "active")], }); return ; } ``` ### Setting up Appwrite for server-side rendering The Appwrite Web SDK runs in browsers. For server components, you need the Node SDK: ```bash npm install node-appwrite ``` Create two client factories. The admin client uses an API key for public data: ```ts // lib/appwrite/server.ts import { Client, TablesDB, Account } from "node-appwrite"; export async function createAdminClient() { const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); return { tablesDB: new TablesDB(client), account: new Account(client), }; } ``` The session client uses the logged-in user's session for protected data: ```ts import { Client, TablesDB, Account } from "node-appwrite"; import { cookies } from "next/headers"; export async function createSessionClient() { const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID); const session = (await cookies()).get("session"); if (session) { client.setSession(session.value); } return { tablesDB: new TablesDB(client), account: new Account(client), }; } ``` The difference matters. Admin client queries return all rows matching your query. Session client queries return only rows the user has permission to access. ### Handling authentication Login with a server action: ```tsx // app/login/page.tsx import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { createAdminClient } from "@/lib/appwrite/server"; export default function LoginPage() { async function login(formData: FormData) { "use server"; const { account } = await createAdminClient(); const session = await account.createEmailPasswordSession( formData.get("email") as string, formData.get("password") as string ); (await cookies()).set("session", session.secret, { httpOnly: true, secure: true, sameSite: "strict", expires: new Date(session.expire), }); redirect("/"); } return (
    ); } ``` Protect routes with a layout: ```tsx // app/(protected)/layout.tsx import { redirect } from "next/navigation"; import { createSessionClient } from "@/lib/appwrite/server"; export default async function ProtectedLayout({ children }) { try { const { account } = await createSessionClient(); await account.get(); } catch { redirect("/login"); } return children; } ``` ### When to skip all of this Use plain React when: - Your app lives behind authentication - Search engines don't need to index it - You prefer client-side data fetching patterns - You're building a very simple app that doesn't need server-capabilities There's nothing wrong with client-side rendering. The mistake is using Next.js and not leveraging what makes it useful. ### Resources - [Appwrite SSR documentation](/docs/products/auth/server-side-rendering) - [Next.js App Router documentation](https://nextjs.org/docs/app) --- ## Building a Valentine's Day sonnet generator using OpenAI and Appwrite Functions https://appwrite.io/blog/post/valentines-day-sonnet-generator This year to make Valentine’s Day a little more special for all you lovebirds, you might remember that we worked on a fun little project. This project allowed a number of you to create romantic sonnets for your loved ones. Did you know, however, that this project was powered by a Node.js Appwrite Function and OpenAI’s GPT-4 API? In this blog, we will share how we developed our Valentine’s Day Sonnet Generator. ### Setting up the OpenAI platform To get an OpenAI API Key, you must create an account on the [OpenAI platform](https://platform.openai.com/). Once your account is set up, visit their [API keys](https://platform.openai.com/account/api-keys) page and create an API Key. Ensure you copy and save this key in a safe place, as the OpenAI platform will not let you view the key after it is created. ![OpenAI API Keys](/images/blog/valentines-day-sonnet-generator/openai.png) > Note: To use the GPT-4 API, your account must be upgraded to the **Usage tier 1**. To learn more, visit their [Usage tiers documentation](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-one). ### Preparing the Appwrite Function Now that we have our OpenAI API Key, let us get the function ready on [Appwrite](https://cloud.appwrite.io/). Head over to your Appwrite project and visit the Functions page. From there, we will select the Templates tab, search for and select the Prompt ChatGPT function template. ![Appwrite Function Templates](/images/blog/valentines-day-sonnet-generator/templates.png) This function requires **1 environment variable** to setup: - `OPENAI_API_KEY`: API Key from our OpenAI account After you have configured the environment variables, you must connect your Appwrite account with GitHub, select **Create a new repository** (this will generate a GitHub repository for you with the function), and leave the production branch and root settings as default to create this function. ### Developing the project While the Prompt ChatGPT function provides us with a majority of the boilerplate, certain areas of project still need to be updated. #### Preparing the UI In the project directory, visit `static/index.html` and replace the existing code with the following: ```html Valentine's Day Sonnet Generator

    Valentine's Day Sonnet Generator ❤️

    Enter your partner's name and receive a sonnet dedicated to them, courtesy of Appwrite and OpenAI

    ``` #### Updating the function logic The original function provided through this template used OpenAI’s GPT-3.5-Turbo API. Since we want to upgrade this to GPT-4, we need to make a couple of changes. Firstly, we must update the version of the OpenAI library in our `package.json` file to version `4.28.0` so that it looks as follows: ```json { "name": "prompt-chatgpt", "version": "1.0.0", "description": "", "main": "src/main.js", "type": "module", "scripts": { "format": "prettier --write ." }, "keywords": [], "dependencies": { "openai": "^4.28.0" }, "devDependencies": { "prettier": "^3.0.0" } } ``` After that is done, we need to visit the `src/main.js` file and replace the existing code with the following: ```client-web import { OpenAI } from 'openai'; import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res, log, error }) => { throwIfMissing(process.env, ['OPENAI_API_KEY']); if (req.method === 'GET') { return res.text(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } try { throwIfMissing(req.body, ['name']); } catch (err) { error(err.message); return res.json({ ok: false, error: err.message }, 400); } const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); try { const response = await openai.chat.completions.create({ model: 'gpt-4', max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: `Write a romantic Valentine\'s Day sonnet dedicated to ${req.body.name}` }], }); const completion = response.choices[0].message?.content; log(completion); return res.json({ ok: true, completion }, 200); } catch (err) { error(err.message); return res.json({ ok: false, error: err.message }, 500); } }; ``` #### Testing the function Once you’ve completed all the aforementioned steps, you can push the code to the generated GitHub repository, at which point Appwrite Cloud will automatically deploy the changes to your function. ![Function deployments](/images/blog/valentines-day-sonnet-generator/deployments.png) You can then go ahead and test your function by opening the function domain in your browser. ### Next steps And with that, our Valentine’s Day sonnet generator is ready! When we released this project on Valentine’s Day this year, it was used over 300 times by people across the world. You can try it out: [apwr.dev/valentines-day-sonnet](https://apwr.dev/valentines-day-sonnet) ![Output function](/images/blog/valentines-day-sonnet-generator/output.png) If you liked this project or want to investigate the full project code, visit the [GitHub repository](https://github.com/adityaoberai/valentines-day-sonnet-generator). For more information about Appwrite Functions, visit the following resources: - [Appwrite Function Docs](https://appwrite.io/docs/functions): These documents provide more information on how to use Appwrite Functions. - [Appwrite Discord](https://discord.com/invite/appwrite): Connect with other developers and the Appwrite team for discussion, questions, and collaboration. --- ## 20 security best practices for vibe coding https://appwrite.io/blog/post/vibe-coding-security-best-practices Vibe coding is changing the way developers build software. Instead of manually writing every function, many developers are beginning to rely on AI-assisted tools to generate code based on natural language instructions. This approach can significantly speed up development and allow teams to create applications faster. But... the convenience of vibe coding comes with significant security risks. AI-generated code is not inherently secure, and without proper oversight, it can introduce vulnerabilities that lead to data breaches, unauthorized access, and critical system failures. Security must be a top priority for vibe coders, and as a developer, you must take responsibility for reviewing, testing, and securing AI-generated code. In this article, we'll explore **20 essential security best practices** you must follow as a vibe coder to ensure your applications remain safe. ### 1. Always review and understand AI-generated code One of the most common mistakes developers make when using AI-generated code is assuming that it is correct and secure by default. AI models do not "think" like humans. They generate code based on patterns from their training data. **They don't have feelings, and therefore, cannot feel responsible for the code they generate.** This means they can produce insecure, inefficient, or completely incorrect solutions that may work at first glance but introduce serious risks. For example, an AI-generated authentication system might include a password-checking function but fail to enforce proper hashing standards. A vibe coder might copy this code into their project without realizing it stores passwords in plaintext, a serious security vulnerability. To avoid such issues, always review AI-generated code line by line, understand each function's purpose and ensure it aligns with security best practices. If the AI generates a complex piece of logic that you do not fully understand, take the time to research and test it before integrating it into your application. ### 2. Rely on proven authentication patterns Authentication is one of the most critical security components of any application. AI-generated code might create a login system that appears functional, but without proper security measures, it could expose user credentials, enable unauthorized access, or fail under real-world attack scenarios. AI can very easily generate a function that checks passwords against stored values in a database but can also easily fail to implement proper password hashing. If as a developer, you are unaware of best practices, there'll be nothing stopping you from using this insecure function to store passwords in plaintext. If an attacker gains access to the database, they would have full visibility into user credentials, leading to massive security breaches. Instead of relying on AI-generated authentication logic, go for **widely adopted** authentication solutions and libraries. A straightforward choice is a platform like [Appwrite](https://appwrite.io/), which provides a secure authentication flow out of the box with several authentication patterns including email/password, social login, magic link/passwordless login, and more. For Node.js applications, you can also use **Passport.js** or **NextAuth** to provide secure authentication flows. For web applications, **Auth0** exists as well, and can ensure that authentication is handled correctly with industry-standard security measures like OpenID Connect, and multi-factor authentication (MFA). You should also ensure that password hashing follows modern best practices. Use **bcrypt** or **Argon2** for hashing rather than outdated methods like MD5 or SHA-1, which are vulnerable to brute-force attacks. When AI generates authentication code, always verify that it follows these principles before deploying it to production. ### 3. Validate and sanitize all user inputs User input is one of the most common attack vectors in web applications. AI-generated code may not properly validate inputs, leaving applications vulnerable to **SQL injection, cross-site scripting (XSS), and remote code execution (RCE)**. These vulnerabilities allow attackers to manipulate data, execute malicious scripts, or gain unauthorized access. Consider a scenario where AI generates a search function for a database query: ```js app.get('/search', async (req, res) => { const query = `SELECT * FROM users WHERE name = '${req.query.name}'` const result = await db.query(query) res.json(result) }) ``` This function directly inserts user input (`req.query.name`) into an SQL query, making it vulnerable to SQL injection. An attacker could send: ``` /search?name=' OR '1'='1 ``` This would return all user records, exposing sensitive data. To prevent this, always **validate input** and use **parameterized queries**: ```js app.get('/search', async (req, res) => { const name = req.query.name?.trim() if (!name || /[^a-zA-Z0-9 ]/.test(name)) { return res.status(400).json({ error: 'Invalid input' }) } const result = await db.query('SELECT * FROM users WHERE name = ?', [name]) res.json(result) }) ``` Using an **ORM** like Prisma, Sequelize, or TypeORM can further reduce risks by abstracting query handling. Alternatively, [Appwrite's Database API](https://appwrite.io/docs/references/1.6.x/client-web/databases) provides built-in query filtering, eliminating the need to construct raw SQL queries. Beyond SQL injection, user inputs should be validated for **length, type, and format**. Use libraries like **Zod (TypeScript)** or **Joi (JavaScript)** to enforce strict validation rules before processing any user data. Sanitization is equally important. Removing or escaping dangerous characters in user inputs prevents attacks like XSS and command injection. ### 4. Store secrets securely and avoid hardcoding credentials AI-generated code sometimes suggests using API keys, database credentials, or other sensitive information **directly within source files**. **This is a serious security risk.** Hardcoded secrets can be accidentally pushed to public repositories, where attackers can easily retrieve them and exploit access to your systems. For example, AI-generated code might include: ```js const apiKey = 'sk_test_1234567890abcdef' // DO NOT DO THIS fetch(`https://api.example.com/data?key=${apiKey}`) .then((response) => response.json()) .then((data) => console.log(data)) ``` Instead of hardcoding secrets, always store them in **environment variables** or a **secrets manager**. Services like **AWS Secrets Manager, Azure Key Vault, and HashiCorp Vault** provide secure storage and controlled access to sensitive credentials. In a Node.js application, use environment variables like this: ```js const apiKey = process.env.API_KEY ``` Then, store credentials in a `.env` file, which should be **excluded from version control**: ``` API_KEY=sk_test_1234567890abcdef ``` Proper secret management ensures that sensitive data is never exposed in public repositories, reducing the risk of unauthorized access. ### 5. Do not store API keys in env files of frontend frameworks One of the most common security mistakes developers make is storing API keys, secrets, or sensitive credentials inside environment files of frontend frameworks like Next.js, React, or Vue.js. While environment variables are a good practice for server-side applications, using them incorrectly in frontend projects can expose sensitive credentials to attackers. #### Why frontend environment variables are insecure In frontend frameworks like React and Vue, environment variables are usually bundled at build time, meaning they are part of the JavaScript files served to users. If a key is included in a .env file like this: ``` REACT_APP_API_KEY=sk_test_1234567890abcdef ``` And referenced in a component: ```js const apiKey = process.env.REACT_APP_API_KEY ``` This key will be visible in browser developer tools, the network tab, and even the compiled JavaScript files. An attacker can easily obtain it by inspecting the page source or opening the developer console. This applies to any frontend framework that bundles environment variables into client-side JavaScript, including: - React (`REACT_APP_*` variables) - Vue (`VUE_APP_*` variables) - Next.js (`NEXT_PUBLIC_*` variables) Any environment variable prefixed with PUBLIC or specifically required for client-side code is not secure and should never contain sensitive data. #### How to securely store API keys in frontend projects API keys should always be stored on a **backend server**, not in frontend code. Instead of making direct API calls from the frontend, the recommended approach is to **create a backend proxy that securely handles requests** and appends the API key before forwarding the request to the external service. This way, **the API key is never exposed to the browser**, and attackers cannot extract it from the client-side code. #### Environment variables in server-side frameworks Some frameworks, like **Next.js and Nuxt.js**, offer built-in support for environment variables that are only accessible on the server. For example, in Next.js, any environment variable **without** the `NEXT_PUBLIC_` prefix is only available on the server: ``` API_KEY=sk_test_1234567890abcdef ``` This allows the API key to be used **only in API routes or server-side functions**, preventing it from being bundled into frontend JavaScript. ### 6. Implement strong authorization and access control AI-generated code might not enforce proper authorization checks, which can allow users to access resources they shouldn't. A common mistake is assuming that authentication alone is sufficient for security. However, without granular **role-based access control (RBAC)** or **attribute-based access control (ABAC)**, attackers or unauthorized users might exploit missing checks to escalate privileges. For example, you might use AI to generate an API that allows users to delete a record from your database. Without checking if the logged-in user has the necessary permissions, any user could delete **any** account, leading to a major security issue. To prevent this, implement **role-based access control (RBAC)** to restrict access to only authorized users. Every function that modifies or deletes data should enforce strict authorization rules. Always verify user roles and enforce **least privilege access**, ensuring users only have permissions necessary for their role. If using Appwrite, ensure that your database collections are [configured with the correct permissions](https://appwrite.io/docs/products/databases/permissions) and that you are using the correct roles to restrict access. ### 7. Secure all communications with HTTPS AI-generated code might not enforce HTTPS, leaving applications vulnerable to **man-in-the-middle (MITM) attacks**, where attackers intercept and modify sensitive data exchanged between clients and servers. If login credentials or API keys are transmitted over HTTP, an attacker monitoring the network can easily steal them. To enforce HTTPS, configure your web server to **redirect all HTTP traffic to HTTPS** using **HSTS (HTTP Strict Transport Security)** headers. For Express.js applications, force HTTPS using middleware: ```js const express = require('express') const helmet = require('helmet') const app = express() app.set('trust proxy', true) // Trust reverse proxy // Redirect HTTP to HTTPS app.use((req, res, next) => { if (req.protocol !== 'https') { return res.redirect(301, `https://${req.headers.host}${req.url}`) } next() }) // Enable HSTS app.use( helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true, }), ) app.listen(3000) ``` For production, always use **TLS (Transport Layer Security)** certificates from **Let's Encrypt** or another trusted Certificate Authority (CA). ### 8. Regularly scan dependencies for vulnerabilities AI-generated code often suggests using external libraries without verifying their security. Vulnerable dependencies are one of the most exploited attack vectors in modern applications. Attackers frequently target outdated libraries with known security flaws. To protect against this, **regularly audit dependencies** using: - `npm audit` (for JavaScript/Node.js projects) - `pip-audit` (for Python projects) - `cargo audit` (for Rust projects) - `mvn dependency:check` (for Java/Maven projects) For example, in a Node.js project, running: ``` npm audit fix ``` automatically updates vulnerable dependencies. However, **always test updates thoroughly**, as they may introduce breaking changes. Consider using automated dependency monitoring services like **Snyk** or **Dependabot**, which notify you about security vulnerabilities in your project dependencies. ### 9. Secure API endpoints to prevent unauthorized access One of the most overlooked risks in AI-generated code is weak API security. AI models might generate API endpoints without proper authentication or authorization, exposing sensitive data and operations to anyone with access to the endpoint. Attackers often scan public-facing APIs for security misconfigurations, making this a critical area to secure. Always ensure that your API endpoints are protected by authentication and authorization. Beyond authentication, API security should include: - **Rate limiting:** Prevent brute-force attacks by restricting the number of requests per minute using middleware like `express-rate-limit`. - **CORS policies:** Restrict which domains can make API calls to prevent unauthorized cross-origin requests. - **API gateways:** Use API management platforms like Kong or AWS API Gateway to enforce security at scale. API security must be a priority from the start, as weak APIs are one of the most common entry points for attackers. ### 10. Implement secure session management AI-generated authentication flows might create session-handling logic that lacks proper security controls. Poor session management can lead to **session hijacking, session fixation, or replay attacks**, where attackers steal valid session tokens and gain unauthorized access. A common mistake is using weak session identifiers or storing session tokens in insecure locations, such as local storage in the browser. Attackers can steal these tokens using **XSS (Cross-Site Scripting) attacks**. Instead, sessions should be managed securely: - **Use HTTP-only cookies for session storage**, which prevents JavaScript from accessing session tokens. - **Set secure flags on cookies** (`Secure`, `HttpOnly`, and `SameSite=strict`) to prevent cross-site access. - **Regenerate session IDs after authentication** to prevent session fixation attacks. - **Set session expiration times** to minimize risk if a token is compromised. For example, in Express.js with `express-session`: ```js app.use( session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000, // 1 hour }, }), ) ``` Just like we discussed before, an even better approach is to go with well-established solutions for auth and session management. Appwrite helps you with this out of the box. You can also go with other popular solutions that are specifically designed for this purpose. ### 11. Secure file uploads to prevent malware execution AI-generated file upload handlers may accept any file type without validation, opening the door to **remote code execution (RCE) attacks, malware uploads, and denial-of-service (DoS) exploits**. Attackers can upload malicious scripts disguised as images or PDFs, executing them once they reach the server. For example, an insecure file upload endpoint might look like this: ```js app.post('/upload', upload.single('file'), (req, res) => { res.send('File uploaded') }) ``` This accepts **any file type**, which is dangerous. To secure file uploads: - **Limit allowed file types** (e.g., only allow `.jpg`, `.png`, `.pdf`). - **Scan uploaded files for malware** using antivirus tools like ClamAV. - **Store files outside the web root directory** to prevent direct access. - **Rename uploaded files** to prevent executing malicious filenames. To secure file uploads and prevent malware execution, implement a comprehensive validation strategy. Rather than accepting any file type, explicitly whitelist allowed formats (like JPG, PNG, PDF) and verify file types through MIME checking. Set reasonable file size limits (e.g., 5MB) to prevent denial-of-service attacks. Consider using libraries like `multer` in Node.js to handle these security controls, and remember to scan uploads for malware using tools like ClamAV before processing them. If using Appwrite Storage, you can easily configure all these in your [bucket settings](https://appwrite.io/docs/products/storage/buckets). This approach drastically reduces the risk of attackers uploading malicious scripts disguised as harmless files. By enforcing strict upload rules, you protect your system from dangerous file exploits. ### 12. Apply the principle of least privilege (PoLP) AI-generated code, especially with the growing popularity of [MCP (Model Context Protocol)](https://appwrite.io/blog/post/what-is-mcp?doFollow=true), might grant excessive permissions to users, database connections, or cloud services. This violates the **principle of least privilege**, which states that every system component should have **only the minimum level of access necessary to perform its function**. For example, if an AI-generated database connection uses **a root account with full privileges**, an attacker who gains access to the application could execute **DROP DATABASE** or **modify any table**. Instead, create a dedicated database user with **read-only access** for non-administrative operations. Apply PoLP principles across: - **Cloud services:** Use IAM roles to restrict permissions in AWS, GCP, or Azure. - **API keys:** Limit API scopes to necessary actions only. - **User roles:** Define admin, editor, and viewer roles with different access levels. By limiting access, even if an attacker gains access to a compromised system, the damage they can do is minimized. ### 13. Monitor security logs and enable real-time threat detection One of the biggest risks with AI-generated code is the lack of built-in logging and monitoring. Without proper logging, security incidents go unnoticed until it's too late. AI-generated functions often lack detailed audit trails, making it difficult to investigate breaches, detect unauthorized access, or analyze unusual activity. For example, if an AI generates an authentication function, it may successfully verify user credentials but fail to log login attempts, failed authentications, or suspicious behavior. Without this data, you won't know if someone is attempting a brute-force attack or if an attacker has successfully exploited a vulnerability. To mitigate this, implement a comprehensive **logging and monitoring** system. Beyond logging, enable **real-time threat detection** using **SIEM (Security Information and Event Management)** tools such as Splunk, Datadog, or Elastic Security. These tools analyze logs for suspicious activity, like repeated failed login attempts or access from unusual locations. Additionally, consider integrating **intrusion detection systems (IDS)** like **OSSEC** or **Wazuh** to monitor system logs and detect threats before they escalate. ### 14. Secure cryptographic operations and avoid AI-suggested weak algorithms AI-generated code often suggests outdated or insecure cryptographic algorithms due to limitations in training data. For example, an AI might suggest using **MD5** or **SHA-1** for password hashing, both of which are no longer secure against modern brute-force attacks. To ensure secure cryptographic operations: - Use **bcrypt** or **Argon2** for password hashing. - For encryption, use **AES-256-GCM** instead of older ciphers like DES or Blowfish. - For digital signatures, prefer **ECDSA (Elliptic Curve Digital Signature Algorithm)** over RSA-1024. Remember that AI-generated code can easily skip fundamental steps like salting passwords, making them vulnerable to rainbow table attacks. Always verify cryptographic implementations and ensure they follow modern security standards. ### 15. Rate-limit API requests to prevent brute-force and DoS attacks AI-generated API endpoints often lack protection against excessive requests, making them susceptible to brute-force attacks and **Denial of Service (DoS)** attacks. If an attacker floods your login API with thousands of authentication attempts, they may crack weak passwords or overload your server, making the service unavailable to legitimate users. To prevent this, implement **rate limiting** at the API level using middleware like `express-rate-limit`: ```js const rateLimit = require('express-rate-limit') const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per window message: 'Too many requests, please try again later', }) app.use('/login', limiter) ``` This limits users to **100 login attempts per 15 minutes**, significantly reducing the risk of brute-force attacks while ensuring legitimate users can still access the service. For more advanced protection, use **Web Application Firewalls (WAFs)** like **Cloudflare, AWS WAF, or Imperva**, which provide intelligent traffic filtering and automated threat detection against DoS attacks. ### 16. Implement proper CORS policies to prevent cross-origin exploits We mentioned CORS briefly, but it's important to address it in detail. Cross-Origin Resource Sharing (CORS) controls which external domains can make requests to your API. AI-generated APIs may not enforce strict CORS policies, leading to **cross-origin request forgery (CSRF) and unauthorized API access**. For example, a poorly implemented CORS policy might allow requests from **any** domain (`*`): ```js app.use(cors({ origin: '*' })) ``` This is dangerous because it allows **any website** to make API requests on behalf of an authenticated user, potentially exposing sensitive data. A better approach is to define **allowed origins explicitly**: ```js const corsOptions = { origin: [''], methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], } app.use(cors(corsOptions)) ``` This will help ensure that only trusted websites can interact with your API, reducing the risk of **cross-site request forgery (CSRF) attacks**. ### 17. Secure cloud deployments and lock down publicly exposed services AI-generated cloud deployment configurations may leave services **publicly accessible**, creating a major security risk. If you use AI-generated Terraform, Kubernetes, or AWS configurations, you must carefully review security settings. In AWS, GCP, or Azure environments, always: - **Disable default public access** for storage, databases, and VMs. - **Enforce IAM roles and least-privilege permissions** for cloud services. - **Enable multi-factor authentication (MFA)** for all cloud accounts. ### 18. Ensure regular security audits and penetration testing Vibe coding is not a replacement for **manual security audits and penetration testing**. Even if AI generates seemingly correct code, vulnerabilities can exist in the business logic, integrations, or configurations. To ensure ongoing security: - Perform **static analysis** (SAST) using tools like **SonarQube**. - Conduct **dynamic analysis** (DAST) with penetration testing tools like **Burp Suite**. - Schedule **manual penetration tests** at least **twice a year** to uncover logic flaws. Security audits help identify overlooked risks and improve the security posture of vibe coding. ### 19. Educate developers on AI security risks AI-generated code is **only as secure as the developer using it**. Many developers assume AI-generated code is flawless, but without security knowledge, they might integrate insecure solutions. If you're reading this as a manager, CTO, or organization leader, you should: - Train developers in **secure coding principles**. - Educate teams on **AI-generated code risks** and common mistakes. - Require **peer reviews** for all code before deployment. Security awareness is the first defense against AI-related vulnerabilities. ### 20. Use updated AI models and validate their outputs AI-generated code will continue to improve, but as a developer, you must ensure you use **updated models**. Older AI models may lack awareness of recent security best practices, and might more easily generate outdated or vulnerable code. Regularly review: - AI-generated **dependencies** and libraries. - Security patches and updates for AI-assisted or vibe coding development tools. - AI-generated security policies and configurations. By staying up to date, you can use AI safely while mitigating risks. ### Final thoughts Vibe coding can be a great advantage if done correctly, but AI-generated code **must never be blindly trusted**. Security remains a human responsibility, requiring carefulness, un-rushed review, and adherence to best practices to ensure applications are protected. Many software applications can seriously affect people's lives, and therefore, security should never be taken lightly. By following these **security principles**, you can take proper advantage of vibe coding and ai-assisted development. --- ## WebP support now available for Safari on all devices https://appwrite.io/blog/post/webp-support-for-safari We’re happy to share that Appwrite Cloud users now have full WebP support across all versions of Safari browsers and devices. When we first introduced this feature in the early versions of Appwrite, Safari didn’t support WebP, which created challenges for developers using this format. To maintain application integrity, we automatically fell back to the PNG format, ensuring cross-browser compatibility but at the cost of larger file sizes and slower load times. However, as outlined on [Can I use WebP?](https://caniuse.com/webp), Safari has supported WebP for quite some time now. With this update, Appwrite has fully embraced WebP across all Safari browsers and devices, eliminating the need for fallback solutions. This update is now available to all Appwrite Cloud users, making image delivery more efficient across all platforms. ### Why WebP matters WebP is a modern image format offering superior compression while maintaining high image quality. Compared to traditional formats like JPEG and PNG, WebP can reduce image sizes by up to 30%, resulting in faster page load times, lower bandwidth usage, and an overall better user experience. This is especially critical for developers looking to optimize their applications for performance and scalability. With WebP now fully supported in Safari, Appwrite developers can take full advantage of this format across all browsers and devices. This improvement will lead to reduced latency, less bandwidth consumption, and cost savings - particularly for Cloud accounts, where efficiency matters most. --- ## What is a content delivery network (CDN)? https://appwrite.io/blog/post/what-is-cdn Ever clicked on a website and been surprised at how quickly, or painfully slowly, it loads? That speed isn't just about how powerful the server is; it's about where the server is. If your app lives in New York but your users are in Tokyo, every request must cross an ocean before seeing anything. Not ideal. That's where a **content delivery network (CDN)** comes in. Think of it as a network of pit stops scattered around the globe, ready to serve your content from the closest possible location. Images, scripts, videos, or even entire pages, CDNs make sure they get to your users faster, with fewer delays and less strain on your main server. In this blog, we'll break down the basics of CDNs, their benefits, and how Appwrite developers can leverage them. ### How Does a CDN Work? At its core, a **content delivery network (CDN)** is a team of servers spread out across the globe, working together to get content to your users as quickly as possible. Instead of everyone knocking on the door of your origin server, they're routed to the closest “edge” server. **Here's how CDN works**: 1. A user makes a request (say, loading an image or a script). 2. The CDN checks its nearest edge server. - If the content is already cached there, then it's delivered instantly. - If not, the CDN grabs it from your origin server, stores a copy locally, and serves it up. 3. The next user nearby gets it instantly, and there is no waiting around. This system, called [**edge caching**](/docs/products/network/caching#edge-level), keeps things fast, light, and reliable. Since the CDN handles repeat requests, your origin server can breathe a little easier, too. ### Benefits of using a CDN So why go through the trouble of adding a CDN in front of your app? Here's what you actually get out of it: - **Speed that feels instant:** Content gets served from the closest edge server, not the other side of the planet. Less travel time means faster load times. - **More reliable apps:** If one server goes down, the CDN automatically reroutes requests to the next available one. No single point of failure. - **Lower costs:** CDNs take the load off your origin server by caching static assets and help cut bandwidth bills. - **Built-in security:** Many CDNs offer DDoS protection and SSL/TLS encryption out of the box, keeping your app safer at scale. - **A CDN handles traffic spikes with ease:** When there is a sudden surge in users, the CDN spreads the load across its network so performance doesn't tank. In short, CDNs make your app faster, sturdier, safer, and more cost-efficient to operate. ### Example: Without vs. with a CDN - **Without a CDN**, a user in Tokyo accessing an app hosted in New York may face noticeable latency because every request travels across the globe. - **With a CDN**, that same request is routed to the nearest CDN edge server in Tokyo, making the app feel instantly responsive. ### Using a CDN with Appwrite If you're building on Appwrite, you've already got a head start: - **Appwrite Sites** – Every site you deploy has a built-in CDN, so your static assets are automatically cached and served closer to your users with no extra setup. - **The Appwrite CDN** – With 120+ points of presence (PoPs) worldwide, Appwrite's CDN ensures your content reaches users quickly and reliably, regardless of location. It handles static and dynamic content, integrates directly with Appwrite's regions and edges, and uses advanced compression and smart caching to keep delivery fast and efficient. Plus, everything runs over **TLS**, so security is baked in by default. For APIs, storage, or self-hosted projects, you can still put a CDN in front of your Appwrite deployment to amplify performance even further. Check out the [Appwrite Network docs](/docs/products/network) for more details. ### Conclusion Modern CDNs go far beyond caching static files. With edge computing, intelligent routing, and compression, they also accelerate **dynamic content, APIs, and real-time workloads**. For developers, this means lower latency, higher availability, and stronger security baked into the delivery layer. And if you're working with Appwrite, you already have these capabilities through [Appwrite Sites](/docs/products/sites) and the [Appwrite CDN](/docs/products/network/cdn). Together, they help you build apps that scale seamlessly while delivering the instant, always-on experience users now expect. ### More resources - [Appwrite Sites docs](/docs/products/sites) - [The Appwrite CDN](/docs/products/network/cdn) - [Why multi-cloud is taking over](/blog/post/why-multi-cloud-is-taking-over) --- ## What is CIAM (Customer Identity and Access Management)? https://appwrite.io/blog/post/what-is-ciam Balancing security and user experience has always been a challenge for digital businesses. Too much friction, and users abandon. Too little, and security is compromised. Customer Identity and Access Management (CIAM) bridges this gap, offering a secure, seamless way to authenticate, authorize, and personalize user journeys at scale. **Customer Identity and Access Management (CIAM)** refers to the technologies and policies that allow businesses to capture and manage [customer identity](/docs/products/auth/identities) and profile data securely. CIAM solutions empower organizations to deliver personalized, secure, and scalable user experiences while ensuring compliance with privacy regulations. Unlike traditional IAM systems that focus on internal employees, CIAM is for external users. This includes customers, partners, and citizens. CIAM offers a balance between user convenience and [data security](/docs/products/auth/security). ### Key features of CIAM A comprehensive CIAM solution includes: - **Registration and authentication:** Simplified sign-up and login processes, including options like social logins, multi-factor authentication (MFA), and single sign-on (SSO). Explore multiple [authentication methods](https://appwrite.io/docs/products/auth) supported by Appwrite. - **Profile management:** Customers can update their profile information, preferences, and privacy settings easily. - **Consent and privacy management:** CIAM ensures that businesses are compliant with regulations like GDPR and CCPA by capturing and managing customer consents. - **Scalability:** Designed to handle millions of users simultaneously without compromising performance. - **Security measures:** Features like passwordless [authentication](/products/auth), biometric verification, and threat detection protect both customer data and business operations. {% call_to_action title="Customer identity without the hassle" description="Add secure authentication for your users in just a couple of minutes." point1="Email/Password, SMS, OAuth, and more" point2="Server side rendering" point3="Session control and management" point4="Built-in security and compliance" cta="Request a demo" url="https://appwrite.io/contact-us/enterprise" /%} ### Why CIAM matters With growing concerns over data breaches and privacy, customers today demand transparency and control over their personal information. At the same time, businesses must deliver frictionless experiences to remain competitive. CIAM helps organizations meet these expectations by: - **Enhancing customer experience:** Easy onboarding, personalized services, and secure interactions boost customer satisfaction and loyalty. - **Improving security posture:** Advanced authentication mechanisms and centralized identity management minimize risks. - **Achieving regulatory compliance:** Proper management of consent and personal data storage helps businesses avoid heavy fines and reputational damage. - **Enabling business growth:** CIAM solutions provide critical customer insights that inform marketing strategies and product development. ### CIAM vs Traditional IAM While CIAM and traditional IAM share foundational technologies, their goals and execution are different: | **Aspect** | **Traditional IAM** | **CIAM** | | --- | --- | --- | | **User base** | Employees/Internal users | Customers/External users | | **Focus** | Operational efficiency and security | Customer experience and personalization | | **Scale** | Thousands of users | Millions of users | | **Regulatory focus** | Enterprise security policies | Privacy regulations (GDPR, CCPA, etc.) | | **Authentication** | Enterprise credentials | Social login, biometrics, federated ID | ### The future of CIAM As digital ecosystems expand and customer expectations evolve, CIAM will continue to grow in importance. Trends like [passwordless authentication](/docs/products/auth/magic-url), AI-driven identity verification, and decentralized identities are shaping the next generation of CIAM solutions. Organizations that focus on strong CIAM strategies will better protect customer data. They will also build trust, improve brand loyalty, and drive business success in a more connected world. ### Further reading - [Appwrite Authentication docs](/docs/products/auth) - [Appwrite Authentication quick start doc](/docs/products/auth/quick-start) - [Appwrite Authentication overview](/products/auth) --- ## Why should you use Golang in your app? https://appwrite.io/blog/post/what-is-golang Appwrite has just announced support for Go SDK and Function runtime in version 1.6. The Go programming language (also known as Golang) is popular for its concurrency model, speed, and simple URL-based dependency management. With Appwrite’s new Go runtime, you can take advantage of Go’s speed to handle complex tasks like calculations, statistics, or file transformations more efficiently. This blog is going to go over Go strengths, limitations, and how you can potentially leverage it in your projects, whether you’re already a Go developer or a JS/Python developer looking to write faster applications. ### What is the Go programming language? Go, or Golang, is a statically typed programming language developed by Google. It's designed for simplicity, reliability, and efficiency, making it ideal for both beginners and experienced developers. The language was designed by Robert Griesemer, Rob Pike, and Ken Thompson, who aimed to address some of the common criticisms of other languages used at Google, such as C++ and Java, particularly in terms of compilation time and ease of use. Go was officially announced to the public in November 2009, and its 1.0 version was released in March 2012. Since then, it has steadily gained popularity, especially in the fields of cloud services, DevOps, and microservices architectures. ### What does the Go programming language do? The Go programming language offers a few features that make it popular among developers: - **Standard library:** Go comes with a rich standard library that includes everything from web servers to cryptography. This extensive library means you can often avoid third-party dependencies, leading to more secure and maintainable code. - **Code package management:** Code package management helps organize and use both your own and external code packages, and it allows you to publish packages with just a few commands. - **Static typing:** Static typing in Go ensures that data types are correct and compatible, preventing common errors found in dynamically typed languages. - **Platform independence:** Go's platform independence means you can compile Go code to run on almost any operating system. - **Concurrency model:** Go's concurrency model uses lightweight goroutines, which act like threads, and channels that let these goroutines communicate. The syntax is simple and resembles patterns in dynamic languages, focusing on using interfaces rather than inheritance. This makes it easier to write programs that can handle multiple tasks simultaneously. #### Benefits of Golang: - **Performance** Go is compiled to machine code, which makes it incredibly fast. This speed is particularly beneficial for backend development, where performance can significantly impact user experience and scalability. - **Concurrent programming** One key reason to use Golang is its support for concurrent programming with Goroutines, which are lightweight functions that can run independently. Occupying just 2 KB of memory, Goroutines allow thousands of concurrent processes without crashing your system, unlike Java’s heavyweight threads. This makes Golang ideal for creating compact and efficient software. - **Cross-platform compatibility** Go is designed to be cross-platform. You can compile Go code to run on various operating systems, ensuring that your applications can reach a broader audience without significant modifications. - **Growing ecosystem** The Go community is vibrant and growing, with numerous libraries and frameworks available for full-stack development. Tools like Gin (a web framework) and GORM (an ORM library) make it easier to build robust applications quickly. #### Limitations of Golang: - **Limited library support** The ecosystem for third-party libraries in Go is not as extensive as more established languages like Java or Python. This can sometimes make it difficult to find libraries for specific use cases. - **Verbose error handling** Error handling in Go can be verbose and repetitive. The language's design encourages explicit error checking, which can lead to a lot of boilerplate code. - **Manual memory management limitations** Although Go has garbage collection, it does not provide as fine-grained control over memory management as languages like C or C++. This can be a limitation for applications requiring highly optimized memory usage. - **No native GUI development** Go lacks native support for building graphical user interfaces, requiring reliance on third-party libraries or other languages for GUI applications. ### What is Golang used for? Golang is a versatile language with a wide range of applications: #### Web development Go is widely used to build scalable and high-performance web applications. Its standard library has powerful tools for handling HTTP requests, routing, and data encoding. Frameworks like Gin and Echo add features like middleware support and routing optimization, making it easy to handle high traffic. Plus, Go’s quick compilation and simple syntax help developers build and deploy web apps quickly. Here’s how you would use Appwrite Go functions to create an account: ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/account" "github.com/appwrite/sdk-for-go/client" ) func main() { appwriteClient := client.NewClient() appwriteClient.SetProject("") appwriteClient.SetKey("") service := account.NewAccount(appwriteClient) response, error := service.CreateEmailPasswordSession( "email@example.com", "example1234", ) if error != nil { fmt.Println(error) return } fmt.Println(response) } ``` #### Microservices Go’s fast compilation times and minimal runtime overhead make it well-suited for developing lightweight and easy-to-maintain microservices. Its fast compilation and minimal runtime overhead make it efficient. With features like `goroutines`, Go allows you to handle many tasks at once, which is crucial for microservices that need to operate independently and work seamlessly with others. Plus, Go’s simple syntax reduces the cognitive load on developers. #### Cloud and distributed systems The language’s ability to handle high concurrency aligns well with the needs of cloud-based applications and distributed architectures. Go is the language behind key cloud-native tools like Docker and Kubernetes, which are central to modern cloud infrastructure. Go’s performance and concurrency capabilities effectively manage containerized applications and orchestrate complex systems. #### DevOps tools Go’s ease of use and fast execution help create scripts and tools that automate repetitive tasks and manage system operations. Prominent DevOps tools like Terraform, which automates infrastructure provisioning, and Prometheus, a monitoring and alerting toolkit, are developed in Go. These tools benefit from Go’s efficient execution and ease of deployment. #### Data processing Go is great for building tools and services that handle large data volumes. Its efficient memory management and concurrency features enable effective data processing and manipulation. Go’s speed supports high-speed data pipelines, real-time analytics, and other data-intensive applications, managing multiple data streams concurrently for scalable and efficient handling. Creating a database with Appwrite Go functions: ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/client" "github.com/appwrite/sdk-for-go/databases" ) func main() { appwriteClient := client.NewClient() appwriteClient.SetProject("") appwriteClient.SetKey(“”) appwriteDatabases := databases.NewDatabases(appwriteClient) response, err := appwriteDatabases.Create("unique()", "Pokemon") if err != nil { fmt.Println(err) return } fmt.Println(response) } ``` #### Command-line tools Go is a popular choice for creating command-line applications due to its straightforward syntax and powerful standard library. It supports the creation and management of command-line interfaces, making it easy to develop system utilities and scripts. Many open-source command-line tools benefit from Go’s simplicity, speed, and ease of deployment. #### Real-time applications Go’s efficient concurrency model is well-suited for real-time applications that require handling numerous simultaneous tasks. Chat services, gaming servers, and real-time data processing systems all benefit from Go’s ability to manage concurrent operations smoothly. Go’s goroutines and channels provide a simple yet powerful way to handle real-time communication and processing. #### APIs and backend services Go is often used for developing APIs and backend services due to its performance and scalability. Its fast execution and efficient concurrency handling are perfect for building backend infrastructure that supports high traffic and complex data interactions. Go’s robust standard library simplifies API development with strong support for HTTP and JSON, ensuring backend services can scale to meet growing demands. Here’s how you would create an account email and password session using Appwrite’s SDK. ```go package main import ( "fmt" "github.com/appwrite/sdk-for-go/account" "github.com/appwrite/sdk-for-go/client" ) func main() { appwriteClient := client.NewClient() appwriteClient.SetProject("") appwriteClient.SetKey("") service := account.NewAccount(appwriteClient) response, error := service.CreateEmailPasswordSession( "email@example.com", "example1234", ) if error != nil { fmt.Println(error) return } fmt.Println(response) } ``` ### Go vs. Python - **Performance**: Go generally offers better performance than Python due to its compiled nature, whereas Python is an interpreted language. - **Concurrency**: Go has built-in support for concurrency with goroutines, making it suitable for high-performance, concurrent applications. Python has concurrency libraries like asyncio, but it’s not as seamless as Go. - **Ease of use**: Python is known for its simplicity and readability, making it a popular choice for beginners. Go is also designed to be simple and readable, but its static typing can be more cumbersome for quick scripting tasks. - **Ecosystem**: Python has a vast ecosystem, particularly in data science, machine learning, and web development. Go’s ecosystem is growing, especially in cloud and systems programming. ### Go vs. Java - **Performance**: Both Go and Java offer strong performance, but Java's JVM allows it to perform well in highly optimized environments. Go has a simpler runtime, which can lead to more predictable performance. - **Concurrency**: Go’s concurrency model using goroutines is simpler and more lightweight compared to Java’s thread model, making it easier to write concurrent applications in Go. - **Static typing**: Both languages are statically typed, but Go’s type system is simpler and less verbose than Java’s, reducing boilerplate code. - **Ecosystem**: Java has a mature ecosystem with extensive libraries and frameworks, especially for enterprise applications. Go’s ecosystem is newer but rapidly growing, particularly in cloud services and microservices. ### Go vs. C++ - **Performance**: C++ generally offers higher performance due to its lower-level memory management, but Go provides a good balance of performance and ease of use. - **Memory management**: Go has automatic garbage collection, which simplifies memory management compared to C++’s manual memory management. - **Ease of use**: Go’s syntax is simpler and more modern, making it easier to learn and use compared to C++. - **Safety**: Go is designed with a focus on simplicity and safety, reducing common programming errors found in C++, such as memory leaks and pointer arithmetic issues. ### Go vs. Node.js (JavaScript) - **Performance**: Go typically offers better raw performance due to its compiled nature, whereas Node.js relies on the V8 JavaScript engine. - **Concurrency**: Go’s goroutines provide a straightforward model for concurrent execution, while Node.js uses an event-driven, non-blocking I/O model that can be more complex to manage for certain tasks. - **Ease of use**: JavaScript’s ubiquity and ease of use make Node.js accessible, especially for web developers. Go is also easy to use but requires learning a new language syntax. - **Ecosystem**: Node.js has a vast ecosystem with npm, making it easy to find libraries for almost any need. Go’s ecosystem is more focused, particularly strong in areas like web servers and cloud services. ### What companies use Go? #### Uber Uber uses Go for its high-performance, scalable backend services. The concurrency model of Go allows Uber to handle millions of ride requests simultaneously. Go's efficiency helps reduce latency and improve the overall user experience. More specifically, [they mention](https://www.uber.com/en-CZ/blog/go-geofence-highest-query-per-second-service/) high developer accessibility and reliability: > “Go typically takes just a few days for a C++, Java or Node.js developer to learn, and the code is easy to maintain. This service has had 99.99% uptime since inception. Importantly, we haven’t seen any issues with Go’s runtime.” > #### Dropbox Dropbox migrated its performance-critical components from Python to Go, which significantly improved its file synchronization performance. Go's faster execution speed and concurrency were key factors in handling Dropbox's large-scale data processing. Dropbox also [open-sourced its Go libraries](https://github.com/dropbox/godropbox) to help the community build large production systems. #### SoundCloud SoundCloud uses Go to build internal services that require high concurrency and performance. Go's simplicity and ease of maintenance also help their developers to iterate quickly and deploy updates smoothly. Here’s what [developers at SoundCloud say about Golang](https://developers.soundcloud.com/blog/go-at-soundcloud): > Static typing and fast compilation enable us to do near-realtime static analysis and unit testing during development. It also means that building, testing and rolling out Go applications through our deployment system is as fast as it gets. > #### Monzo Monzo, a UK digital bank, started using Golang for its microservices architecture before eventually moving its entire backend infrastructure over to Go. Monzo’s systems engineer, Matt Heath, [praised Go for its simplicity and speed](https://www.infoq.com/news/2017/03/monzo-bank-golang/): > Go is a perfect language for creating microservice architectures, and the concurrency features, and the language in general, has allowed the easy creation of small and simple networked services at Monzo that are focused around the 'single responsibility principle'. > ### Learning Go programming language Go is pretty simple to learn, especially if you’ve already worked with JavaScript, Python, or C++. Go’s [learning hub](https://go.dev/learn/) offers a wide variety of resources, from guided courses to video tutorials and example-led lessons. One of the best ways to learn Go is to follow their guided learning journeys. They offer dedicated video and written tutorials for [web developers](https://gowebexamples.com/) and [complete beginners](https://www.youtube.com/watch?v=Q0sKAMal4WQ). You can get help and support from the large Go developer community, specifically on [Reddit](https://www.reddit.com/r/golang/). ### Conclusion Golang is a great choice for web app development due to its performance and concurrency capabilities. If you're building a high-traffic, performance-intensive app that needs to be reliable and maintainable, Appwrite’s new Go SDK + Function runtime can help you leverage Go’s speed and simplicity. Here are some more resources to get you started with Go and Appwrite Go SDK: - [3 things you can build with Go runtime](https://appwrite.io/blog/post/3-things-you-can-build-with-go-runtime) - [Go docs](https://go.dev/doc/) - [Appwrite Discord](https://appwrite.io/discord) --- ## What is HIPAA and why should you care https://appwrite.io/blog/post/what-is-hipaa-compliant If you work in the health industry there is a good chance you have heard of HIPAA, but if you don’t, it might be unknown waters. HIPAA stands for the Health Insurance Portability and Accountability Act. It might sound like it’s only the business of healthcare professionals, but if you're working with healthcare apps or platforms that handle health information, HIPAA becomes your business, too. So, let’s break down what HIPAA is and why you, as a developer, need to sit up and take notice. #### What is HIPAA? HIPAA, established in 1996, is a U.S. law designed to provide privacy standards to protect patients' medical records and other health information provided to health plans, doctors, hospitals, and other healthcare providers. It consists of several rules, but the Privacy Rule and the Security Rule are the main ones affecting digital health information. #### What types of apps are required to meet HIPAA regulations? If you are handling data related to health, you need to adhere to the regulations. Here are some examples of applications: - Healthcare apps (like Apple health) - Hospital applications - Patient portals - Medical results dashboard - Patient intake forms - Video apps - Wellness and mental health - Chat apps There are more examples of applications, but the above are the most general. #### Why does it matter to you? **1. Privacy is a priority** In an age where data breaches have become a frequent headline, ensuring the privacy of health information is critical. You must understand HIPAA to build systems that protect patient data against unauthorized access. **2. Security isn't optional** The Security Rule within HIPAA specifies safeguards that are divided into three parts - administrative, physical, and technical. As a developer, you’re in the trenches of technical safeguards, ensuring data encryption, implementing secure access controls, and more. **3. It's the law** Non-compliance with HIPAA can result in hefty fines, legal action, and a tarnished reputation. If your application handles protected health information (PHI), your application must comply with HIPAA regulations, making it your responsibility to handle PHI properly. **4. Trust builds business** When users trust your application to safeguard their health information, they're more likely to use and recommend it. Compliance with HIPAA is not just about avoiding penalties; it’s about building a product that people can trust. **5. Innovation opportunity** With the digital health market growing rapidly, there’s a huge opportunity for innovation in healthcare technology. Understanding HIPAA compliance can be a significant advantage, allowing you to create solutions that meet a critical market need while ensuring privacy and security. You might still wonder, ‘Why should I care?’ I don’t work in health. But remember, if you can handle data that adheres to these strict policies, you show your users they can trust you. #### How you should approach HIPAA - **Educate yourself and your team** Start with a solid understanding of HIPAA's requirements. Resources are available from the U.S. Department of Health & Human Services and other reputable sites. - **Implement strong security measures** Use encryption for data at rest and in transit, ensure proper authentication mechanisms are in place, and regularly update and patch systems. - **Consider HIPAA early on** Integrate HIPAA considerations into the design phase of your development process. It’s easier to build compliance into your application from the start than to retrofit it later. - **Invest in a HIPAA compliant tech stack** Investigate what platforms and tools ad here to HIPAA compliancy and reassure they meet both your tech and compliancy requirements. #### Caring for your users HIPAA might seem daunting at first glance, but it’s fundamentally about protecting individuals' health information. We have the power to build applications that not only innovate healthcare but also safeguard the very personal information people entrust to them. Understanding and implementing HIPAA regulations is not just a legal requirement; it's a critical component of ethical software development in the healthcare domain. So, the next time you come across HIPAA in your development journey, remember it’s more than just compliance—it’s about caring for the data privacy and security of your users. Looking for a HIPAA compliant backend provider? Looking to replace Firebase? Appwrite can be your solution. Take a look at our [documentation](https://appwrite.io/docs/advanced/security) to learn more about our security. --- ## What exactly is MCP, and why is it trending? https://appwrite.io/blog/post/what-is-mcp Updated on October 6, 2025 If you've ever tried using an AI assistant for something practical, like pulling real data from your work files, checking a database, or sending a message, then you've probably hit a frustrating wall. These AI models are brilliant, but they're also **disconnected** from the tools we actually use. You ask: - *“Summarize my latest report.”* → AI doesn't know where your reports are. - *“Check today's database entries.”* → AI can't see your database. - *“Post an update on Slack.”* → AI can't touch Slack. For all their intelligence, AI models are basically **brains with no hands**. They can think, but they can't **interact** with the world outside of what's in their training data. This is where you start to need **MCP (Model Context Protocol)**. And no, that name is not very intuitive. You've probably heard it thrown around on social media and in tech circles, but it doesn't make much sense on its own. **What does "Model Context Protocol" mean?** Let's break it down in a way that actually makes sense. ### So, what's MCP? Forget the fancy words for a second. **MCP** is just a way for AI assistants to interact with real-world tools like databases, file systems, APIs, and apps. Currently, AI assistants can reason, process information, and answer questions, but don't know where anything is, and can't actually do anything outside of its box. Now imagine you give it a phone with direct lines to your company's database, your Slack account, your email inbox, and your documents. That's **exactly** what MCP does. It's the system that allows an AI assistant to: - **Fetch** real data. - **Look up** information from live sources. - **Trigger** actions (like posting messages, running queries, or updating records). Instead of just *guessing* based on past knowledge, the AI can now **pull actual facts** from the places that matter. MCP is just the **standardized way** of making this happen. Instead of every AI needing custom integrations for every tool, MCP provides **one universal way** to hook everything together. MCP was developed by Anthropic and released as an open standard in November 2024, making it a relatively new but rapidly growing solution. ### How does MCP actually work? Let's say you have an AI assistant running on your computer, and you want it to check your company's database for the latest user signups. Without MCP: 1. The AI assistant doesn't have direct access to the database. 2. You'd have to manually open the database, find the right table, and read the data yourself. With MCP: 1. The AI assistant sends a request like *“Get the latest 10 users from the database”* to the MCP server. 2. The **MCP server** translates that into an actual database query and fetches the result. 3. The **MCP server** sends the data back to the AI assistant in a structured way. 4. The AI assistant now “knows” the latest user signups and can use that data to answer you. From your perspective, the AI **just knew** the answer. But behind the scenes, MCP acted as the middleman, handling all the messy API calls and database queries. You can skip this part if you're not interested in yet another technical detail, but here's a bit more on how it works: MCP uses a client-server architecture. The AI assistant (called an MCP host) talks to an MCP client, which connects to an MCP server. The server then accesses data sources, like your database or APIs, securely. Communication happens via standard input/output (stdio) for local servers or Server-Sent Events (SSE) for remote servers, though remote support is still in development. For now, MCP servers run locally to limit data access scope, ensuring security. ### Why would one need MCP? You might be thinking: *“Okay, so AI assistants need a way to talk to external tools. But isn't that just APIs?”* Good question. The key difference is that **MCP standardizes the way AI models access different tools**. #### Before MCP If you wanted an AI to work with five different tools (Slack, Google Drive, a database, GitHub, and an internal API), you had to: - Write custom code for each API. - Deal with different authentication systems for each one. - Manually update integrations when API changes happened. Every AI needed custom connections to every tool it interacted with. #### With MCP MCP solves this problem by introducing **one universal way** for AI to access tools. - Instead of writing five different integrations, the AI only needs to know MCP. - Instead of every AI assistant needing custom code for each tool, MCP servers handle the communication. This makes AI workflows faster, simpler, and scalable. Plus, MCP is flexible. You can switch between different AI providers (like Anthropic, OpenAI, or others) without reconfiguring your integrations, because they all use the same MCP standard. This vendor-agnostic approach is a big win for developers and businesses. ### What can MCP actually do in the real world? MCP is not just another “AI hype”. It's already being used in real tools today. #### 1. AI-assisted coding Development tools like **Zed, Cursor, and Replit** have integrated MCP into their systems to make it much easier for AI agents to retrieve relevant information, better understand context around a task, and provide more accurate code. Instead of AI just *guessing* how your project works, it can: - Read documentation stored in your repo. - Check recent commits for context. - Fetch database queries from your backend. This makes AI agents much more useful for developers, as they can now carry out tasks end-to-end. #### 2. AI assistants that know your business data Companies can now use MCP to enable internal AI assistants to **pull live sales reports, check inventory, and analyze customer data**. An employee could just ask: - *“What were last month's top-selling products?”* - *“Are we seeing any patterns in customer complaints?”* And instead of getting **generic answers**, the AI pulls **real company data** and responds with **accurate insights**. #### 3. A real AI personal assistant Imagine having an AI assistant that actually understands your files, emails, and calendar. Instead of you manually searching for an old PDF or checking your schedule, you just ask: - *“Summarize my meeting notes from last week.”* - *“Find the invoice I received in February.”* MCP lets AI assistants **fetch real information** without you needing to switch apps or dig through folders. #### 4. Cloud platforms like Appwrite Appwrite has introduced the **Appwrite MCP server** to allow AI assistants to interact with Appwrite projects directly. With this, you can now **query databases, fetch user lists, and trigger actions** on your Appwrite project using AI assistants. ### More about Appwrite MCP servers Appwrite now supports two MCP servers that make it easier for AI assistants and developer tools to interact directly with your Appwrite projects and documentation. ### MCP server for Appwrite API Appwrite MCP is an **MCP server** that connects AI assistants to your **Appwrite project**. It supports all Appwrite APIs, including: - **Databases**: Query and retrieve database records - **Users**: Fetch user lists and details - **Storage**: File operations (beta) - **Functions**: Execute and manage functions - **Authentication**: User authentication operations - And more... Note: Some features like file operations (getFile, getFilePreview) are currently in beta. #### How it works Here's a simple example of how Appwrite MCP works: 1. **The AI assistant sends a request** via MCP (e.g., *“Get all users from my Appwrite project”*). 2. **The Appwrite MCP server receives the request** and translates it into an Appwrite API call. 3. **The Appwrite API returns the data** to the MCP server. 4. **The MCP server structures the response** in a format the AI can understand. 5. **The AI assistant uses the data** to generate a response. #### How to set it up An easy way to try out the Appwrite MCP is by using Claude Desktop. Install Claude on your computer if you don't have it already, then navigate to **Settings → Developer** and click on **Edit Config**. This should lead you to your `claude_desktop_config.json` file. Next, edit your config file and add this: ```json { "mcpServers": { "appwrite": { "command": "uvx", "args": [ "mcp-server-appwrite" ], "env": { "APPWRITE_PROJECT_ID": "your-project-id", "APPWRITE_API_KEY": "your-api-key" } } } } ``` > You'll need to [install uvx](https://docs.astral.sh/uv/getting-started/installation/) on your computer for the command to work. Next, opening Claude Desktop should show you a tool icon in the chat input field with a list of the available MCP tools. ![Claude Desktop MCP tool icon](/images/blog/what-is-mcp/claude-mcp-tools.png) If you get an error that the Appwrite MCP server failed to start, it might be that your computer hasn't recognized the `uvx` command yet. In that case, you can replace the `uvx` string in your config object's `command` property with the direct path to your uv installation. Here's an example for MacOS: ```json "command": "/Users/username/.local/bin/uvx", ``` With the MCP server successfully started on Claude Desktop, you should now be able to ask Claude to retrieve data or perform an action on your Appwrite project. ![Claude Desktop MCP chat](/images/blog/what-is-mcp/claude-mcp-chat.png) You can also set up the Appwrite MCP server with other AI tools like Cursor and WindSurf. For more examples and specific setup instructions, check out the [MCP documentation](https://appwrite.io/docs/tooling/mcp?doFollow=true). ### MCP server for Appwrite docs The Appwrite MCP server for documentation allows AI assistants and code-generation tools to access Appwrite’s complete documentation, making it easier to build, debug, and learn directly through natural language commands. This means your AI tools can now: - Access full documentation – AI assistants get direct access to all Appwrite docs. - Stay up to date – Responses are based on the latest published documentation. - Search intelligently – Use semantic search across documentation for faster, more relevant results. - Generate code – Fetch implementation examples and code snippets from the docs. - Learn best practices – Get recommendations from Appwrite’s official guidelines. ### Installation You can integrate the documentation MCP server with multiple tools and editors: - Claude Desktop - Claude Code - Cursor - Windsurf Editor - VS Code - OpenCode For setup instructions and examples, check out the [Appwrite Docs MCP server documentation](/docs/tooling/mcp/docs). ### Final thoughts MCP changes how AI assistants work. Instead of being limited to general knowledge, they can now access **real data from real systems** in a structured way. This shift will increase the usefulness of AI in day-to-day workflows. So if you've ever felt that AI models are great at answering questions but useless when it comes to real-world tasks, MCP is one way to bridge that gap. And because MCP is an **open standard**, it doesn't lock anyone into a single platform or tool. It's something that can evolve and be adopted across different industries. The more systems that integrate MCP, the more natural it becomes to interact with AI as something that can actually get things done. ### Further reading - [Appwrite MCP documentation](/docs/tooling/mcp?doFollow=true) - [Appwrite Docs MCP server](/docs/tooling/mcp/docs) - [Anthropic MCP documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp) - [Building a chat app with Appwrite and Google Gemini](/blog/post/build-a-chat-app-with-appwrite-and-gemini?doFollow=true) --- ## Why developers choose Appwrite over Auth0 and Firebase https://appwrite.io/blog/post/why-developers-choose-appwrite-auth Thanks to AI tools and agentic coding, building apps is faster than ever, but speed often comes at the cost of security. We’ve all seen “vibe-coded” apps launched overnight, only to reveal serious flaws later. If apps are easier to build, they’re also easier to break, making secure authentication critical than ever. Closed-source services like Auth0 and Firebase offer convenience but often bring lock-in, scaling costs, and limited flexibility. In contrast, open-source authentication solutions give organizations more control, transparency, and compliance readiness. One such solution is **Appwrite Authentication:** An open-source alternative that pairs developer-friendly APIs with enterprise-grade security. With **Appwrite Cloud**, you get a fully managed service for speed, and if you prefer, you can also **self-host** if that’s your need. ### TL;DR Appwrite Authentication is an **open-source alternative to Firebase and Auth0** that combines developer-friendly APIs with enterprise-grade security. Unlike closed platforms, it avoids vendor lock-in, offers predictable pricing, and supports both **Appwrite Cloud** (fully managed) and **self-hosting** (full control). With features like MFA, RBAC, OAuth, and compliance with GDPR, HIPAA, and SOC 2, Appwrite is built for **startups, enterprises, agencies, and students alike**. ### Table overview: Authentication services compared | Feature | Appwrite | Auth0 | Firebase Auth | Supabase Auth | | --- | --- | --- | --- | --- | | Open-source | Yes | No | No | Yes | | Self-hosting | Yes | No | No | Yes | | Pricing | Free (self-host) + Paid Cloud | Paid (scales quickly) | Free + Paid Tiers | Free + Paid Tiers | | Multi-Factor Authentication | Yes | Yes | Yes | Yes | | OAuth & Social Providers | Yes | Yes | Yes | Yes | | Role-Based Access Control (RBAC) | Yes | Yes | Limited | Yes | | GDPR/HIPAA compliance options | Yes | Yes | Limited | Limited | | | | | | | ### FAQ ### 1. What is an authentication service? At its core, an authentication service is what makes sure the person (or system) trying to access your app really is who they claim to be. It handles the basics like **signups, logins, and password resets**, but also the more advanced pieces like **session management, tokens, and role-based access**. Think of it as the **front door to your application**. Without it, anyone could walk in. With the right authentication service, only the right users get through, whether that’s your customers, your team, or even other services in a microservices setup. ### 2. Why choose an open-source authentication service? Because with open-source, you’re not locked into someone else’s rules. A closed service like Auth0 might be quick to start, but over time you’re tied to their pricing, their roadmap, and their limitations. With an open-source authentication service, you get: - **Transparency** (you can actually see how it works), - **Flexibility** (run it in the cloud, or self-host if you need to) - **Control** (your data stays where you want it). ### 3. How does Appwrite Authentication compare to Auth0? - **Auth0** offers enterprise-grade features, but can become super expensive as you scale. - **Appwrite** delivers comparable features (OAuth, MFA, RBAC, SSO) but is **open-source** and **self-hostable**, making it attractive for teams of all sizes. We have an in-depth Appwrite vs Auth0 pricing comparison, you can read it [here](/post/appwrite-vs-auth0). ### 4. Does Appwrite support multi-factor authentication (MFA)? Yes. Appwrite Authentication supports MFA, giving developers and security teams an additional layers of protection. ### 5. What are the different types of Authentication methods Appwrite supports? Appwrite supports a variety of authentication methods to fit every app and every niche. Here are some Appwrite's authentication flows: - Email and password - Phone(SMS) - Magic URL - Email OTP - OAuth 2.0 - Anonymous login - JWT - SSR - Custom token - Multi-factor authentication ### 5. Can I migrate from Auth0 or Firebase Auth to Appwrite? Yes. Appwrite provides APIs and migration guides to automatically move accounts, database rows, and storage files from Auth0, Firebase, or other providers, ensuring smooth transitions without user disruption. ### 6. What programming languages and frameworks does Appwrite support? Appwrite offers SDKs for almost all the popular frameworks and languages. [Visit this page](/docs/sdks) to get an extensive list of all the supported SDKs. **Client libraries**: - JavaScript web SDK - Flutter SDK - React Native SDK - Apple SDK - Android SDK **Server libraries** - Node.js SDK - Python SDK - Dart SDK - PHP SDK - Ruby SDK - .NET SDK - Deno SDK - Go SDK - Swift SDK - Kotlin SDK ### 7. Is Appwrite Authentication secure and compliant (GDPR, HIPAA)? Yes. Appwrite takes compliance seriously and is built to safeguard sensitive data across both Appwrite Cloud and self-hosted deployments. Here are the key compliance standards that Appwrite supports: - **GDPR:** The European Union’s regulation for protecting personal data and privacy. - **CCPA:** California’s Consumer Privacy Act, which safeguards consumer data rights. - **SOC 2 Type I:** Certification that validates Appwrite’s security, availability, and operational controls. - **HIPAA:** U.S. standards for securing personal health information in healthcare applications. - **PCI:** Payment Card Industry standards, met through Stripe, for securely handling payment data. ### 8. Can I self-host Appwrite Authentication? Yes. Appwrite can be deployed on your own servers with **Docker**, but most teams choose **Appwrite Cloud** because it’s fully managed, automatically updated, and ready to scale without the overhead of running infrastructure yourself. Self-hosting is available if you prefer, but Cloud is the fastest and simplest way to get started. ### 9. How does Appwrite pricing compare to Auth0? Auth0’s pricing is tied tightly to monthly active users (MAUs) and scales up quickly, often pushing teams into costly enterprise tiers as they grow. Appwrite takes a different approach: its limits are more generous, pricing is predictable, and you’re billed mainly on actual resource usage, not just logins. In practice, your user base can scale to thousands of users without causing a steep jump in your budget. For a detailed breakdown, see our comparison blogs: - [Appwrite vs Auth0: Which is better for a B2C app?](/blog/post/appwrite-vs-auth0-b2c) - [Appwrite Auth vs Auth0: a comparison of authentication services](/blog/post/appwrite-vs-auth0) ### 10. Who should use Appwrite Authentication? Appwrite Authentication is designed to support developers and organizations at every stage. - **Enterprises:** Appwrite supports **SOC 2, GDPR, HIPAA, and CCPA compliance**, making it suitable for regulated industries. Enterprises can choose **Appwrite Cloud** for managed operations at scale. Features like RBAC, SSO, and MFA give security teams the controls they need. - **Startups:** Appwrite makes it easy to move from idea to MVP without being slowed down by infrastructure. With [**Appwrite** **Startups program**](/startups), founders get access to credits, resources, and guidance to scale their authentication layer alongside their business. The free tier is generous, pricing is predictable, and you’re never locked in. - **Software development agencies:** As your client base expands, Appwrite Cloud ensures managed scalability and developer-first APIs that let you deliver faster. Agencies benefit from flexible project setups and [**Appwrite’s Partners program**](/partners), which provides collaboration opportunities, co-marketing, and deeper technical support to help agencies serve clients better. - **Students & hobbyists:** Open-source and free to self-host, Appwrite is perfect for learning and experimenting. Students can build real-world projects using the same tech trusted in production apps, while engaging with an active open-source community for help and collaboration. We also offer a dedicated **[Education program](/education)** that provides resources, guidance, and credits to help students explore modern dev tool in a hands-on way. ### 11. Can I self-host Appwrite as a Firebase alternative? Yes. Unlike Firebase (which is fully tied to Google Cloud), Appwrite gives you the option to self-host. It runs on **Docker**, so you can deploy it on your own servers, private cloud, or even locally for testing. Why this matters: - **Full control** → Decide where your data lives and how your infrastructure runs. - **Compliance** → For industries with strict requirements, self-hosting makes it easier to meet regulations like GDPR or HIPAA. - **Portability** → You’re not locked into one cloud provider — you can start with Appwrite Cloud and move to self-hosting later, or vice versa, without rewriting your app. ### 12. Is Appwrite different from Supabase for authentication? Both Appwrite and Supabase cover the essentials of authentication: user registration, login, passwordless flows, social logins, and role-based access control (RBAC) to manage permissions. Where they differ is in the additional options they provide: - Appwrite supports integration with external systems through custom token login and offers teams and labels to help manage user groups and permissions more easily. - Supabase supports SAML, which is useful for enterprise single sign-on (SSO) scenarios. In short, both platforms deliver strong authentication capabilities, with each offering different features that may fit specific project needs. --- ## Why developers are leaving Next.js for TanStack Start, and loving it https://appwrite.io/blog/post/why-developers-leaving-nextjs-tanstack-start The React world is full of opinions, and every few years a new framework changes how we think about building apps. Next.js has been the default choice for a long time. It offered a clear path to production and made React easier to manage. But recently, more developers have been moving toward **TanStack Start**, a newer full-stack framework built by the same team behind TanStack Query and TanStack Router. They're not leaving because Next.js is broken. They're leaving because **TanStack feels lighter, clearer, and closer to plain React,** and it gives them back control over how things actually work. ### The growing frustration with Next.js Many developers used to love Next.js for its simplicity. But as it grew, so did its complexity. The shift to the App Router, the introduction of React Server Components, and constant new patterns have made the framework harder to follow. In community discussions, one developer summed it up: *"Next.js lost me with the constant changes and complexity. The benefits don't justify the cognitive overload."* Others mentioned how debugging has become confusing and how new features often break old patterns. Another recurring concern is that **Next.js feels tied to Vercel**. Many devs appreciate Vercel as a company, but they feel the framework has become more about the platform than the developer. As one user said, *"It's tough to separate Next.js from Vercel. Most advantages people list are actually Vercel features."* ### TanStack's foundation of trust TanStack didn't appear out of nowhere. It grew from the success of **TanStack Query** (formerly React Query), a library that completely changed how developers handled state and server data. It replaced complex Redux setups with something that "just made sense." That same idea of **simplicity without shortcuts** is what's driving interest in TanStack Start. Developers already trust the TanStack team because their tools consistently solve real problems without hiding what's going on behind the scenes. As one developer put it, *"They did for routing and server logic what they already did for state management — made it simple, predictable, and type-safe."* ### What TanStack Start actually is TanStack Start is a **full-stack React framework** that builds on TanStack Router, powered by **Vite** for fast builds and **Vite Env APIs**, which supports Nitro among other deployment plugins. It combines client-side interactivity with server-side rendering, all while keeping things explicit and type-safe. Key features developers like: - **Type-safe routing:** Everything is validated at compile time. If a route or parameter changes, you'll know immediately. - **Server functions:** You can write backend logic (like database queries or API calls) in the same codebase and call it safely from the client. - **Client-first rendering:** You get SSR for performance, but navigation feels like a single-page app. - **Simple deployment:** Works on Netlify, Cloudflare, Vercel, or your own Node server with minimal setup. It's not trying to reinvent React. It's giving React a modern backbone that's easy to understand. ### Why developers prefer it Developers in the community consistently mention a few themes: 1. **Less magic, more control.** Frameworks like Next.js and Remix rely on "magic" conventions which is behaviors that happen automatically. TanStack Start avoids that. You choose how data loads, where it runs, and what gets rendered. 2. **Feels closer to React.** Many devs say that once they start using TanStack Start, they forget they're even in a framework. It feels like normal React with some helpful extras. 3. **A smoother developer experience.** Type-safe routes, integrated server functions, and predictable data fetching make debugging faster. One Reddit user said, *"When I first tried it, I thought I was missing something because it was so easy — it just worked."* 4. **Flexible hosting and tooling.** Using Vite and Nitro means you can build and deploy anywhere. You're not locked into one company's stack or runtime. ### Real-world use cases The developers experimenting with TanStack Start aren't just building toy projects. They're using it for **production apps**, internal dashboards, and even company websites. A few examples mentioned in community threads: - A **real-time quiz app** built for an event, where switching from React Router 7 to TanStack Start fixed hydration issues right before launch. - A **multi-language analytics dashboard** powered by Laravel on the backend and TanStack Start on the frontend, praised for its easy SSR setup. - **Startups and indie projects** migrating from Next.js because they wanted more predictable code and simpler local hosting options. Many devs say TanStack Start lets them **scale from side projects to production** without losing control or adding complexity. ### Why this shift matters This shift isn't just about replacing one framework with another. It reflects a broader change in what developers value. For years, frameworks have chased automation and abstractions and trying to do more for the developer. But that often came with confusion, slower builds, and less transparency. TanStack's approach goes the other way. It gives developers clarity, type safety, and freedom to understand and shape their stack. As one developer wrote, *"TanStack brings back sanity. It's a framework that 99% of devs actually want to use."* ### The takeaway TanStack Start is still young, but it's already making an impact. Its community is growing quickly, its documentation is improving fast, and it's backed by a team with a proven record of building tools developers trust. Next.js isn't going anywhere, but developers are looking for something that feels simpler and more honest. TanStack Start gives them that: a framework that's powerful without being heavy, modern without being controlling, and familiar without being boring. If you've ever wished React felt like React again, you'll probably understand why developers are leaving Next.js for TanStack Start, and loving it. --- ## Why multi-cloud is taking over https://appwrite.io/blog/post/why-multi-cloud-is-taking-over Choosing the right cloud provider can be challenging, which is why more businesses are turning to **multi-cloud,** using services from multiple cloud providers instead of relying on just one. The newly announced Appwrite Network is a prime example, with its multi-cloud, global, and vendor-agnostic approach. Why multi-cloud? It's simple: it offers better flexibility, control, and performance. Let's break down why multi-cloud is becoming the go-to solution for organizations of all sizes. ### What is multi-cloud? Multicloud means using more than one cloud provider, like AWS, Google Cloud, or Microsoft Azure, for different services. Instead of putting all your eggs in one basket, you spread them out, using the strengths of each provider. This approach lets you select the best features from different vendors to meet your business needs, reducing the risk of vendor lock-in. More organizations are adopting multi-cloud strategies because they allow applications to run where needed without adding unnecessary complexity. ### Benefits of going multi-cloud Appwrite Network uses a multi-cloud, agnostic approach for several key reasons. By leveraging multiple clouds, Appwrite can distribute its services globally, reducing latency and ensuring a more reliable and faster user experience across different regions. #### 1. **No vendor lock-In** Relying on one cloud provider can be risky. What if their prices go up? Or their services start to slow down? Multicloud keeps your options open. You're free to switch things up whenever you want without being tied to one vendor's rules or pricing. #### 2. **Better performance and cost savings** Every cloud platform has its strengths. Some are great at handling data, while others excel at running applications. With multi-cloud, you can pick the best tool for each job. This means you get better performance and can even save money by optimizing your cloud use. For instance, you could use Google Cloud for your AI projects while letting AWS handle your data storage—getting the best of both worlds without overspending. #### 3. **Global reach** With multi-cloud, your business isn't limited to the geographical reach of a single provider. You can leverage multiple cloud providers to have a truly **global presence**. Need a data center in Asia, Europe, and North America? No problem. By using different cloud services in different regions, you can reach users wherever they are—faster and more reliably. This also comes in handy when dealing with data sovereignty laws, which often require data to be stored in specific countries. With multi-cloud, you can easily store data in compliance with local regulations while still maintaining a global footprint. #### 4. **Reduced latency** One of the biggest challenges in cloud computing is **latency**—the delay between a user making a request and the system responding. When your cloud provider's data center is far from your user, that lag can be noticeable. With multi-cloud, you can strategically place your services on data centers that are closest to your users, reducing latency and speeding up performance. For example, if your app has users in Europe and Asia, you can deploy servers in both regions through different providers. This reduces the time it takes for data to travel, resulting in a smoother, faster experience for your users. #### 5. Data compliance and security If your business operates in multiple regions, you probably have to deal with different rules about where data is stored. With multi-cloud, you can store data in different locations using multiple providers, ensuring you meet all those pesky legal requirements. For example, you might store European data on a cloud provider that complies with GDPR while using another provider for data in the US. #### 6. Flexibility Multi-cloud gives you access to a wider range of tools and technologies. Need a feature that one cloud offers but another doesn't? No problem. You can combine them. This flexibility allows you to stay agile and adapt quickly to changes in your business or technology needs. No single cloud provider is perfect at everything. Some excel at certain things, like AWS for scalability or Google Cloud for AI tools. By using multi-cloud, you get access to the best features each platform offers, helping you stay ahead of the competition. ### Cons of multi-cloud While multi-cloud offers plenty of benefits, it also comes with some challenges that businesses need to consider: 1. **Increased complexity**: Managing multiple cloud platforms can quickly become complicated. Each provider has its own set of tools, services, and interfaces, which require more time and expertise to manage efficiently. 2. **Higher costs**: Multicloud setups can lead to higher costs, as you may need to pay for additional services, data transfers, or even specialized management tools to keep everything running smoothly across providers. 3. **Security risks**: As more platforms are used, the attack surface expands, making security management more difficult. Businesses need to ensure strong security practices across all providers to avoid vulnerabilities. 4. **Data integration challenges**: Moving data between different cloud providers can be tricky. Ensuring seamless integration and preventing data silos is a common challenge in a multi-cloud environment. ### Multicloud is the future Multicloud isn't just a trend. It's a smart strategy for businesses that want flexibility, reliability, and the best tools at their disposal. It frees you from vendor lock-in, optimizes performance, and gives you peace of mind with backup options. Multicloud is taking over because it puts the power back in the hands of businesses, letting them choose the right tools, minimize risks, and deliver a better user experience. If you'd like to learn more about how the Appwrite Network makes best use of the multi-cloud approach, take a look at these resources: - [Appwrite Network announcement](/blog/post/appwrite-network-announcement?doFollow=true) - [How to reduce cloud latency](/blog/post/how-to-reduce-cloud-latency?doFollow=true) - [Introducing Database Backups](/blog/post/introducing-database-backups?doFollow=true) --- ## Why you need to try the new Bun function runtime https://appwrite.io/blog/post/why-you-need-to-try-the-new-bun-runtime When Bun announced their 1.0 release, marking Bun stable and production-ready, we excitedly began working on a Bun runtime for [Appwrite Functions](/docs/products/functions). We loved the idea of Bun, because it’s more than just another Node runtime but a whole set of tools. Bun does everything from dependency management to testing and beyond with consistent DX and light-speed performance. This idea of being simple, reducing distractions, and giving developers all the tools in one place to just build stuff resonated with Appwrite’s product philosophy. Here’s a small selection of cool features that make it particularly useful as an Appwrite Function runtime. ### Dependency management Bun has its own [dependency management tool](https://bun.sh/package-manager), which is just `bun install`. You can still use `npm` or `yarn` or `pnpm`, but we like the idea that Bun just gives you something so you don’t need to make one more decision. Bun is also fast, like really fast. I ran package installs on fresh containers for a `create svelte@latest` project, here are the numbers. Good ol’ NPM. ```bash $ npm create svelte@latest my-app-node $ cd my-app-node && npm install npm install added 111 packages, and audited 112 packages in 6s 11 packages are looking for funding run `npm fund` for details found 0 vulnerabilities ``` Bun install. ```bash $ bun create svelte@latest my-app-bun $ cd my-app-bun && bun install bun install v1.0.23 (83f2432d) + @fontsource/fira-mono@4.5.10 (v5.0.8 available) + @neoconfetti/svelte@1.0.0 (v2.2.1 available) + @sveltejs/adapter-auto@3.1.0 + @sveltejs/kit@2.3.3 + @sveltejs/vite-plugin-svelte@3.0.1 + svelte@4.2.8 + svelte-check@3.6.3 + typescript@5.3.3 + vite@5.0.11 warn: @sveltejs/kit's postinstall script took 1.2s 111 packages installed [1336.00ms] ``` This is really important to Appwrite Function developers during development because it shaves off seconds of time each time you deploy code to Appwrite Cloud. These seconds add up to minutes if you’re deploying and testing code 10 times in an hour. Try `bun install` on your projects and see how much time it can save for you. ### Typescript out of the box You don’t need to run `tsc` to transpile Typescript using Bun. This is convenient, saves time, and avoids a messy `dist/` folder. It just works. Again, in the context of an Appwrite Function, the build time you can save is pretty significant. Here are the deploy times for a simple typescript [Starter Function](https://github.com/appwrite/templates) template in Bun and Node.js **Bun** build time ![Bun build time](/images/blog/why-you-need-to-try-the-new-bun-runtime/bun-buildtime.png) **Typescript** build time ![Typescript build time](/images/blog/why-you-need-to-try-the-new-bun-runtime/ts-buildtime.png) ### Implements standard Web APIs Bun implements many web standards, like `fetch`, `FormData`, `WebSocket`, and other useful tools. This means you no longer need to install [node-fetch](https://www.npmjs.com/package/node-fetch), [formdata-node](https://www.npmjs.com/package/formdata-node), and dozens of other libraries in your projects just to do simple things like making an HTTP request. For example, I can write this Appwrite Function to fetch random Capybara with 0 dependencies. ```js export default async ({ req, res, log, error }) => { if(req.method !== 'GET') { return res.text('Not found', 404); } const response = await fetch(`https://api.giphy.com/v1/gifs/random?api_key=${process.env["GIPHY_API_KEY"]}&tag=capybara`); const data = await response.json(); const gifUrl = data.data.images.fixed_height.url; return res.json({ capybara: gifUrl, message: 'Capybara of the day!' }); }; ``` Fewer dependencies, fewer decisions to make, and more focus on building cool stuff. **JSX out of the box** Bun will happily let you mix `tsx` and `jsx` files into your code. Bun will just transpile your [JSX into vanilla JS before execution](https://bun.sh/docs/runtime/jsx). This is perfect for Appwrite Functions! Appwrite Functions exposes HTTPS endpoints for each function, which lets you build simple web forms for collecting feedback, gathering contact information, or displaying a random GIF of a capybara. Write some simple JSX and render it to a readable stream with `react-dom`. Then we can return this in our Appwrite Function. ```jsx import { renderToReadableStream } from "react-dom/server"; const styles = ` body { font-family: sans-serif; text-align: center; } div { max-width: 600px; margin: 0 auto; } img { width: 100%; height: auto; }`; const GIPHY_API = "https://api.giphy.com/v1/gifs/random"; export async function fetchGif(tag: string) { const response = await fetch( `${GIPHY_API}?api_key=${process.env.GIPHY_API_KEY}&tag=${tag}` ); const { data } = (await response.json()) as any; return data.images.fixed_height.url; } export default async function handler({ req, res, log, error }: any) { if (!process.env.GIPHY_API_KEY) { throw new Error("GIPHY_API_KEY is not set."); } if (req.method !== "GET") { return res.json( { message: "Method not allowed.", }, 405 ); } const gifUrl = await fetchGif("Cute capybara"); // We're returning JSX! const html = await renderToReadableStream( <>